Codeforces Round 1280 简要题解

23 篇文章 1 订阅
20 篇文章 0 订阅

A. Cut and Paste

B. Beingawesomeism

C. Jeremy Bearimy

D. Miss Punyverse

令点 i i i的权值 c i = w i − b i c_i=w_i-b_i ci=wibi,则一个连通块有贡献当且仅当 ∑ c i > 0 \sum c_i>0 ci>0
考虑一个显然的DP,设 F [ i ] [ j ] [ k ] F[i][j][k] F[i][j][k]表示考虑点 i i i为根的子树,除根结点所在的连通块恰有 j j j个,其中有 k k k个是有贡献的情况下,根节点所在的连通块(可能会跟父亲合并)的最大权值和,转移直接做背包即可。
这样复杂度太高了,但可以发现对于两个状态 F [ i ] [ j ] [ k 1 ] F[i][j][k_1] F[i][j][k1] F [ i ] [ j ] [ k 2 ] F[i][j][k_2] F[i][j][k2],若 k 1 < k 2 k_1<k_2 k1<k2 F [ i ] [ j ] [ k 1 ] F[i][j][k_1] F[i][j][k1]转移到的最终的答案不会超过 F [ i ] [ j ] [ k 2 ] F[i][j][k_2] F[i][j][k2]转移到的,因此对于 k k k这一维只用保留最大的即可。具体的,令 G [ i ] [ j ] G[i][j] G[i][j]考虑点 i i i为根的子树,除根结点所在的连通块恰有 j j j个的情况下,最大的一个(其中有贡献的连通块数目,根节点所在的连通块权值和)的pair。
简单分析可以知道单组数据时间复杂度为 O ( n m ) \mathcal O(nm) O(nm)

#include <bits/stdc++.h>
#define MOD 1000000007
#define FR first
#define SE second
#define inf 0x3f3f3f3f3f3f3f3fLL

using namespace std;

typedef long long ll;
typedef pair<int,ll> pr;

inline pr merge(pr x,pr y) {
  return pr(x.FR+y.FR,x.SE+y.SE);
}

vector <int> e[3005];
int num[3005];

pr f[3005][3005];
int k,ans;

int dfs(int x,int fa) {
  static pr g[3005];
  int s=0;
  f[x][0]=pr(0,0);
  for(int i=0;i<e[x].size();i++)
    if (e[x][i]!=fa) {
    	int u=e[x][i];
    	int t=dfs(u,x);
    	for(int j=0;j<=s+t;j++) g[j]=pr(0,-inf);
    	for(int j=0;j<=s;j++)
    	  for(int k=0;k<=t;k++) g[j+k]=max(g[j+k],merge(f[x][j],f[u][k]));
    	for(int j=0;j<=s+t;j++) f[x][j]=g[j];
    	s+=t;
	}
  if (x==1) ans=f[1][k-1].FR+(f[1][k-1].SE+num[1]>0);
  f[x][s+1]=pr(f[x][s].FR+(f[x][s].SE+num[x]>0),0);
  for(int i=s;i>0;i--) 
    f[x][i]=max(pr(f[x][i].FR,f[x][i].SE+num[x]),pr(f[x][i-1].FR+(f[x][i-1].SE+num[x]>0),0));
  f[x][0].SE+=num[x];
  s++;
  return s;
}

int main() {
  int cases;
  scanf("%d",&cases);
  for(;cases;cases--) {
  	int n;
  	scanf("%d%d",&n,&k);
  	for(int i=1;i<=n;i++) {
	  e[i].clear();
	  num[i]=0;
    }
  	for(int i=1;i<=n;i++) {
  		int x;
  		scanf("%d",&x);
  		num[i]-=x;
	  } 
	for(int i=1;i<=n;i++) {
		int x;
		scanf("%d",&x);
		num[i]+=x;
	}
	for(int i=1;i<n;i++) {
		int x,y;
		scanf("%d%d",&x,&y);
		e[x].push_back(y);
		e[y].push_back(x);
	}
	dfs(1,0);
	printf("%d\n",ans);
  }
  return 0;
} 

E. Kirchhoff’s Current Loss

