ICPC2021 沈阳站

B-Bitwise Exclusive-OR Sequence

牛客网

题意:对于\(n(n<=1*10^5)\)个数的序列,给定\(m(m<=2*10^5)\)个限制条件,每个限制条件形如\(u\) \(v\) \(w(w<2^{30})\)表示\(a_u\) \(xor\) \(a_v\) \(=\) \(w\),构造一个满足所有限制条件序列,使得序列所有元素的和最小;如果不存在输出-1。

分析:根据按位异或的性质,把w拆成30位的二进制数来考虑。对于一组限制条件u v w,如果w的第k位为1,那么\(a_u\)\(a_v\)的第k位不同;如果w的第k位为1,那么\(a_u\)\(a_v\)的第k位相同。可以用扩展域并查集来表示上述两种情况,设\(f[i]\)表示\(a_i\)的第k位为1,\(f[i+n]\)表示\(a_i\)的第k位为0,那么如果\(a_u\)\(a_v\)的第k位不同,则合并\(f[u]\)\(f[v+n]\)\(f[u+n]\)\(f[v]\);如果\(a_u\)\(a_v\)的第k位相同,则合并\(f[u]\)\(f[v]\)\(f[u+n]\)\(f[v+n]\)。序列不存在等价于并查集合并过程中出现矛盾。每个限制条件都处理完之后,对于当前第\(k\)位,其对答案产生的贡献就是赋值为1的个数\(num\)乘上\(2^k\)

为了求\(num\),对于每个集合还要记录一个size,初始化\(size[i]=1(1<=i<=n),size[i]=0(n+1<=i<=n+n)\),因为任意\(a_i\)第k位要么为0要么为1,只可能出现一种情况,而且最后\(i\)\(i+n\)一定在两个相对的集合,此时这两个集合赋1赋0是可以相互交换的(二分图染色的性质),因此不妨开始先认为第k位都为1。于是\(num=min(size[find(i)],size[find(i+n)])\),即相对的两个集合谁\(size\)小就对谁赋1,保证最优。

