• UOJ NOI Round 4


    科技不够场。

    序列妙妙值

    出题人 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),并且还掌握着每条信道的管理权限,然而你并不知道每条信道连接着哪两个结点。

    为了恢复出网络结构,你可以使用一种土办法:重启大法!

    当然重启也是需要智慧的。具体来说,你可以进行若干次操作,每次操作方式如下:

    1. 给每个结点 (i) 标上一个自己定的权值 (a_i)

    2. 选取一个信道的子集 (S),把不在 (S) 里的信道都关闭,只让 (S) 里的信道保持开启状态;

    3. 此时,每个结点 (i) 会自动计算出与 (i) 通过开启状态的信道直接相邻的所有点 (v)(a_v) 异或和,记为 (b_i)

    4. 你通过管理员权限获取所有结点的 (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;
    }
    
  • 相关阅读:
    调用系统api修改系统时间
    格式化为货币
    select的使用(二)
    select的使用(一)
    保留n位四舍五入小数
    加密解密,CryptoStream()的使用
    从字符串总分离文件路径、命名、扩展名,Substring(),LastIndexOf()的使用;替换某一类字符串,Replace()的用法
    根据标点符号分行,StringBuilder的使用;将字符串的每个字符颠倒输出,Reverse的使用
    将汉字转化为拼音,正则表达式和得到汉字的Unicode编码
    unicode编码、字符的转换和得到汉字的区位码
  • 原文地址:https://www.cnblogs.com/autoint/p/13493480.html
Copyright © 2020-2023  润新知