先忽略每个叶子的阻值要是整数的限制,考虑在实数域上做这个最优化问题。
对于点 i i i,令 f i ( x ) f_i(x) fi(x)表示考虑点 i i i子树,最小的叶子阻值和关于点 i i i的阻值的函数,容易归纳证明 f i ( x ) f_i(x) fi(x)是一个正比例函数(我们显然可以对子树做常数倍放缩)。
定义 c c c f i ( x ) f_i(x) fi(x)的斜率,令点 i i i的儿子的函数斜率分别为 c 1 , c 2 , . . . , c k c_1,c_2,...,c_k c1,c2,...,ck
对于叶子显然有 c i = 1 c_i=1 ci=1
对于串联显然有 c i = min ⁡ i = 1 k c i c_i=\min_{i=1}^{k}c_i ci=mini=1kci,此时只保留 c i c_i ci最小的儿子。
并联稍微有点麻烦。分析一下,考虑令点 i i i的阻值为 1 1 1,此时点 i i i的儿子的阻值分别为 R 1 , R 2 , . . . , R k R_1,R_2,...,R_k R1,R2,...,Rk,那么就是要最小化 c = ∑ i = 1 k c i R i c=\sum_{i=1}^{k}c_iR_i c=i=1kciRi,且 ∑ i = 1 k 1 R i = 1 \sum_{i=1}^{k}\frac{1}{R_i}=1 i=1kRi1=1。用拉格朗日乘数法,可以求解出当 R i = ∑ i = 1 k c i c i R_i=\frac{\sum_{i=1}^{k}\sqrt {c_i}}{\sqrt {c_i}} Ri=ci i=1kci 时取到最小值,此时 c = ∑ i = 1 k c i \sqrt c=\sum_{i=1}^{k}\sqrt {c_i} c =i=1kci
注意到每个点的 c \sqrt c c 都是整数,因此 f 1 ( r ) f_1(r) f1(r)也是整数。再分析一下可以发现,令 1 1 1号点的 c \sqrt c c v v v,则 f 1 ( r ) = v 2 r f_1(r)=v^2r f1(r)=v2r,取到最优解时每个阻值非 0 0 0的叶子阻值均为 v r vr vr,也是整数。因此事实上整数的限制可以忽略。
直接求出 v v v并dfs出所有阻值非 0 0 0的叶子即可,单组数据时间复杂度 O ( n ) \mathcal O(n) O(n)

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f

using namespace std;

typedef long long ll;

vector <int> e[200005];

int f[200005],id[200005],dfs_cnt;
bool col[200005],ban[200005];

void dfs1(int x) {
  if (!e[x].size()) {
  	id[x]=++dfs_cnt;
  	f[x]=1;
  	return;
  }
  if (!col[x]) {
  	int minn=0;
  	for(int i=0;i<e[x].size();i++) {
  		int u=e[x][i];
  		dfs1(u);
  		if (f[u]<f[minn]) minn=u;
	  }
	f[x]=f[minn];
	for(int i=0;i<e[x].size();i++)
	  if (e[x][i]!=minn) ban[e[x][i]]=1;
  }
  else {
  	for(int i=0;i<e[x].size();i++) {
  		int u=e[x][i];
  		dfs1(u);
  		f[x]+=f[u];
	  }
  }
}

bool vis[200005];

void dfs2(int x) {
  if (!e[x].size()) {
  	vis[id[x]]=1;
  	return;
  }
  for(int i=0;i<e[x].size();i++)
    if (!ban[e[x][i]]) dfs2(e[x][i]);
}

char str[2000005];
int st[2000005];

void build(int n) {
  int sz=0,top=0;
  for(int i=1;i<=n;i++)
    if (str[i]=='(') {
    	e[++sz].clear();
    	if (top) e[st[top]].push_back(sz);
    	st[++top]=sz;
    	ban[sz]=vis[sz]=f[sz]=0;
	}
	else if (str[i]=='*') {
		e[++sz].clear();
		ban[sz]=vis[sz]=f[sz]=0;
		if (top) e[st[top]].push_back(sz);
	}
	else if (str[i]=='S') col[st[top]]=0;
	else if (str[i]=='P') col[st[top]]=1;
	else if (str[i]==')') top--;
}

