IOI2020集训队作业-13 (CF528C, AGC038F, AGC029E)

A - CF528C Data Center DramaSol加完边之后存在合法的定向方案的充分必要条件是:所有点的度数都是偶数并且边的总数是偶数。必要性显然。而如果条件满足,则原图一定存在欧拉回路且欧拉回路的长度是偶数,把欧拉回路求出来然后令第1,3,5⋯1,3,5\cdots1,3,5⋯条边的方向与遍历它的方向相同,第2,4,6⋯2,4,6\cdots2,4,6⋯条边方向与遍历它的方向相...
摘要由CSDN通过智能技术生成

A - CF528C Data Center Drama

Sol

加完边之后存在合法的定向方案的充分必要条件是:所有点的度数都是偶数并且边的总数是偶数。

必要性显然。而如果条件满足,则原图一定存在欧拉回路且欧拉回路的长度是偶数,把欧拉回路求出来然后令第 1 , 3 , 5 ⋯ 1,3,5\cdots 1,3,5条边的方向与遍历它的方向相同,第 2 , 4 , 6 ⋯ 2,4,6\cdots 2,4,6条边方向与遍历它的方向相反,就可以得到满足题意的定向方案。

所以加边的方案一定是:首先把度数为奇数的点两两配对连边,最后如果边数是偶数再连一条自环。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define PB push_back
#define PII pair<int,int>
#define MP make_pair
#define fir first
#define sec second
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}

const int N=1e5+10,M=3e5+10;
vector<PII> G[N];
vector<int> E,P;
int n,m,du[N];
int cur[N],vis[M];
void dfs(int u) {
	for(int &i=cur[u];i<G[u].size();++i) if(!vis[G[u][i].sec]) {
		int v=G[u][i].fir;
		vis[G[u][i].sec]=1;
		dfs(v);
		P.PB(v); 
	}
}
int main() {
	rd(n),rd(m);
	for(int i=1,x,y;i<=m;++i) {
		rd(x),rd(y);
		G[x].PB(MP(y,i));
		G[y].PB(MP(x,i));
		du[x]^=1,du[y]^=1;
	}
	for(int i=1,lst=0;i<=n;++i) if(du[i]) {
		if(lst) {
			++m;
			G[lst].PB(MP(i,m));
			G[i].PB(MP(lst,m));
			lst=0;
		}
		else lst=i;
	}
	if(m&1) {
		m++;
		G[1].PB(MP(1,m));
	}
	printf("%d\n",m);
	dfs(1);
	for(int i=0;i+1<P.size();++i) {
		if(i&1) printf("%d %d\n",P[i],P[i+1]);
		else printf("%d %d\n",P[i+1],P[i]);
	}
	printf("%d %d\n",P.back(),P[0]);
	return 0;
}	

B - AGC038F Two Permutations

Sol

P P P看成每个点出度为 1 1 1的有向图,则题目条件等价于可以将一个环中的点 { x 1 , x 2 ⋯ x m } \{x_1,x_2\cdots x_m\} {x1,x2xm} A i A_i Ai确定为 { x 1 , x 2 ⋯ x m } \{ x_1,x_2\cdots x_m\} {x1,x2xm}或者 { P x 1 , P x 2 , ⋯ P x m } \{P_{x_1},P_{x_2},\cdots P_{x_m}\} {Px1,Px2,Pxm} Q , B Q,B Q,B同理,用 a i , b i = 0 / 1 a_i,b_i=0/1 ai,bi=0/1表示 A i A_i Ai被确定为 i / P i i/P_i i/Pi B i B_i Bi被确定为 i / Q i i/Q_i i/Qi

  1. 如果 P i = i , Q i = i P_i=i,Q_i=i Pi=i,Qi=i,则第 i i i位置无论如何都不会产生贡献。
  2. 如果 P i = i , Q i ≠ i P_i=i,Q_i\not =i Pi=i,Qi=i,则只要 b i = 1 b_i=1 bi=1就一定会产生贡献。对于 P i ≠ i , Q i = i P_i\not =i,Q_i = i Pi=i,Qi=i同理。
  3. 如果 P i ≠ Q i , P i ≠ i , Q i ≠ i P_i\not=Q_i,P_i\not=i,Q_i\not=i Pi=Qi,Pi=i,Qi=i,那么只有当 a i = b i = 0 a_i=b_i=0 ai=bi=0的时候不会产生贡献。
  4. 如果 P i = Q i , P i ≠ i P_i=Q_i,P_i\not= i Pi=Qi,Pi=i,那么只有 a i ≠ b i a_i\not =b_i ai=bi的时候会产生贡献。

