IOI2020集训队作业-11 (CF566E, AGC034E, AGC022D)

本文深入解析了算法竞赛中的策略,包括图论问题的解决思路、树形结构的优化操作及动态规划的应用技巧。通过具体案例,阐述了如何利用数据结构和算法解决复杂问题,为参赛者提供了宝贵的经验。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

A - CF566E Restoring Map

Sol

对每一对(u,v)(u,v)(u,v),求出包含它的setsetset的数量是等于0,1,20,1,20,1,2还是大于等于333

如果为000则这两个点之间的距离大于444,等于111则距离等于444,等于222则距离等于333,大于222则距离小于等于222

观察发现,如果图的直径大于等于555,我们就可以通过已经知道的距离为333和距离为444的点对,确定下两个点之间的距离的奇偶性,然后结合已知的距离小于等于222的点对就可以知道哪些点对之间的距离是111

然后再讨论直径小于555的情况即可。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <assert.h>
#define PII pair<int,int>
#define MP make_pair
#define fir first
#define sec second
#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=1010;
int nxt[N][N],num[N][N],n;
int find(int p,int x) { return nxt[p][x]==x?x:nxt[p][x]=find(p,nxt[p][x]); }
int b[N],vis[N];
vector<PII> G[N];
int col[N];
void dfs(int u,int c) {
	col[u]=c;
	for(int i=0;i<G[u].size();++i) {
		int v=G[u][i].fir;
		if(!~col[v]) dfs(v,c^G[u][i].sec);
	}
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i) {
		for(int j=1;j<=n+1;++j)
			nxt[i][j]=j;
	}
	
	for(int i=1;i<=n;++i) {
		int m; rd(m);
		for(int j=1;j<=m;++j) rd(b[j]),vis[b[j]]=1;
		for(int j=1;j<=m;++j) {
			int u=b[j];
//			for(int k=1;k<=n;++k) if(vis[k]&&num[u][k]<3) num[u][k]++;
			for(int k=find(u,1);k<=n;k=find(u,k+1)) if(vis[k]) {
				num[u][k]++;
				if(num[u][k]==3) nxt[u][k]=k+1;
			}
		}
		for(int j=1;j<=m;++j) vis[b[j]]=0;
	}
	
	if(n==2) {
		printf("1 2\n");
		return 0;
	}
	
	int f5=0;
	for(int i=1;i<=n&&!f5;++i) for(int j=1;j<=n&&!f5;++j)
		if(num[i][j]==0) f5=1;
	
	if(!f5) {
		int f4=0;
		for(int i=1;i<=n&&!f4;++i) for(int j=1;j<=n&&!f4;++j)
			if(num[i][j]==1) f4=1;
		if(!f4) {
			int f3=0;
			for(int i=1;i<=n&&!f3;++i) for(int j=1;j<=n&&!f3;++j)
				if(num[i][j]==2) f3=1;
			if(!f3) {
				for(int i=2;i<=n;++i) printf("1 %d\n",i);
				return 0;
			}
			int u=0,v=0;
			for(int i=1;i<=n;++i) {
				int flg=0;
				for(int j=1;j<=n;++j) if(num[i][j]==2) { flg=1; break; }
				if(!flg) {
					if(!u) u=i;
					else { v=i; break; }
				}
			}
			int p=1; for(;p==u||p==v;++p);
			printf("%d %d\n",u,v);
			printf("%d %d\n",p,u);
			for(int i=1;i<=n;++i) if(i!=u&&i!=v&&i!=p) {
				if(num[i][p]==2) printf("%d %d\n",v,i);
				else printf("%d %d\n",u,i);
			}
			return 0;
		}
		int rt=1;
		for(;;++rt) {
			int flg=1;
			for(int i=1;i<=n;++i)
				if(i!=rt&&num[i][rt]<3) { flg=0; break; }
			if(flg) break;
		}
		vector<int> a,b;
		for(int i=1;i<=n;++i) if(i!=rt) {
			int mi=3;
			for(int j=1;j<=n;++j) mi=min(mi,num[i][j]);
			if(mi==2) a.PB(i);
			else b.PB(i);
		}
		for(int i=0;i<a.size();++i) printf("%d %d\n",a[i],rt);
		for(int i=0;i<b.size();++i)
			for(int j=0;j<a.size();++j)
				if(num[b[i]][a[j]]==3) { printf("%d %d\n",b[i],a[j]); break; }
		return 0;
	}
	
	for(int i=1;i<=n;++i)
		for(int j=1;j<=n;++j) if(num[i][j]&&num[i][j]<3) {
			if(num[i][j]==1) G[i].PB(MP(j,0)),G[j].PB(MP(i,0));
			else G[i].PB(MP(j,1)),G[j].PB(MP(i,1));
		}
	memset(col,-1,sizeof(col));
	int tot=0;
	for(int i=1;i<=n;++i) if(!~col[i]) dfs(i,tot),tot+=2;
	int edgetot=0;
	for(int i=1;i<=n;++i) for(int j=i+1;j<=n;++j)
		if(num[i][j]==3&&(col[i]>>1)==(col[j]>>1)&&col[i]!=col[j]) printf("%d %d\n",i,j),edgetot++;
	return 0;
}

