IOI2020集训队作业-8 (CF627F, AGC035D, AGC033F)

A - CF627F Island Puzzle

题意

有一棵包含 n n n个节点的树,以及 n n n种石头(编号 0 0 0 n − 1 n-1 n1)。每个节点上恰好有一个石头,每个点上的石头互不相同。一次操作中可以交换 0 0 0号石头和另一个所在点与它有边相连的石头。

现在给出初始每个石头的位置,以及需要达到的、每个石头的最终位置。

问:1)能否进行若干次操作,使每个石头都达到它应该在的最终位置?如果能,你还要求出最小步数。2)如果不能,则能否加一条边,使得可以进行若干次操作之后使每个石头达到它应该在的最终位置?并求出最小步数。

n ≤ 200000 n\le 200000 n200000

Sol

操作是可逆的,即,如果将 0 0 0交换到某一个位置,然后再按照我们把它换过去的路径把它换回来,则所有石头的位置相较于最初都没有改变。

判断是否有解:首先把 0 0 0从它原来的位置换到最终的位置。此时如果所有点都已经在了它的最终位置上,则不需要加边,答案为 0 0 0移动的次数;否则,接下来要进行的操作相当于是把 0 0 0拿到一个环里,转若干圈,然后再拿回去。环外的所有的石头,以及我们把 0 0 0拿进环的时候与 0 0 0交换的那个出环的石头,所处的位置都不会改变。故而,此时我们必须要求:所有不在自己最终位置上的点加上一个在自己最终位置上的点可以构成一个环,并且那个在自己最终位置上的点到 0 0 0的最终位置的距离是这些点中最近的,并且这些点可以通过顺时针/逆时针转相同的次数到达自己的最终位置。

考虑怎么求最优解。如果环与 0 0 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;
}
void FAIL() { printf("-1"); exit(0); }
const int N=2e5+10;
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 vis[N],dep[N],fa[N];
int ca[N],cb[N];
int S,T;
void dfs(int u,int last) {
	dep[u]=dep[last]+1,fa[u]=last;
	for(int k=head[u];k;k=e[k].next) {
		int v=e[k].to; if(v==last) continue;
		dfs(v,u);
	}
}
bool jud(int u) { return ca[u]==cb[u]; }
vector<int> cir;
int inc[N],id1;
int main() {
	rd(n);
	for(int i=1;i<=n;++i) { rd(ca[i]); if(!ca[i]) S=i; }
	for(int i=1;i<=n;++i) { rd(cb[i]); if(!cb[i]) T=i; }
	for(int i=1,x,y;i<n;++i) rd(x),rd(y),ad(x,y);
	dfs(T,0);
	{
		int u=S;
		while(fa[u]) swap(ca[u],ca[fa[u]]),vis[u]=1,u=fa[u];
		vis[T]=1;
	}
	int s1=0,s2=0,d,m,u,v;
	{
		for(int i=1;i<=n;++i)
			if(!jud(i)&&dep[i]>dep[s1]) s1=i;
		if(!s1) {
			printf("0 %d\n",dep[S]-1);
			return 0;
		}
		for(u=s1;!jud(u);u=fa[u])
			cir.PB(u),inc[u]=1;
		for(int i=1;i<=n;++i)
			if(!jud(i)&&dep[i]>dep[s2]&&!inc[i]) s2=i;
		id1=cir.size();
		if(s2) {
			for(v=s2;!jud(v);v=fa[v]) {
				if(inc[v]) FAIL();
				cir.PB(v),inc[v]=1;
			}
			if(v!=u) FAIL();
			reverse(cir.begin()+id1,cir.end());
		}
		else v=s2=u;
		
		for(int i=1;i<=n;++i) if(!jud(i)&&!inc[i]) FAIL();
		
		m=cir.size();
		for(d=1;d<m;++d) if(ca[cir[d]]==cb[cir[0]]) break;
		for(int i=0;i<m;++i) if(ca[cir[(i+d)%m]]!=cb[cir[i]]) FAIL();
		printf("%d %d ",min(s1,s2),max(s1,s2));
	}
	{
		int tot=0,flg_ons1=0;
		for(int i=S;i;i=fa[i]) vis[i]=1;
		for(int i=0;i<cir.size();++i) tot+=vis[cir[i]];
		for(int i=0;i<id1&&!flg_ons1;++i) if(vis[cir[i]]) flg_ons1=1;
		ll ans=dep[S]-1;
		if(tot) {
			if(!flg_ons1) d=m-d;
			ans+=min(d*(ll)(m+1),(m-d)*(ll)(m+1)-2*tot);
		}
		else {
			ans+=min(d,m-d)*(ll)(m+1);
			while(!vis[u]) ans+=2,u=fa[u];
		}
		printf("%lld",ans);
	}
	return 0;
}

B - AGC035D Add and Remove

题意

