Codeforces Round #721(Div.2)题解(1527A~E)

Codeforces Round #721(Div.2)题解(1527A~E)

Prepared by Wogua_boy

整完OS期中考终于把这场补完了,少有的能让我全部补完的场..

A.And Then There Were K

题意:

给出一个数\(n\),找到最大的\(k\),使得\(n\&(n-1)\&(n-2)\&(n-3)\&...\&k=0\)

\(n \leq 10^9\)

题解:

二进制运算。

考虑\(n\)的二进制形式:\(100...01101\),随便写的一个,可以发现必须要把第一位二进制位\(\&\)掉。

那么就贪心的取\(k\)为:\(011111......1\),位数和\(n\)相同。在\(\&\)的过程中剩下的所有二进制位也必然会被\(\&\)掉。

时间复杂度\(O(logn)\)

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*f;
}
 
const int maxn=2e5+100;
 
int t,n,a[maxn];
int main () {
	t=read();
	while (t--) {
		n=read();
		int cnt=0;
		while (n) {
			cnt++;
			n/=2;
		}
		printf("%d\n",(1<<(cnt-1))-1);
	}
}

B1.Palindrome Game (easy)

题意:

给出一个01字符串,Alice和Bob在字符串上博弈,\(Alice\)先手。

每个人可以做两种操作:

(1)将一个本来为0的位改成1,花费1。

(2)翻转整个字符串,花费0。但前提是这个串不能是回文串,以及,上一次操作不能是(2)。

在easy版本中,保证字符串一开始是回文的。

Alice和Bob都足够聪明,谁会取得胜利?

题解:

博弈+分类讨论。

首先,每个人的贪心策略肯定是让字符串以回文的状态进入下一轮。

考虑在0000上博弈:

0000
1000 Alice 1
0001 Bob 0
1001 Alice 1
1101 Bob 1
1011 Alice 0
1111 Bob 1

这样两者都尽可能的贪心,但是发现打平了。

事实上后手可以通过这样一个trick来获得胜利:

0000
1000 Alice 1
1001 Bob 1
1101 Alice 1
1011 Bob 0
1111 Alice 1

大致意思就是,每当对手企图破坏回文状态,就在对手操作的回文位置补一位,如果对手只差一步就构造出回文串,就翻转字符串。

这样后手可以稳定比先手少花费2获得胜利。

当字符串为奇数长度,同时中间位置的字符是0的时候,Alice可以通过修改回文中心位置来转换先后手从而取得胜利。

当全为1的时候平局。

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*f;
}
 
const int maxn=2e5+100;
 
//单点修改
//翻转,在不是回文同时上一次操作不是翻转的前提下
int t,n;
string s;
int main () {
	t=read();
	while (t--) {
		n=read();
		cin>>s;
		int f=0;
		for (int i=0;i<s.size();i++) if (s[i]=='0') f++;
		if (!f) {
			printf("DRAW\n");
			continue;
		}
		if (n%2==1&&s[n/2]=='0'&&f!=1) {
			printf("ALICE\n");
			continue;
		} 
		printf("BOB\n");
	}
}

B2.Palindrome game (hard)

题意:

在B1的基础上,给出的字符串任意。

题解:

博弈+分类讨论。

考虑走到回文状态的先后手。

首先Alice先手,一开始不是回文的情况下,Alice可以一直翻转字符串,强制Bob去修改,当Bob只差一步构造出回文串的时候,Alice补那一下,强制Bob下一次继续修改,Alice从而在回文串状态下获得后手。

考虑奇数长度下中心位置为0的情况,可以发现对答案没有影响。

这种特殊情况:只有两个0,一个在中心,应该输出平局。没有操作空间。

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*f;
}
 
const int maxn=2e5+100;
 