int main() {
  int cases;
  scanf("%d",&cases);
  for(;cases;cases--) {
  	int r;
  	scanf("%d",&r);
  	cin.getline(str,2e6);
  	int len=strlen(str+1);
  	build(len);
  	dfs_cnt=0;
  	f[0]=inf;
  	dfs1(1);
  	dfs2(1);
  	int s=0;
  	for(int i=1;i<=dfs_cnt;i++) s+=vis[i];
  	printf("REVOLTING ");
  	for(int i=1;i<=dfs_cnt;i++) printf("%lld ",(vis[i])?(ll)s*r:0LL);
  	printf("\n");
  }
  return 0;
} 

F. Intergalactic Sliding Puzzle

注意到操作是可逆的,并且如果 E E E在某一行一定可以移到中间。我们称一个 E E E在第二行中间的状态为标准状态,那么可以将初态和终态均移成标准状态,并且要求在标准状态间转移。这样的转移只能是一个旋转操作(旋两行左半边和第一行中间;两行右半边和第一行中间;全部)。
考虑从上到下,从左到右写下当前状态的每个数字,得到一个 4 k + 1 4k+1 4k+1的排列 P P P。注意到旋转操作旋转的长度都是奇数,因此不会改变 P P P的奇偶性。那么若 P P P是奇排列显然无解,偶排列的话我们下面给出构造。
对于左半边的两个数 a a a b b b,右半边的数 c c c,我们可以先将 b b b旋到中间(旋左半边);再将 c c c旋到中间(旋右半边);再将 a a a选到中间(旋左半边);再将 b b b旋到中间,此时 a a a到了原来 c c c的位置;再将 b b b旋到原来 a a a的位置,此时 c c c恰好到了原来 b b b的位置。注意到这样操作相当于 ( a , b , c ) (a,b,c) (a,b,c)变成了 ( b , c , a ) (b,c,a) (b,c,a),当 b b b在右半边的时候也类似。
有了这个操作就很好做了。考虑先将 k + 1 k+1 k+1旋到第一行中间。然后对于当前在左半边但终态不该在的数,跟右半边终态不该在的数一一匹配,用上面的操作即可交换(分别设为 a a a c c c,随便找个 b b b就好了)。
对于左半边的数字 ( a , b ) (a,b) (a,b)和右半边的数字 ( c , d ) (c,d) (c,d),依次使用上面的操作 ( a , b ) , ( c , d ) (a,b),(c,d) (a,b),(c,d)-> ( b , d ) , ( c , a ) (b,d),(c,a) (b,d),(c,a)-> ( b , a ) , ( d , c ) (b,a),(d,c) (b,a),(d,c),于是可以每次在两边分别做一个对换。注意到整个排列是偶排列,因此两边内部做对换达到终态次数的奇偶性相同,那么直接做就好了。
把左旋和右旋写成shortcut,那么单组数据时间复杂度和压缩后操作序列长度都是 O ( k 2 ) \mathcal O(k^2) O(k2)

#include <bits/stdc++.h>
#define FR first
#define SE second

using namespace std;

typedef pair<int,int> pr;

int num[70];
string ans;

bool init(int n) {
  ans.clear(); 
  int id[70];
  memset(id,0,sizeof(id));
  for(int i=1;i<=n;i++) id[n-i+1]=i;
  for(int i=n+1;i<=2*n;i++) id[n+i+1]=i;
  for(int i=2*n+1;i<=3*n;i++) id[i-n+1]=i;
  for(int i=3*n+1;i<=4*n;i++) id[7*n+2-i]=i;
  id[n+1]=4*n+1;
  for(int i=1;i<=4*n+2;i++) num[i]=id[num[i]];
  int u=0;
  for(int i=1;i<=4*n+2;i++)
    if (!num[i]) {
    	u=i;
    	break;
	}
  if (u<=n) {
  	for(int i=1;i<=u;i++) ans+="r";
  	ans+="d";
  	for(int i=u;i>1;i--) swap(num[i],num[i-1]);
  	swap(num[1],num[4*n+1]);
  	swap(num[4*n+1],num[4*n+2]);
  }
  else if (u<=2*n) {
  	for(int i=u;i<=2*n;i++) ans+="r";
  	for(int i=u;i<2*n;i++) swap(num[i],num[i+1]);
  	swap(num[2*n],num[4*n+2]);
  }
  else if (u<=3*n) {
  	for(int i=u;i>2*n;i--) ans+="l";
  	ans+="d";
  	for(int i=u;i>2*n+1;i--) swap(num[i],num[i-1]);
  	swap(num[2*n+1],num[4*n+1]);
  	swap(num[4*n+1],num[4*n+2]);
  }
  else if (u<=4*n) {
  	for(int i=u;i<=4*n;i++) ans+="l";
  	for(int i=u;i<4*n;i++) swap(num[i],num[i+1]);
  	swap(num[4*n],num[4*n+2]);
  }
  else if (u==4*n+1) {
  	ans+="d";
  	swap(num[4*n+1],num[4*n+2]);
  }
  bool s=0;
  for(int i=1;i<=4*n+1;i++)
    for(int j=i+1;j<=4*n+1;j++)
	  if (num[i]>num[j]) s^=1;
  return s^1; 
}