n n n张卡片排成一排,第 i i i张卡片上的数字为 A i A_i Ai。将进行若干次下面的操作直到只剩下两张卡片:选择一张牌 x x x,要求 x x x不能是最左边的牌也不能是最右边的牌,然后把 x x x这张牌拿走,把 x − 1 , x + 1 x-1,x+1 x1,x+1这两张牌上的数字分别变成它们原来的数字加上 A x A_x Ax。问最后剩下的两张牌上的数字的和最小是多少。 2 ≤ n ≤ 18 , 0 ≤ A i ≤ 1 0 9 2\le n\le 18,0\le A_i\le 10^9 2n18,0Ai109

Sol

将操作倒序,就变成了:对于每一张牌,记录下这张牌上的数字最终会以什么系数计入答案,插入一张牌的时候,这张牌的贡献就是它上面的数值乘以它左右的牌的系数的和,它的系数就是它左右的牌的系数的和。

f [ l ] [ r ] [ x l ] [ x r ] f[l][r][x_l][x_r] f[l][r][xl][xr]表示只考虑 [ l , r ] [l,r] [l,r]这个区间, l , r l,r l,r是这个区间内最早插入的元素,它们的系数分别是 x l , x r x_l,x_r xl,xr,这个区间内的元素的答案最小是多少(不包含 l , r l,r l,r对答案的贡献)。则有
f [ l ] [ r ] [ x l ] [ x r ] = min ⁡ k { f [ l ] [ k ] [ x l ] [ x l + x r ] + f [ k ] [ r ] [ x l + x r ] [ x r ] + ( x l + x r ) A k } f[l][r][x_l][x_r] = \min_k \{ f[l][k][x_l][x_l+x_r]+f[k][r][x_l+x_r][x_r]+(x_l+x_r) A_k\} f[l][r][xl][xr]=kmin{f[l][k][xl][xl+xr]+f[k][r][xl+xr][xr]+(xl+xr)Ak}

可以证明不同的 ( x l , x r ) (x_l,x_r) (xl,xr)的数量是 O ( 2 n ) O(2^n) O(2n),这是因为往下递归的时候,新的 ( x l , x r ) (x_l,x_r) (xl,xr) k k k的选择无关,而只与原来的 ( x l , x r ) (x_l,x_r) (xl,xr)有关,只会产生两种新的 ( x l , x r ) (x_l,x_r) (xl,xr) ( x l , x l + x r ) (x_l,x_l+x_r) (xl,xl+xr) ( x l + x r , x r ) (x_l+x_r,x_r) (xl+xr,xr)。而递归层数是 n n n,所以 ( x l , x r ) (x_l,x_r) (xl,xr)只有 O ( 2 n ) O(2^n) O(2n)种。

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <map>
#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;
}
/*
f[l,r,xl,xr]:
	in range [l,r], l and r were the first values vomitted
	l is counted xl times
	r is counted xr times
	f is the minimum possible sum of the contribution of values in range (l,r) 
*/
const int N=20;
map<pii,ll> mp[N][N];
int n;
int a[N];
ll dfs(int l,int r,int xl,int xr) {
	if(r==l+1) return 0;
	if(mp[l][r].count(MP(xl,xr))) return mp[l][r][MP(xl,xr)];
	ll ans=1e18;
	for(int k=l+1;k<r;++k)
		ans=min(ans,dfs(l,k,xl,xl+xr)+dfs(k,r,xl+xr,xr)+(xl+xr)*(ll)a[k]);
	return mp[l][r][MP(xl,xr)]=ans;
}
int main() {
	rd(n);
	for(int i=1;i<=n;++i) rd(a[i]);
	printf("%lld\n",dfs(1,n,1,1)+a[1]+a[n]);
	return 0;
}

C - AGC033F Add Edges

题意

有一张包含 n n n个点、 m m m条无向边的图 G G G和一个包含 n n n个点的树 T T T,节点编号都是 1 1 1 n n n

如果对于某三个点 a , b , c a,b,c a,b,c,满足 G G G中存在边 ( a , b ) , ( a , c ) (a,b),(a,c) (a,b),(a,c),并且在 T T T a , b , c a,b,c a,b,c同一条链上(顺序没有要求),则将边 ( b , c ) (b,c) (b,c)加入 G G G

求:重复以上过程直到无法再加边时, G G G中的边数。

n , m ≤ 2000 n,m\le 2000 n,m2000

Sol

可以考虑将图 G G G进行压缩:如果存在三个点 a , b , c a,b,c a,b,c ( a , b ) , ( a , c ) (a,b),(a,c) (a,b),(a,c) G G G中,且 T T T中这三个点在一条链上,顺序是 a , b , c a,b,c a,b,c,那么就删掉 G G G中的 ( a , c ) (a,c) (a,c),加入 ( b , c ) (b,c) (b,c)。压缩到不能再压缩时,对于任意两个点 x , y x,y x,y,这两个点之间在最终的图中有边,当且仅当存在一个序列 x = a 1 , a 2 , a 3 , ⋯ a m = y x = a_1, a_2, a_3, \cdots a_m = y x=a1,a2,a3,am=y,满足对于所有的 i ∈ [ 1 , m ) i \in [1,m) i[1,m),压缩后的 G G G中有 ( a i , a i + 1 ) (a_i,a_{i+1}) (ai,ai+1),并且树上 a 1 , a 2 , a 3 ⋯ a m a_1,a_2,a_3\cdots a_m a1,a2,a3am按照顺序依次出现在了一条链上(不要求树上 a i a_i ai a i + 1 a_{i+1} ai+1之间有边)。