//单点修改
//翻转,在不是回文同时上一次操作不是翻转的前提下
int t,n;
string s;
int main () {
	t=read();
	while (t--) {
		n=read();
		cin>>s;
		string s1=s;
		reverse(s1.begin(),s1.end());
		int f=0;
		for (int i=0;i<s.size();i++) if (s[i]=='0') f++;
		if (!f) {
			printf("DRAW\n");
			continue;
		}
		if (s1==s) {
			if (!f) {
				printf("DRAW\n");
				continue;
			}
			if (n%2==1&&s[n/2]=='0'&&f!=1) {
				printf("ALICE\n");
				continue;
			} 
			printf("BOB\n");
			continue;
		}
		f=0;
		int cnt=0;
		for (int i=1;i<=n/2;i++) {
			if (s[i-1]!=s[n-i]) {
				f++;
			}
			if (s[i-1]==s[n-i]&&s[i-1]=='0') {
				cnt++;
			}
		}
		if (f==1) {
			if (n%2==1&&s[n/2]=='0') {
				if (cnt==0)
					printf("DRAW\n");
				else
					printf("ALICE\n");
			}
			else {
				printf("ALICE\n");
			}
		}
		else
		printf("ALICE\n");
	}
}

C.Sequence Pair Weight

题意:

定义一个序列的重量为\((i,j)i<j\)的组数使得\(a_i=a_j\)

给出一个序列a,询问它所有子串的重量之和。

题解:

考虑一组\(a_i=a_j\)会影响多少个子序列,显然是\(i(n-j+1)\)个子串。

那么就枚举一遍,每个位置\(i\)对答案的贡献就是前面所有和\(a_i\)相等的位置的下标之和乘上\(n-i+1\)

随便找个数据结构维护一下就行。

时间复杂度\(O(nlogn)\)

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*f;
}
 
const int maxn=2e5+100;
int t,n;
int a[maxn];
int main () {
	t=read();
	while (t--) {
		n=read();
		for (int i=1;i<=n;i++) a[i]=read();
		map<int,long long> mp;
		long long ans=0;
		for (int i=1;i<=n;i++) {
			ans+=1ll*mp[a[i]]*(n-i+1);
			mp[a[i]]+=i;
		}
		printf("%lld\n",ans);
	}
}
 

D.MEX Tree

题意:

给出一棵树,下标0到n-1。

对从0到n的每个k,求出有多少条路径MEX=k。

题解:

树上分类讨论+容斥

首先容斥计算0的答案,有多少条路径不包含0?

答案是取出0的所有相邻节点,他们内部的子树的路径数量加起来。

然后容斥计算1的答案,有多少条路径包含0不包含1?

所有经过0的路径减去经过01的路径,经过01的路径数量就是先确定01的位置,然后求出0相对于1的子树大小x和1相对于0的子树大小y,xy就是答案。这里x相对于y的子树或y相对于x的子树在下文统称相对子树。

然后从2开始计算答案。

假设当前计算的点是x。

我们要求有多少条路径包含0到x-1,却不包含x。

用A,B表示包含0到x-1的路径的最短路径的起点和终点。

如果x在A或B的相对子树内,直接将A或B的相对子树大小减去i的子树大小,根据01路径的计算方法求一遍即可,然后把A或B更新为i即可。

如果i在A到B的路径上,那么i的答案注定是0。因为要构造包含0到i-1的路径,必定会经过i。

如果i既不在A或B的相对子树内,也不在A到B的路径上,当前的答案就是A的相对子树大小乘上B的相对子树,同时后面所有路径都无法构造。

时间复杂度\(O(nlogn)\)

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*f;
}