时间复杂度\(O(30*mlogn)\),可能会卡常?据说优化的二分图做法可以做到\(mlogn\)。最后提醒一句,十年oi一场空......

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
	char ch = getchar(); int x = 0, f = 1;
	while (ch < '0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
	while ('0' <= ch && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
	return x * f;
}
const int N=2e5+5;
int n,m;
int u[N],v[N],w[N],fa[N],Size[N];
int find(int x){
	if(x!=fa[x])fa[x]=find(fa[x]);
	return fa[x];
}
int main(){
	n=read();m=read();
	for(int i=1;i<=m;++i){
		u[i]=read();v[i]=read();w[i]=read();
	}
	ll ans=0;
	for(int k=0;k<30;++k){
		for(int i=1;i<=n;++i){
			fa[i]=i;fa[i+n]=i+n;
			Size[i]=1;Size[i+n]=0;
		}
		for(int i=1;i<=m;++i){
			int x=find(u[i]),y=find(v[i]);
			int xx=find(u[i]+n),yy=find(v[i]+n);
			if(w[i]&(1<<k)){//异或为1 
				if(x==y){
					cout<<"-1"<<endl;
					return 0;
				}
				if(x==yy)continue;
				fa[x]=yy;Size[yy]+=Size[x];
				fa[xx]=y;Size[y]+=Size[xx];
			}
			else{
				if(x==yy){
					cout<<"-1"<<endl;
					return 0;
				}
				if(x==y)continue;
				fa[x]=y;Size[y]+=Size[x];
				fa[xx]=yy;Size[yy]+=Size[xx];
			}
		}
		for(int i=1;i<=n;++i){
			int x=find(i),y=find(i+n);
			ans+=1ll*min(Size[x],Size[y])*(1ll<<k);
			Size[x]=0;Size[y]=0;
		}
	}
	cout<<ans<<endl;
	return 0;
}

E-Edward Gaming, the Champion

牛客网

题意:给定一个小写字母字符串(长度\(<=200000\)),查找"edgnb"出现的次数。

分析:签到题。略。

F-Encoded Strings I

牛客网

题意:给定一个长度为\(n(n<=1000)\)的字符串S,对于每一个前缀子串\(S[1]~S[i](1<=i<=n)\),都可以转换为另一个字符串\(T_i\),转换规则为:对于某个字符\(c\),在子串中它最后出现在位置\(j(1<=j<=i)\),如果位置\(j\)后面还有\(k\)个与\(c\)不同的字符,那么前缀子串中的所有字符\(c\)都要转换成\(a\)~\(z\)中的第k+1个字符。对于所有的\(T_i\)输出字典序最大的那一个。

分析:\(n<=1000\),可以直接根据题意暴力来做。转换规则看上去很复杂,实际上就是对于每一个前缀子串从后往前扫描,第一个碰到的新字符转换为\('a'\),第二个碰到的新字符转换为\('b'\),依此类推即可。每一次转换出新串之后,和当前的最优解比较字典序看是否需要更新即可。时间复杂度\(O(n^2)\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
	char ch = getchar(); int x = 0, f = 1;
	while (ch < '0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
	while ('0' <= ch && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
	return x * f;
}
const int N=1e3+5;
char s[N],ch[N],Ans[N],cnt[N];
int tong[25],ans;
void update(char s1[N],int len1,char s2[N],int len2){
	for(int i=1;i<=min(len1,len2);++i){
		if(s1[i]>s2[i])return;
		if(s1[i]<s2[i]){
			ans=len2;
			for(int j=1;j<=len2;++j)Ans[j]=s2[j];
			return;
		}
	}
	if(len1>=len2)return;
	ans=len2;
	for(int j=1;j<=len2;++j)Ans[j]=s2[j];
}
int main(){
	int n=read();
	for(int i=1;i<=n;++i)cin>>s[i];
	ans=1;Ans[1]='a';
	for(int i=2;i<=n;++i){
		for(int j=1;j<=20;++j)tong[j]=0;
		int now=0;
		for(int j=i;j>=1;--j){
			if(tong[s[j]-'a'+1]==0)tong[s[j]-'a'+1]=++now;
			cnt[j]=tong[s[j]-'a'+1]-1+'a';
		}
		update(Ans,ans,cnt,i);
	}
	for(int i=1;i<=ans;++i)cout<<Ans[i];cout<<endl;
	return 0;
}

H-Line Graph Matching

牛客网

题意:给定n个点m条边的无向图,相邻的两条边可以匹配,求所有可以匹配的边的最大边权和。\(n<=10^5,n-1<=m<=2*10^5\)。保证无向图一定联通,且不存在自环和重边。

分析:m为偶数,那么所有边都能匹配,直接输出所有边权之和即可。m为奇数,考虑删一条边权最小的边,这条边可删,要么是无向图的非割边(删掉这条边无向图仍然连通,因此剩下的边仍然可以两两匹配),要么是无向图的割边且该割边把无向图分成两个连通块之后,每个连通块内边的条数均为偶数。

求无向图的割边使用tarjan算法(模板),求出所有的割边之后,可以像有向图那样缩点,其实割边一定不在环内,非割边一定在环内,缩点之后整个无向图成了一棵树,每条树边就是原图的割边。对树进行dfs,每次遍历到一条边,就判断删除这条边之后,两个连通块内是否都是偶数条边,因此时间复杂度为\(O(n)\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
	char ch = getchar(); int x = 0, f = 1;
	while (ch < '0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
	while ('0' <= ch && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
	return x * f;
}
const int N = 1e5 + 5;
const int M = 4e5 + 5;
int n, m,minn=1e9,du[N], vis[N],tot_du[N];
int tot = 1, head[N], nxt[M],to[M],w[M];
int cnt, dfn[N], low[N], bridge[M];
int TOT, HEAD[N], TO[M], NXT[M],W[M];
int dcc,col[N];
void add(int x, int y, int z) {
	nxt[++tot] = head[x]; head[x] = tot; to[tot] = y;w[tot]=z;
}
void Add(int X, int Y,int Z) {
	NXT[++TOT] = HEAD[X]; HEAD[X] = TOT; TO[TOT] = Y;W[TOT]=Z;
}
void tarjan(int u, int inedge) {
	dfn[u] = low[u] = ++cnt;
	int flag = 0;
	for (int i = head[u]; i; i = nxt[i]) {
		int v = to[i];
		if (!dfn[v]) {
			tarjan(v, i);
			low[u] = min(low[u], low[v]);
			if (low[v] > dfn[u])bridge[i] = bridge[i ^ 1] = 1;
		}
		else if (i != (inedge ^ 1))
			low[u] = min(low[u], dfn[v]);
	}
}
void dfs(int u){
	col[u]=dcc;
	tot_du[dcc]+=du[u];
	for(int i=head[u];i;i=nxt[i]){
		int v=to[i];
		if(col[v]||bridge[i])continue;
		dfs(v);
	}
}
void DFS1(int u){
	for(int i=HEAD[u];i;i=NXT[i]){
		int v=TO[i];
		if(vis[v])continue;
		vis[v]=1;
		DFS1(v);
		tot_du[u]+=tot_du[v];
	}
}
void DFS2(int u){
	for(int i=HEAD[u];i;i=NXT[i]){
		int v=TO[i];
		if(vis[v])continue;
		vis[v]=1;
		int pd=(tot_du[v]-1)/2;
		if(pd%2==0){
			minn=min(minn,W[i]);
		}
		DFS2(v);
	}
}
int main() {
	n = read(); m = read();
	ll sum = 0;
	for (int i = 1; i <= m; ++i) {
		int x = read(), y = read(), z = read();
		add(x, y, z); add(y, x, z);
		++du[x]; ++du[y]; sum += z;
	}
	if (m % 2 == 0) {
		cout << sum << endl;
		return 0;
	}
	for (int i = 1; i <= n; ++i) {
		if (!dfn[i])tarjan(i, 0);
	}
	//非割边直接删除
	//割边如果分成偶数 就可以删除 
	for(int i=1;i<=n;++i){
		if(!col[i]){
			++dcc;
			dfs(i);
		}
	} 
	for(int i=2;i<tot;i+=2){
		if (!bridge[i]) {
			minn=min(minn,w[i]);
			continue;
		}
		int u=to[i];
		int v=to[i^1];
		if(col[u]==col[v])continue;
		Add(col[u],col[v],w[i]);
		Add(col[v],col[u],w[i]);
	}
	for(int i=1;i<=dcc;++i)vis[i]=0;
	vis[1]=1;DFS1(1);
	for(int i=1;i<=dcc;++i)vis[i]=0;
	vis[1]=1;DFS2(1);
	cout<<sum-minn<<endl;
	return 0;
}

J-Luggage Lock

牛客网

题意:4位数字密码锁,给定初始状态\(a_1a_2a_3aa_4\)和最终状态\(b_1b_2b_3b_4\),每一次操作可以对连续的数位加一或者减一,求最少操作次数。多组询问,\(T<=1*10^5\)

分析:初始状态和最终状态的具体数值不重要,重要的是每一位的差值,因此每一对询问都可以转换成\(0000\)到某一状态。具体来说,拿\(a_i-b_i\),如果为负数就加10。密码锁总共只有10000个状态,每一次操作可以扩展出20个状态,因此可以考虑BFS预处理出\(0000\)到所有状态的最短路,就能做到\(O(1)\)回答。

有一个点没注意,调了一个多小时,就是状态扩展的时候不能直接对密码锁数字整体操作,也就是不能用加\(1111\),加\(1110\),减\(110\)这些操作来代替,而要一位一位加减,同时记得加10模10.举个很简单的例子,现在的状态是\(1001\),如果直接减\(1111\),(再加10000)得到的是\(9990\),而实际上应该是\(990\)

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
	char ch = getchar(); int x = 0, f = 1;
	while (ch < '0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
	while ('0' <= ch && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
	return x * f;
}
const int N=2e5+5;
int q[10005],step[10005];
int dx[10][4]={{1,1,1,1},{1,1,1,0},{0,1,1,1},{1,1,0,0},{0,1,1,0},{0,0,1,1},{1,0,0,0},{0,1,0,0},{0,0,1,0},{0,0,0,1}};
int main(){
	int head=0,tail=0,st=0;
	q[++tail]=st;step[st]=1;
	while(head<tail){
		int now=q[++head];
		int a=now/1000,b=(now/100)%10,c=(now/10)%10,d=now%10;
		for(int i=0;i<10;++i){
			int aa=(a+dx[i][0])%10,bb=(b+dx[i][1])%10,cc=(c+dx[i][2])%10,dd=(d+dx[i][3])%10;
			int x=aa*1000+bb*100+cc*10+dd;
			if(!step[x]){
				step[x]=step[now]+1;
				q[++tail]=x;
			}
			
			aa=(a-dx[i][0]+10)%10,bb=(b-dx[i][1]+10)%10,cc=(c-dx[i][2]+10)%10,dd=(d-dx[i][3]+10)%10;
			x=aa*1000+bb*100+cc*10+dd;
			if(!step[x]){
				step[x]=step[now]+1;
				q[++tail]=x;
			}
		}
	}
	int T=read();
	while(T--){
		string s1,s2;cin>>s1>>s2;
		int now=0;
		for(int i=0;i<4;++i){
			now=now*10+((s1[i]-s2[i]+10)%10);
		}
		cout<<step[now]-1<<endl;
	}
	return 0;
}

L-Perfect Matchings

牛客网

题意:对于\(2*n\)个点的完全图,从图中删去包含这\(2*n\)个点的一棵树,即删去\(2n-1\)条边,求剩余图中完全匹配的个数。所谓一组完全匹配,即选出n条边使得这n条边恰好包含了所有\(2*n\)个顶点。\(n<=2000\)

分析:剩余图中仍有\((n-1)*(2n-1)\)条边,数据量极其庞大。从删去的这棵树上面考虑,将删去的\(2n-1\)条边称为树边,拿完全图的总匹配个数减去包含了树边的匹配个数。如何求包含了树边的匹配方案数量,见下方链接。

题解

补充几个点:一、\(f[i][j][0/1]\)准确表达应该是以i为根的子树内,选了j条树边作为匹配边,此时点i没有/已经被匹配的方案数。二、当选出了i条树边,即匹配了\(2*i\)个点,还剩下\(2*(n-i)\)个点没有被匹配时,这\(2*(n-i)\)个点可以任意两两匹配产生的方案数为\(y=(2m-1)!!\),其中\(m=n-i\),这个式子的意思是\(2m-1\)内所有奇数的累积,因为现在有\(2m\)个点,对于任意第一个点它有\(2m-1\)种选法,去掉这两个点还剩\(2m-2\)个点,那么对于第三个点,它有\(2m-3\)种选法,因此产生的方案数为\(2m-1\)内所有奇数的累乘。

#include<bits/stdc++.h>
#define ll long long
using namespace std;
inline int read() {
	char ch = getchar(); int x = 0, f = 1;
	while (ch < '0' || ch>'9') { if (ch == '-') f = -1; ch = getchar(); }
	while ('0' <= ch && ch <= '9') { x = x * 10 + ch - '0'; ch = getchar(); }
	return x * f;
}
const int N=4e3+5;
const int M=8e3+5;
const int mod=998244353; 
int n,m,sz[N],jc[N];
ll tmp[N][2],f[N][N][2];
int tot,head[N],nxt[M],to[M];
void add(int a,int b){
	nxt[++tot]=head[a];head[a]=tot;to[tot]=b;
}
void dfs(int u,int fa){
	sz[u]=1;f[u][0][0]=1;
	for(int i=head[u];i;i=nxt[i]){
		int v=to[i];
		if(v==fa)continue;
		dfs(v,u);
		memset(tmp,0,sizeof(tmp));
		for(int j=0;j<=sz[u]/2;++j){
			for(int k=0;k<=sz[v]/2;++k){
				tmp[j+k][0]=(tmp[j+k][0]+(1ll*f[u][j][0]*(f[v][k][0]+f[v][k][1])%mod))%mod;
				tmp[j+k][1]=(tmp[j+k][1]+(1ll*f[u][j][1]*(f[v][k][0]+f[v][k][1])%mod))%mod;
				tmp[j+k+1][1]=(tmp[j+k+1][1]+(1ll*f[u][j][0]*f[v][k][0])%mod)%mod;
			}
		}
		sz[u]+=sz[v];
		for(int i=0;i<=sz[u]/2;++i){
			f[u][i][0]=tmp[i][0];
			f[u][i][1]=tmp[i][1];
		}
		
	}
}
void init(){
	jc[0]=1;
	for(int i=1;i<=m;i++){
		if(i%2==0)jc[i]=jc[i-1];
		else jc[i]=(1ll*i*jc[i-1])%mod;
	}
}
int main(){
	n=read();m=2*n;init();
	for(int i=1;i<m;++i){
		int x=read(),y=read();
		add(x,y);add(y,x);
	}
	dfs(1,0);
	ll ans=0;
	for(int i=0;i<=n;++i){
		ll x=(f[1][i][0]+f[1][i][1])%mod;
		ll y;
		if(i<n)y=jc[2*(n-i)-1];
		else y=1;
		if(i%2==0)ans=(ans+(1ll*x*y)%mod)%mod;
		else ans=(ans-(1ll*x*y)%mod+mod)%mod;
	}
	cout<<ans<<endl;
	return 0;
}

posted on 2022-10-30 19:29  PPXppx  阅读(31)  评论(0编辑  收藏  举报