B - AGC034E Complete Compress

题意

有一棵包含nnn个节点的树,以及一个长度为nnn010101串,第iii个字符表示第iii个节点上的碎片的数量。一次操作你可以选择一个之间距离(路径上的边数)至少是222的两个点,把它们向彼此移动一个节点。问最少需要多少次操作使得所有的碎片集中在同一个点。如果不可能则输出−1-11n≤2000n\le 2000n2000

Sol

首先枚举一个点作为最终停留的点,把这个点作为根。

那么只会有两种类型的操作:1)将这个根的不同子树内的两个碎片移动。2)将这个根的某个子树内、lcalcalca不为它们其中之一的两个碎片移动。实际上对于任意一个节点,它的子树内的操作也一定满足。

考虑对一个点求出这个点子树内的碎片进行操作过后,到这个点的父亲的距离之和的最小值;求出对这个子树内的碎片到这个点的父亲的距离之和;分别记为minumin_uminumaxumax_umaxu。设uuu节点的所有儿子中,minvmin_vminv最大的儿子为ppp。那么有$min_u = \max { 0 , \min_p - (\max_u - \max_p)} + size_u ,其中,其中size_u表示表示u子树内的碎片的数量。最后,判断根节点的子树内的碎片的数量。最后,判断根节点的\min$是否为000,如果是,说明存在合法的操作方式,操作次数为max⁡rt2\max_{rt}\over 22maxrt

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#define PII pair<int,int>
#define MP make_pair
#define fir first
#define sec second
#define PB push_back
#define db long double
#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=2010;
int head[N],ecnt,n;
struct ed { int to,next; }e[N<<1];
void ad(int x,int y) {
	e[++ecnt]=(ed){y,head[x]}; head[x]=ecnt;
	e[++ecnt]=(ed){x,head[y]}; head[y]=ecnt;
}
int curd;
char str[N];
void dfs(int u,int last,int dis) {
	if(str[u]=='1') curd+=dis;
	for(int k=head[u];k;k=e[k].next) {
		int v=e[k].to; if(v==last) continue;
		dfs(v,u,dis+1);
	}
}
int ans=1e9;
int tot;
void dfs1(int u,int last,int d) {
	if(str[u]=='1') tot+=d;
	for(int k=head[u];k;k=e[k].next) {
		int v=e[k].to; if(v==last) continue;
		dfs1(v,u,d+1);
	}
}
int mi[N],mx[N],sz[N];
void dfs2(int u,int last) {
	int tot=0,p=0;
	sz[u]=str[u]-'0';
	for(int k=head[u];k;k=e[k].next) {
		int v=e[k].to; if(v==last) continue;
		dfs2(v,u);
		if(mi[v]>mi[p]) p=v;
		tot+=mx[v];
		sz[u]+=sz[v];
	}
	mx[u]=tot;
	tot-=mx[p];
	mi[u]=max(0,mi[p]-tot);
	if(last) mi[u]+=sz[u],mx[u]+=sz[u];
}
void sol(int u) {
	tot=0,dfs1(u,0,0);
	if(tot&1) return;
	dfs2(u,0);
	if(mi[u]>0) return;
	ans=min(ans,tot/2);
}
int main() {
	rd(n); scanf("%s",str+1);
	for(int i=1,x,y;i<n;++i) rd(x),rd(y),ad(x,y);
	for(int i=1;i<=n;++i) sol(i);
	if(ans==1e9) printf("-1");
	else printf("%d",ans);
	return 0;
}