const int maxn=2e5+100;
int t,n;
int sz[maxn]; 
int dfn[maxn];
int tot=0;
vector<int> g[maxn];
int father[30][maxn];
int h[maxn];
void dfs (int x,int pre) {
	sz[x]=1;
	dfn[x]=++tot;
	for (int y:g[x]) {
		if (y==pre) continue;
		h[y]=h[x]+1;
		father[0][y]=x;
		dfs(y,x);
		sz[x]+=sz[y];
	}
}
int lca (int x,int y) {
	if (h[x]<h[y]) swap(x,y);
	for (int i=20;i>=0;i--) {
		if (h[x]-h[y]>>i) x=father[i][x];
	}
	if (x==y) return x;
	for (int i=20;i>=0;i--) {
		if (father[i][x]!=father[i][y]) {
			x=father[i][x];
			y=father[i][y];
		}
	}
	return father[0][x];
}
int main ()  {
	t=read();
	while (t--) {
		n=read();
		tot=0;
		for (int i=0;i<=20;i++) for (int j=0;j<n;j++) father[i][j]=0;
		for (int i=0;i<n;i++) g[i].clear(),h[i]=0;
		for (int i=1;i<n;i++) {
			int x=read();
			int y=read();
			g[x].push_back(y);
			g[y].push_back(x);
		}
		dfs(0,-1);
		for (int i=1;i<=20;i++) for (int j=0;j<n;j++) father[i][j]=father[i-1][father[i-1][j]];
		//容斥计算0的答案
		//就是0的所有子树内部算一下路径即可
		long long ans=0;
		for (int y:g[0]) {
			ans+=1ll*sz[y]*(sz[y]-1)/2;
		} 
		printf("%lld ",ans);
		//容斥计算1的答案
		//就是所有经过0的路径减去经过01的路径
		//1子树内的点不考虑即可 
		ans=0;
		long long sum=1;
		int fa=-1;
		for (int y:g[0]) {
			int x=sz[y];
			if (dfn[1]>=dfn[y]&&dfn[1]<=dfn[y]+sz[y]-1) {
				//如果1在y的子树内
				fa=y;
				x-=sz[1]; 
			}
			ans+=1ll*x*sum;
			sum+=x;
		}
		printf("%lld ",ans);
		int A=0,B=1;//AB分别表示之前路径的起点和终点
		//fa表示如果A为0,B在A的哪个子树 
		for (int i=2;i<=n;i++) {
			//计算mex=i的答案
			//如果i==n
			if (i==n) {
				printf("1 ");
				break;
			} 
			
			
			//先讨论i的位置
			//i在B的子树内
			if (dfn[i]>=dfn[B]&&dfn[i]<=dfn[B]+sz[B]-1) {
				ans=0;
				long long x,y;
				if (A==0) {
					x=n-sz[fa];
				}
				else {
					x=sz[A];
				}
				y=sz[B]-sz[i];
				ans=1ll*x*y;
				printf("%lld ",ans);
				B=i;
			} 
			else {
				//如果i在A的子树内
				if (A==0&&(dfn[i]<dfn[fa]||dfn[i]>dfn[fa]+sz[fa]-1)) {
					ans=0;
					long long x,y;
					x=n-sz[fa]-sz[i];
					y=sz[B];
					ans=1ll*x*y;
					printf("%lld ",ans);
					A=i;
				} 
				else if (A!=0&&dfn[i]>=dfn[A]&&dfn[i]<=dfn[A]+sz[A]-1) {
					ans=0;
					long long x,y;
					x=sz[A]-sz[i];
					y=sz[B];
					ans=1ll*x*y;
					printf("%lld ",ans);
					A=i;
				}
				//如果i在A到B的路径上
				//答案是0 
				else if (lca(A,i)==i||lca(B,i)==i) {
					ans=0;
					printf("%lld ",ans);
				}
				//如果不在 
				//答案是包含A到B的路径的路径数量
				// 
				else {
					long long x,y;
					ans=0;
					if (A==0) x=n-sz[fa];
					else x=sz[A];
					y=sz[B];
					ans=1ll*x*y;
					printf("%lld ",ans);
					for (int j=i+1;j<=n;j++) printf("0 ");
					break;
				}
			}
		}
		printf("\n");
	}
} 

E.Partition Game

题意:

给出一个长度为n的数组a。

一个数组t的花费是\(cost(t)=\sum_{x \in set(t)}last(x)-first(x)\)

请你把数组a划分成k段,使得每一段的花费之和最小。

\(n \leq 3e5+5e4,k \leq 100\)

题解:

这题完全看的官方题解,用线段树维护dp过程的转移。

让我们用dp解决这个问题。