void gettrans(int n) {
  printf("A ");
  putchar('u');
  for(int i=1;i<=n;i++) putchar('l');
  putchar('d');
  for(int i=1;i<=n;i++) putchar('r');
  printf("\n");
  printf("B ");
  for(int i=1;i<=n;i++) putchar('l');
  putchar('u');
  for(int i=1;i<=n;i++) putchar('r');
  putchar('d');
  printf("\n");
  printf("C ");
  putchar('u');
  for(int i=1;i<=n;i++) putchar('r');
  putchar('d');
  for(int i=1;i<=n;i++) putchar('l');
  printf("\n");
  printf("D ");
  for(int i=1;i<=n;i++) putchar('r');
  putchar('u');
  for(int i=1;i<=n;i++) putchar('l');
  putchar('d');
  printf("\n");
}

void swap1(int x,int y,int z,int n) {
  z-=2*n;
  for(int i=1;i<=y;i++) ans+="A";
  for(int i=1;i<=z;i++) ans+="C";
  for(int i=1;i<=(x-y+2*n+1)%(2*n+1);i++) ans+="A";
  for(int i=1;i<=z;i++) ans+="D";
  for(int i=1;i<=x;i++) ans+="B";
  z+=2*n;
  swap(num[x],num[z]);
  swap(num[y],num[x]);
}

void swap2(int x,int y,int z,int n) {
  x-=2*n;y-=2*n; 
  for(int i=1;i<=y;i++) ans+="C";
  for(int i=1;i<=z;i++) ans+="A";
  for(int i=1;i<=(x-y+2*n+1)%(2*n+1);i++) ans+="C";
  for(int i=1;i<=z;i++) ans+="B";
  for(int i=1;i<=x;i++) ans+="D";
  x+=2*n;y+=2*n;
  swap(num[x],num[z]);
  swap(num[y],num[x]);
}

void solve(int n) {
  int id=0;
  for(int i=1;i<=4*n+1;i++)
    if (num[i]==4*n+1) {
    	id=i;
    	break;
	}
  if (id<=2*n) {
  	for(int i=1;i<=id;i++) {
  		ans+="A";
  		swap(num[4*n+1],num[1]);
  		for(int j=1;j<2*n;j++) swap(num[j],num[j+1]);
	  }
  }
  else if (id<=4*n) {
  	for(int i=1;i<=id-2*n;i++) {
  		ans+="C";
  		swap(num[4*n+1],num[2*n+1]);
  		for(int j=2*n+1;j<4*n;j++) swap(num[j],num[j+1]);
	  }
  }
  for(;;) {
  	int u=0,v=0;
  	for(int i=1;i<=2*n;i++) {
  		if (num[i]>2*n) u=i;
  		if (num[2*n+i]<=2*n) v=2*n+i;
	  }
	if (!u) break;
	if (u==1) swap1(u,2,v,n);
	else swap1(u,1,v,n);
  }
  pr a[70],b[70];
  bool vis[70];
  memset(vis,0,sizeof(vis));
  int sz1=0,sz2=0;
  for(int i=1;i<=2*n;i++)
    if (!vis[i]) {
    	int x=i,cur[70],cnt=0;
    	do {
    		cur[++cnt]=x;
    		vis[x]=1;
    		x=num[x];
		} while (x!=i);
		for(int j=cnt;j>1;j--) a[++sz1]=pr(cur[j-1],cur[j]);
	}
  for(int i=2*n+1;i<=4*n;i++)
    if (!vis[i]) {
    	int x=i,cur[70],cnt=0;
    	do {
    		cur[++cnt]=x;
    		vis[x]=1;
    		x=num[x];
		} while (x!=i);
		for(int j=cnt;j>1;j--) b[++sz2]=pr(cur[j-1],cur[j]);
	}
  while (sz1<sz2) a[++sz1]=pr(1,2);
  while (sz2<sz1) b[++sz2]=pr(2*n+1,2*n+2);
  for(int i=1;i<=sz1;i++) {
  	swap1(a[i].FR,a[i].SE,b[i].SE,n);
  	swap2(b[i].SE,b[i].FR,a[i].SE,n);
  }
  for(int i=1;i<=n;i++) ans+="r";
}

