科技不够场。
序列妙妙值
出题人 01 很喜欢加法,也很喜欢异或运算(即 C/C++ 里的 ^
运算符)。有天他一拍脑袋:把两个运算混一起,岂不是妙极了?
对于一个序列 (b_1, dots, b_m),他希望你将序列 (b) 划分为 (k) 段连续非空子序列,使得每一段的异或和之和最小。即,他想知道在所有满足 (0 = p_0 < p_1 < cdots < p_k = m) 条件的序列 (p) 中下式的最小值: $$ sum_{i=1}^{k} (b_{p_{i-1} + 1} mathbin{mathrm{xor}} cdots mathbin{mathrm{xor}} b_{p_i}) $$ 这个最小值即称为这个序列的妙妙值。
但是这个问题非常简单,于是出题人 01 找来了一个长度恰好为 (n) 的非负整数序列 (a_1, dots, a_n)。他想考考你,(a) 的每个前缀 (a_1, dots, a_j) ((k le j le n))的妙妙值分别是多少呢?
对于所有测试点,满足 (1 le k le n le 60000, k le 8,a_i < 2^{16})
题解
显然的DP状态(f(i,j))表示前(j)个数分(i)段的最小代价。
考虑这样一个暴力,把(f(i-1,j))存在(g(j))里,然后转移(f(i,j))的时候枚举(k)从(g(k))转移。时间复杂度(O(knv))。
本质上这是(O(1))修改,(O(v))查询。现在我们尝试平衡一下两部分的复杂度。
把前(8)位的异或贡献放到修改的时候做,把后(8)位的异或贡献放到查询的时候做。这样就能做到(O(knsqrt{v}))。
CO int N=6e4+10,inf=1e9;
int a[N],g[1<<8][1<<8],f[9][N];
int main(){
int n=read<int>(),m=read<int>();
for(int i=1;i<=n;++i) a[i]=a[i-1]^read<int>();
f[1][0]=inf,copy(a+1,a+n+1,f[1]+1);
for(int k=2;k<=m;++k){
for(int x=0;x<1<<8;++x) fill(g[x],g[x]+(1<<8),inf);
for(int i=0;i<=n;++i){
f[k][i]=inf;
for(int x=0;x<1<<8;++x) f[k][i]=min(f[k][i],g[a[i]>>8][x]+((a[i]&((1<<8)-1))^x));
for(int x=0;x<1<<8;++x) g[x][a[i]&((1<<8)-1)]=min(g[x][a[i]&((1<<8)-1)],f[k-1][i]+((a[i]>>8^x)<<8));
}
}
for(int i=m;i<=n;++i) write(f[m][i],"
"[i==n]);
return 0;
}
网络恢复
这是一道交互题。
出题人 02 喜欢网上冲浪。可是这天,他所在的小区的网络坏掉了,于是他喊来了你帮忙修一修。
小区的网络由 (N) 个网络结点和 (M) 条信道组成,可以被看作是一张 (N) 个点 (M) 条边的无向简单图(简单图满足任意两点之间至多存在一条直接相连的边,且没有自环)。点从 (1 sim N) 编号,边从 (1 sim M) 编号。目前,你只知道信道的总数是 (M),并且还掌握着每条信道的管理权限,然而你并不知道每条信道连接着哪两个结点。
为了恢复出网络结构,你可以使用一种土办法:重启大法!
当然重启也是需要智慧的。具体来说,你可以进行若干次操作,每次操作方式如下:
-
给每个结点 (i) 标上一个自己定的权值 (a_i);
-
选取一个信道的子集 (S),把不在 (S) 里的信道都关闭,只让 (S) 里的信道保持开启状态;
-
此时,每个结点 (i) 会自动计算出与 (i) 通过开启状态的信道直接相邻的所有点 (v) 的 (a_v) 异或和,记为 (b_i);
-
你通过管理员权限获取所有结点的 (b_i) 值,然后关闭的信道都重启,网络恢复至原状。
请你在不超过 (50) 次操作内,求出所有信道构成的集合。
注意,你只需要求出信道的集合。即,你只需要恢复出哪些结点之间有信道,不用恢复出每条信道对应的编号。
题解
每轮随机取出一些边,只考虑被取出的边。每条边在不同的轮中可以被重复取出。使用剥叶子的方法持续找出度为(1)的点,直到剩下每个点的度都至少为(2)。每条边有玄学的概率在至少一轮中被发现了。能得80分。
CO int N=5e4+10,M=3e5+10;
uint64 a[N],b[N];
int p[M];
unordered_map<uint64,int> f;
int que[2*N];
set<pair<int,int> > e;
void report(int x,int y){
if(x>y) swap(x,y);
if(e.count({x,y})) return;
e.insert({x,y});
Report(x,y);
}
void solve(int n){
int l=1,r=n;
for(int i=1;i<=n;++i) que[i]=i;
while(l<=r){
int x=que[l++];
if(!f.count(b[x])) continue;
int y=f[b[x]];
report(x,y);
b[y]^=a[x],b[x]=0;
que[++r]=y;
}
}
void Solve(int n,int m){
srand(20030506);
for(int i=1;i<=n;++i) a[i]=gen(),f[a[i]]=i;
iota(p+1,p+m+1,1);
random_shuffle(p+1,p+m+1);
int len=(m+49)/50;
for(int t=1;t<=50;++t){
vector<uint64> b=Query(vector<uint64>(a+1,a+n+1),vector<int>(p+(t-1)*len+1,p+min(t*len,m)+1));
copy(b.begin(),b.end(),::b+1);
solve(n);
}
}
稍微加点优化,就能得到100分。考察选手乱搞能力。
CO int base=(1<<16)-1;
mt19937_64 gen(20030506);
int n,m;
set<pair<int,int> > e;
IN void report(int x,int y){
e.insert(minmax(x,y));
}
void solve(vector<int> s){
vector<uint64> a(n);
for(int i=0;i<n;++i){
a[i]=gen()>>16<<16; // random number for leaf
if(gen()&1) a[i]|=i; // identity number for circle
}
vector<uint64> b=Query(a,s);
unordered_map<uint64,int> f;
for(int i=0;i<n;++i) f[a[i]]=i;
deque<int> q;
for(int i=0;i<n;++i)if(f.count(b[i])) q.push_back(i); // deg=1
for(int t=0;t<1000;++t){
while(q.size()){
int x=q.front();q.pop_front();
if(!b[x]) continue;
int y=f[b[x]];
report(x,y);
b[x]=0;
if(b[y]!=a[x] and b[y]){
b[y]^=a[x];
if(f.count(b[y])) q.push_back(y);
}
}
vector<int> rem;
for(int i=0;i<n;++i)if(b[i]) rem.push_back(i);
if(rem.empty()) break;
shuffle(rem.begin(),rem.end(),gen);
int any=0;
function<void(int,int)> judge=[&](int x,int y)->void{
if(b[x] and b[y] and f.count(b[x]^a[y])){
report(x,y);
b[x]^=a[y];
b[y]^=a[x];
q.push_back(x);
if(f.count(b[y])) q.push_back(y);
any=1;
}
};
for(int x:rem){
int y=b[x]&base;
if(0<=y and y<n) judge(x,y);
}
if(!any){
for(int i=0;i<(int)rem.size()*10;++i){
int x,y;
do x=gen()%rem.size(),y=gen()%rem.size();
while(x==y);
judge(rem[x],rem[y]);
}
}
if(!any) break;
}
}
void Solve(int n,int m){
::n=n,::m=m;
int c=n*0.8;
for(int i=0;i<m;i+=c){
int need=min(i+c,m);
vector<int> s(need-i);
iota(s.begin(),s.end(),i+1);
solve(s);
while((int)e.size()<need){
vector<int> s0,s1;
for(int x:s) gen()&1?s0.push_back(x):s1.push_back(x);
solve(s0);
if((int)e.size()==need) break;
solve(s1);
}
}
for(CO pair<int,int>&p:e) Report(p.first+1,p.second+1);
}
校园闲逛
为了出题,出题人 03 喜欢在校园里闲逛。
校园可以看成抽象成是一张 (n) 个点 (m) 条边的有向图,第 (i) 条边从 (a_i) 连向 (b_i),边权为 (c_i)。
出题人 03 总共会闲逛 (Q) 天,在第 (i) 天,他会从 (x_i) 出发,到达 (y_i),同时希望自己走过的路径边权和恰好为 (v_i)。
出题人 03 很好奇,每一天他可以有多少种不同的路径呢? 由于答案很大,你只需要回答答案对 (998244353) 取模的结果。
同一条路径可以多次经过同一条边。两条路径相同当且仅当两条路径上的边数相同且边的编号依次相等。
对于所有测试点,满足 (1 le n le 8,0 le m le 300000,1 le max_v le 65000,0 le Q le 10000)。
题解
矩阵多项式求逆模板题(误)。
需要注意求逆过程中矩阵乘法的顺序。
struct matrix {int x[8][8];} e;
void init_matrix(){
for(int i=0;i<8;++i) e.x[i][i]=1;
}
matrix operator+(matrix a,CO matrix&b){
for(int i=0;i<8;++i)for(int j=0;j<8;++j)
a.x[i][j]=add(a.x[i][j],b.x[i][j]);
return a;
}
matrix operator-(matrix a,CO matrix&b){
for(int i=0;i<8;++i)for(int j=0;j<8;++j)
a.x[i][j]=add(a.x[i][j],mod-b.x[i][j]);
return a;
}
matrix operator*(matrix a,int b){
for(int i=0;i<8;++i)for(int j=0;j<8;++j)
a.x[i][j]=mul(a.x[i][j],b);
return a;
}
matrix operator*(CO matrix&a,CO matrix&b){
matrix ans={};
for(int k=0;k<8;++k)
for(int i=0;i<8;++i)for(int j=0;j<8;++j)
ans.x[i][j]=add(ans.x[i][j],mul(a.x[i][k],b.x[k][j]));
return ans;
}
typedef vector<matrix> poly;
CO int N=1<<17;
int omg[2][N],rev[N];
void init_poly(){
omg[0][0]=1,omg[0][1]=fpow(3,(mod-1)/N);
omg[1][0]=1,omg[1][1]=fpow(omg[0][1],mod-2);
rev[0]=0,rev[1]=1<<16;
for(int i=2;i<N;++i){
omg[0][i]=mul(omg[0][i-1],omg[0][1]);
omg[1][i]=mul(omg[1][i-1],omg[1][1]);
rev[i]=rev[i>>1]>>1|(i&1)<<16;
}
}
template<bool dir>
void FFT(poly&a){
int lim=a.size(),len=log2(lim);
for(int i=0;i<lim;++i){
int r=rev[i]>>(17-len);
if(i<r) swap(a[i],a[r]);
}
for(int i=1;i<lim;i<<=1)
for(int j=0;j<lim;j+=i<<1)for(int k=0;k<i;++k){
matrix t=a[j+i+k]*omg[dir][N/(i<<1)*k];
a[j+i+k]=a[j+k]-t,a[j+k]=a[j+k]+t;
}
if(dir){
int ilim=fpow(lim,mod-2);
for(int i=0;i<lim;++i) a[i]=a[i]*ilim;
}
}
poly operator~(poly a){
int n=a.size();
poly b={e};
a.resize(1<<(int)ceil(log2(n)));
for(int lim=2;lim<2*n;lim<<=1){
poly c(a.begin(),a.begin()+lim);
c.resize(lim<<1),FFT<0>(c);
b.resize(lim<<1),FFT<0>(b);
for(int i=0;i<lim<<1;++i) b[i]=b[i]*(e*2-c[i]*b[i]);
FFT<1>(b),b.resize(lim);
}
return b.resize(n),b;
}
int main(){
init_matrix();
init_poly();
int n=read<int>(),m=read<int>(),q=read<int>(),lim=read<int>();
poly f(lim+1);
f[0]=e;
while(m--){
int x=read<int>()-1,y=read<int>()-1,w=read<int>();
f[w].x[x][y]=add(f[w].x[x][y],mod-1);
}
f=~f;
while(q--){
int x=read<int>()-1,y=read<int>()-1,w=read<int>();
write(f[w].x[x][y],'
');
}
return 0;
}