考虑用\(f(i,j)\)表示前\(j\)个元素分成\(i\)段的答案。

\(c(i,j)\)表示从下标\(i\)\(j\)的子段花费。

那么有以下转移方程:

\(f(i,j)=min_{k=1}^{j-1}(f(i-1,k)+c(k+1,j))\)

如果暴力计算,时间复杂度\(O(n^2k)\)

现在我们需要用数据结构来计算\(f(i-1,k)+c(k+1,j)\)这个过程。

我们开一个新数组\(b_k=k-lst_{a_k}\)\(lst_{a_k}\)表示\(a_k\)的上一次出现位置。

然后,我们用\(k\)颗线段树维护,比如我们已经维护了前缀\(j\)的答案,对前缀\(j+1\),我们只需要在\([0,lst_{a_{j+1}}-1]\)这个区间上加上\(b_{j+1}\)即可。

时间复杂度\(O(nklogn)\)

#include<bits/stdc++.h>
using namespace std;
inline int read()
{
	int x=0,f=1;char c=getchar();
	while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();}
	while (c>='0'&&c<='9') x=(x<<3)+(x<<1)+(c^48),c=getchar();
	return x*f;
}

const int maxn=2e5+100;
int n,k;
int a[maxn];
struct node {
	int l,r;
	int sum;
	int lazy;
}segTree[maxn*4];
int f[maxn];
void build (int i,int l,int r) {
	segTree[i].l=l;
	segTree[i].r=r;
	segTree[i].lazy=0;
	if (l==r) {
		segTree[i].sum=f[l];
		return;
	}
	int mid=(l+r)>>1;
	build(i<<1,l,mid);
	build(i<<1|1,mid+1,r);
	segTree[i].sum=min(segTree[i<<1].sum,segTree[i<<1|1].sum);
} 
void spread (int i) {
	if (segTree[i].lazy) {
		segTree[i<<1].sum+=segTree[i].lazy;
		segTree[i<<1|1].sum+=segTree[i].lazy;
		segTree[i<<1].lazy+=segTree[i].lazy;
		segTree[i<<1|1].lazy+=segTree[i].lazy;
		segTree[i].lazy=0;
	}
}
void up (int i,int l,int r,int v) {
	if (segTree[i].l>=l&&segTree[i].r<=r) {
		segTree[i].sum+=v;
		segTree[i].lazy+=v;
		return;
	}
	spread(i);
	int mid=(segTree[i].l+segTree[i].r)>>1;
	if (l<=mid) up(i<<1,l,r,v);
	if (r>mid) up(i<<1|1,l,r,v);
	segTree[i].sum=min(segTree[i<<1].sum,segTree[i<<1|1].sum);
}
int query (int i,int l,int r) {
	if (segTree[i].l>=l&&segTree[i].r<=r) {
		return segTree[i].sum;
	}
	spread(i);
	int mid=(segTree[i].l+segTree[i].r)>>1;
	int ans=1e9;
	if (l<=mid) ans=min(ans,query(i<<1,l,r));
	if (r>mid) ans=min(ans,query(i<<1|1,l,r));
	return ans;
}
int lst[maxn];
int pre[maxn];
int b[maxn];
int main () {
	n=read();k=read();
	for (int i=1;i<=n;i++) {
		a[i]=read();
		lst[i]=pre[a[i]];
		pre[a[i]]=i;
		b[i]=i-lst[i];
	}
	for (int i=0;i<=n;i++) f[i]=1e9;
	f[0]=0;
	build(1,0,n);
	for (int i=1;i<=k;i++)  {
		for (int j=0;j<=n;j++) f[j]=1e9;
		f[0]=0;
		for (int j=1;j<=n;j++) {
			if (lst[j]) up(1,0,lst[j]-1,b[j]);
			f[j]=query(1,0,j-1);
		} 
		build(1,0,n);
	}
	printf("%d\n",f[n]);
}
posted @ 2021-05-22 21:03  zlc0405  阅读(128)  评论(0编辑  收藏  举报