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

A - CF566E Restoring Map

Sol

对每一对 ( u , v ) (u,v) (u,v),求出包含它的 s e t set set的数量是等于 0 , 1 , 2 0,1,2 0,1,2还是大于等于 3 3 3

如果为 0 0 0则这两个点之间的距离大于 4 4 4,等于 1 1 1则距离等于 4 4 4,等于 2 2 2则距离等于 3 3 3,大于 2 2 2则距离小于等于 2 2 2

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

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

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

题意

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

Sol

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

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

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

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

首先可以把 ⌊ t i 2 L ⌋ \lfloor {t_i\over 2L}\rfloor 2Lti加入答案,然后把 t i t_i ti 2 L 2L 2L

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

对于第 i i i个购物中心,记 l i l_i li表示从右边过来到 i i i,列车下次经过 i i i的时候能不能直接上车; r i r_i ri同理。

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

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

如果有一个购物中心的 t i = 0 t_i=0 ti=0,或者是 l i = r i = 0 l_i=r_i=0 li=ri=0,那么直接把它的贡献加入答案,不用考虑用它匹配。因为 t i = 0 t_i=0 ti=0的时候只需要保证经过了它就可以了,另一种情况由于必须在 i i i等列车跑完一圈所以与 t i = 0 t_i=0 ti=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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值