int rd() {
  string s;
  cin>>s;
  if (s[0]=='E') return 0;
  return atoi(s.c_str());
}

int b[70];
string cur;

bool check(int n) {
  cur.clear();
  int id=0;
  for(int i=1;i<=4*n+2;i++)
    if (!b[i]) {
    	id=i;
    	break;
	}
  for(int i=0;i<ans.size();i++)
    if (ans[i]>='a'&&ans[i]<='z') cur+=ans[i];
    else if (ans[i]=='A') {
	    cur+="u";
	    for(int i=1;i<=n;i++) cur+="l";
	    cur+="d";
	    for(int i=1;i<=n;i++) cur+="r";
	}
	else if (ans[i]=='B') {
	    for(int i=1;i<=n;i++) cur+="l";
	    cur+="u";
	    for(int i=1;i<=n;i++) cur+="r";
	    cur+="d";
	}
	else if (ans[i]=='C') {
	    cur+="u";
	    for(int i=1;i<=n;i++) cur+="r";
	    cur+="d";
	    for(int i=1;i<=n;i++) cur+="l";
	}
	else {
	    for(int i=1;i<=n;i++) cur+="r";
	    cur+="u";
	    for(int i=1;i<=n;i++) cur+="l";
	    cur+="d";
	}
  for(int i=0;i<cur.length();i++)
    if (cur[i]=='l') {
    	if (id==1||id==2*n+2) return 0;
    	swap(b[id],b[id-1]);
    	id--;
	}
	else if (cur[i]=='r') {
		if (id==2*n+1||id==4*n+2) return 0;
		swap(b[id],b[id+1]);
		id++;
	}
	else if (cur[i]=='u') {
		if (id!=2*n+2&&id!=3*n+2&&id!=4*n+2) return 0;
		swap(b[id],b[id-2*n-1]);
		id-=2*n+1;
	}
	else {
		if (id!=1&&id!=n+1&&id!=2*n+1) return 0;
		swap(b[id],b[id+2*n+1]);
		id+=2*n+1;
	}
  for(int i=1;i<=4*n+1;i++)
    if (b[i]!=i) return 0;
  return 1;
}

int main() {
  int cases;
  scanf("%d",&cases);
  for(;cases;cases--) {
  	int n;
  	scanf("%d",&n);
  	for(int i=n;i>0;i--) num[i]=rd();
  	num[4*n+1]=rd();
  	for(int i=2*n+1;i<=3*n;i++) num[i]=rd();
  	for(int i=n+1;i<=2*n;i++) num[i]=rd();
  	num[4*n+2]=rd();
  	for(int i=4*n;i>3*n;i--) num[i]=rd();
  	
  	/*for(int i=1;i<=n;i++) b[i]=num[n-i+1];
  	b[n+1]=num[4*n+1];
  	for(int i=n+2;i<=2*n+1;i++) b[i]=num[i+n-1];
  	for(int i=2*n+2;i<=3*n+1;i++) b[i]=num[i-n-1];
  	b[3*n+2]=num[4*n+2];
  	for(int i=3*n+3;i<=4*n+2;i++) b[i]=num[7*n+3-i];*/
  	
  	if (!init(n)) {
  		puts("SURGERY FAILED");
  		continue;
	  }
	puts("SURGERY COMPLETE");
	solve(n);
	cout<<ans<<endl;
	gettrans(n);
	puts("DONE");
	
	//assert(check(n));
  }
  return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值