这可以转化成最小割模型。建一排点分别表示 P P P Q Q Q的所有环。 P P P中的环与 S S S之间的边表示 a i = 0 a_i=0 ai=0,与 T T T之间的边表示 a i = 1 a_i=1 ai=1 Q Q Q中的环与 S S S之间的边表示 b i = 1 b_i=1 bi=1,与 T T T之间的边表示 b i = 0 b_i=0 bi=0。对于3.,连从 P P P中的环到 Q Q Q中的环的边;对于4.,在那两个环之间连双向边。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <vector>
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=2e5+10;
int S,T,ncnt;
namespace Flow {
	int head[N],cur[N],dep[N],ecnt;
	struct ed { int to,next,f; };
	vector<ed> e;
	void init() { memset(head,-1,sizeof(head)); e.clear(); }
	void ad(int x,int y,int f) {
		e.PB((ed){y,head[x],f}); head[x]=e.size()-1;
		e.PB((ed){x,head[y],0}); head[y]=e.size()-1;
	}
	queue<int> que;
	bool bfs() {
		for(int i=1;i<=ncnt;++i) dep[i]=-1,cur[i]=head[i];
		while(!que.empty()) que.pop();
		que.push(S),dep[S]=0;
		while(!que.empty()) {
			int u=que.front(); que.pop();
			if(u==T) return 1;
			for(int k=head[u];~k;k=e[k].next) if(e[k].f) {
				int v=e[k].to;
				if(dep[v]==-1) {
					dep[v]=dep[u]+1;
					que.push(v);
				}
			}
		}
		return 0;
	}
	int dfs(int u,int f) {
		if(u==T||!f) return f; int tmp,ret=0;
		for(int &k=cur[u];~k;k=e[k].next) if(e[k].f) {
			int v=e[k].to;
			if(dep[v]==dep[u]+1&&(tmp=dfs(v,min(e[k].f,f)))) {
				e[k].f-=tmp,e[k^1].f+=tmp;
				f-=tmp,ret+=tmp;
				if(!f) break;
			}
		}
		return ret;
	}
	int work() { int ans=0; while(bfs()) ans+=dfs(S,1e9); return ans; }
}
int P[N],Q[N],n;
int bP[N],bQ[N],cntP,cntQ;
int id[N][2];
int a[N],b[N],ans;
void dfsP(int u) { if(bP[u]) return; bP[u]=cntP,dfsP(P[u]); }
void dfsQ(int u) { if(bQ[u]) return; bQ[u]=cntQ,dfsQ(Q[u]); }
int main() {
	Flow::init();
	rd(n);
	for(int i=0;i<n;++i) rd(P[i]);
	for(int i=0;i<n;++i) rd(Q[i]);
	
	for(int i=0;i<n;++i) if(!bP[i]) cntP++,dfsP(i);
	for(int i=0;i<n;++i) if(!bQ[i]) cntQ++,dfsQ(i);
	S=++ncnt,T=++ncnt;
	for(int i=1;i<=cntP;++i) id[i][0]=++ncnt;
	for(int i=1;i<=cntQ;++i) id[i][1]=++ncnt;
	
	for(int i=0;i<n;++i) {
		if(P[i]==i&&Q[i]==i) continue;
		if(P[i]==i) b[bQ[i]]++;
		else if(Q[i]==i) a[bP[i]]++;
		else if(Q[i]!=P[i]) ans++,Flow::ad(id[bP[i]][0],id[bQ[i]][1],1);
		else ans++,Flow::ad(id[bP[i]][0],id[bQ[i]][1],1),Flow::ad(id[bQ[i]][1],id[bP[i]][0],1);
	}
	for(int i=1;i<=cntP;++i) {
		ans+=a[i];
		Flow::ad(S,id[i][0],0);
		Flow::ad(id[i][0],T,a[i]);
	}
	for(int i=1;i<=cntQ;++i) {
		ans+=b[i];
		Flow::ad(S,id[i][1],b[i]);
		Flow::ad(id[i][1],T,0);
	}
	ans-=Flow::work();
	printf("%d",ans);
	return 0;
}