这个结论可以这样理解:

  • 从把所有边都加入之后的结果来看,压缩后的图和压缩前的图显然等价。
  • 满足这个条件的边显然是会存在于最终的图中的。
  • 如果可以证明:“对于任意三个点 a , b , c a,b,c a,b,c,如果 ( a , b ) , ( a , c ) (a,b),(a,c) (a,b),(a,c)都满足上面的边出现的条件,并且 ( a , b , c ) (a,b,c) (a,b,c)在树上以某种顺序排成一条链,则可以推出 ( a , c ) (a,c) (a,c)满足出现的条件。”那么就可以说明结论对边存在性的定义和原题面是等价的。讨论 a , b , c a,b,c a,b,c在链上可能不同的位置关系,联系此时的图已经不能再压缩的条件,即可得证。

剩下的问题就是怎么求出压缩后的图。

t o p ( a , b ) top(a,b) top(a,b)表示 T T T b b b a a a的路径上,遇到的第一个在(把边全部加上后的) G G G中与 a a a有边相连的点。当我们加入一条边 ( a , b ) (a,b) (a,b)的时候,如果 t o p ( a , b ) top(a,b) top(a,b)存在,那么加入 ( b , t o p ( a , b ) ) (b,top(a,b)) (b,top(a,b))就好;否则我们将加入边 ( a , b ) (a,b) (a,b)。我们需要更新以 a a a为根时 b b b的子树内的点:如果遇到点已经与 a a a直接有边相连,则将那条边删掉,在这个点和 b b b之间连一条边;否则就直接将这个点的 t o p top top更新为 b b b

时间复杂度 O ( n 2 + n m ) O(n^2 +nm) O(n2+nm)

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <queue>
#define PB push_back
#define MP make_pair
#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;
vector<int> e[N];
void ad(int x,int y) { e[x].PB(y),e[y].PB(x); }
int top[N][N],G[N][N],n,m;
int fa[N][N];
void dfs1(int u,int last,int p) {
	fa[p][u]=last;
	for(int i=0;i<e[u].size();++i)
		if(e[u][i]!=fa[p][u]) dfs1(e[u][i],u,p);
}
queue<int> que;
void addedge(int a,int b) {
	if(top[a][b]==b) return;
	if(top[a][b]) return addedge(top[a][b],b);
	if(top[b][a]) return addedge(top[b][a],a);
	G[a][b]=G[b][a]=1;
	top[a][b]=b,top[b][a]=a;
	vector<pair<int,int> > tmp;
	{
		que.push(b);
		while(!que.empty()) {
			int u=que.front(); que.pop();
			for(int i=0;i<e[u].size();++i) {
				int v=e[u][i]; if(v==fa[a][u]) continue;
				if(top[a][v]) {
					G[top[a][v]][a]=G[a][top[a][v]]=0;
					tmp.PB(MP(top[a][v],b));
				}
				else top[a][v]=b,que.push(v);
			}
		}
	}
	swap(a,b);
	{
		que.push(b);
		while(!que.empty()) {
			int u=que.front(); que.pop();
			for(int i=0;i<e[u].size();++i) {
				int v=e[u][i]; if(v==fa[a][u]) continue;
				if(top[a][v]) {
					G[top[a][v]][a]=G[a][top[a][v]]=0;
					tmp.PB(MP(top[a][v],b));
				}
				else top[a][v]=b,que.push(v);
			}
		}
	}
	for(int i=0;i<tmp.size();++i) addedge(tmp[i].first,tmp[i].second);
}
int ans;
void dfs2(int u,int lst,int p) {
	if(G[lst][u]) lst=u,ans++;
	for(int i=0;i<e[u].size();++i) {
		int v=e[u][i]; if(v==fa[p][u]) continue;
		dfs2(v,lst,p);
	}
}
int main() {
	rd(n),rd(m);
	for(int i=1,x,y;i<n;++i) rd(x),rd(y),ad(x,y);
	for(int i=1;i<=n;++i) dfs1(i,0,i);
	for(int i=1,x,y;i<=m;++i) rd(x),rd(y),addedge(x,y);
	for(int i=1;i<=n;++i) dfs2(i,i,i);
	printf("%d\n",ans/2);
	return 0;
}
u][i]; if(v==fa[p][u]) continue;
		dfs2(v,lst,p);
	}
}
int main() {
	rd(n),rd(m);
	for(int i=1,x,y;i<n;++i) rd(x),rd(y),ad(x,y);
	for(int i=1;i<=n;++i) dfs1(i,0,i);
	for(int i=1,x,y;i<=m;++i) rd(x),rd(y),addedge(x,y);
	for(int i=1;i<=n;++i) dfs2(i,i,i);
	printf("%d\n",ans/2);
	return 0;
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值