C - AGC022D Shopping

Sol

O(n2)O(n^2)O(n2)的暴力是考虑每个购物中心是右进右出/左进右出/右进左出/左进左出,进行动态规划。但是这与正解没有什么关系。

首先可以把⌊ti2L⌋\lfloor {t_i\over 2L}\rfloor2Lti加入答案,然后把tit_iti2L2L2L

我们要求的实际上是列车至少跑多少个来回。

对于第iii个购物中心,记lil_ili表示从右边过来到iii,列车下次经过iii的时候能不能直接上车;rir_iri同理。

对于最右边的那个购物中心,我们一定会从左边到它,然后从它到左边。所以如果rn=0r_n=0rn=0ans++ans++ans++

对于剩下的购物中心,如果有i<j,li=rj=1i< j,l_i=r_j=1i<j,li=rj=1,则可以把它们匹配起来,然后少跑一个来回。由于所有li=1,ri=0l_i=1,r_i=0li=1,ri=0的点一定在所有li=0,ri=1l_i=0,r_i=1li=0,ri=1的点的右边,可以单独考虑两边的匹配。

如果有一个购物中心的ti=0t_i=0ti=0,或者是li=ri=0l_i=r_i=0li=ri=0,那么直接把它的贡献加入答案,不用考虑用它匹配。因为ti=0t_i=0ti=0的时候只需要保证经过了它就可以了,另一种情况由于必须在iii等列车跑完一圈所以与ti=0t_i=0ti=0是等价的。

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=3e5+10;
vector<int> s;
ll xi[N],ti[N];
int li[N],ri[N],n;
int vis[N];
int stk[N],top;
ll ans,L;
int main() {
	rd(n),rd(L);
	ans=n;
	for(int i=1;i<=n;++i) rd(xi[i]);
	for(int i=1;i<=n;++i) rd(ti[i]);
	for(int i=1;i<=n;++i) {
		ans+=ti[i]/(2*L);
		ti[i]%=2*L;
		if(ti[i]==0) ans--,vis[i]=1;
		else {
			li[i]=2*xi[i]>=ti[i];
			ri[i]=2*(L-xi[i])>=ti[i];
			if(!li[i]&&!ri[i]) ti[i]=0,vis[i]=1;
		}
//		printf("%d:(%d,%d) %d\n",i,li[i],ri[i],vis[i]);
	}
	int px=1;
	while(px<n&&!(li[px]==1&&ri[px]==0)) px++;
	int tot=0,top=0;
	for(int i=1;i<px;++i) if(!vis[i]) {
		if(!li[i]) {
			if(top) top--,ans--;
		}
		else top++;
	}
	tot+=top,top=0;
	
	for(int i=n-1;i>=px;--i) if(!vis[i]) {
		if(!ri[i]) {
			if(top) top--,ans--;
		}
		else top++;
	}
	tot+=top;
	ans-=tot>>1;
	if(!ri[n]) ans++;
	printf("%lld",ans*2*L);
	return 0;
}
0;
	for(int i=1;i<px;++i) if(!vis[i]) {
		if(!li[i]) {
			if(top) top--,ans--;
		}
		else top++;
	}
	tot+=top,top=0;
	
	for(int i=n-1;i>=px;--i) if(!vis[i]) {
		if(!ri[i]) {
			if(top) top--,ans--;
		}
		else top++;
	}
	tot+=top;
	ans-=tot>>1;
	if(!ri[n]) ans++;
	printf("%lld",ans*2*L);
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值