C - AGC029E Wandering TKHS

Sol

考虑 u u u 1 1 1的路径上(不包括 u u u)编号最大的那个点 x x x,显然从 u u u走到 x x x之后,需要经过的点就不再需要考虑 x x x u u u走的这棵子树内的点了,这可以直接由 x x x的答案减去 u u u所在的子树对 x x x的答案的贡献得到。

而从 u u u走到 x x x需要的代价是,当我们只认为 i d < x id< x id<x的点和 u u u点存在的时候, u u u所在的连通块的大小-1。并查集维护即可。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#define PB push_back
#define ll long long
using namespace std;
template <class T>
inline void rd(T &x) {
	x=0; char c=getchar(); int f=1;
	while(!isdigit(c)) { if(c=='-') f=-1; c=getchar(); }
	while(isdigit(c)) x=x*10-'0'+c,c=getchar(); x*=f;
}
const int N=2e5+10;
namespace DSU {
	int fa[N],sz[N];
	void init(int n) { for(int i=1;i<=n;++i) fa[i]=i,sz[i]=1; }
	int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]); }
	int siz(int x) { return sz[find(x)]; }
	void un(int x,int y) {
		int fx=find(x),fy=find(y);
		if(fx==fy) return;
		sz[fy]+=sz[fx],fa[fx]=fy;
	}
}

vector<int> G[N],Q[N];
int fa[N],son[N],sf[N],n;
int mx[N],ans[N],sub[N];

void dfs1(int u,int last) {
	fa[u]=last,mx[u]=max(mx[last],last);
	sf[u]=son[mx[u]];
	for(int i=0;i<G[u].size();++i) {
		int v=G[u][i]; if(v==last) continue;
		son[u]=v,dfs1(v,u);
	}
}

void dfs2(int u,int last) {
	if(last) ans[u]+=ans[mx[u]]-sub[son[mx[u]]]+1;
	for(int i=0;i<G[u].size();++i) {
		int v=G[u][i]; if(v==last) continue;
		son[u]=v,dfs2(v,u);
	}
}
		
int main() {
	rd(n); DSU::init(n);
	for(int i=1,x,y;i<n;++i) rd(x),rd(y),G[x].PB(y),G[y].PB(x);
	dfs1(1,0);
	for(int i=2;i<=n;++i) Q[mx[i]].PB(i);
	for(int i=1;i<=n;++i) {
		for(int j=0;j<Q[i].size();++j) {
			int u=Q[i][j];
			if(u<i) ans[u]=DSU::siz(u)-1;
			else {
				if(sf[u]!=u) ans[u]+=DSU::siz(sf[u]);
				for(int k=0;k<G[u].size();++k)
					if(G[u][k]!=fa[u]&&G[u][k]<i) {
						sub[G[u][k]]=DSU::siz(G[u][k]);
						ans[u]+=sub[G[u][k]];
					}
			}
		}
		for(int j=0;j<G[i].size();++j)
			if(G[i][j]<i) DSU::un(i,G[i][j]);
	}
	dfs2(1,0);
	for(int i=2;i<=n;++i) printf("%d ",ans[i]);
	return 0;
}
++k)
					if(G[u][k]!=fa[u]&&G[u][k]<i) {
						sub[G[u][k]]=DSU::siz(G[u][k]);
						ans[u]+=sub[G[u][k]];
					}
			}
		}
		for(int j=0;j<G[i].size();++j)
			if(G[i][j]<i) DSU::un(i,G[i][j]);
	}
	dfs2(1,0);
	for(int i=2;i<=n;++i) printf("%d ",ans[i]);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值