其他分享
首页 > 其他分享> > Codeforces Round #721 (Div. 2)

Codeforces Round #721 (Div. 2)

作者:互联网

B. Palindrome Game

题目描述

点此看题

解法

考试时候推规律没推出来,直接上了个 \(O(n^2)\) 暴力 \(dp\)

回文串是很好做的,那不是回文怎么办。有一个开天辟地的结论是:\(\tt Alice\) 不会输。

证明这个结论是很简单的,考虑 \(2\) 操作的本质其实是跳过一次操作

那么如果 \(\tt Alice\) 选了 \(1\) 操作输了,那么他可以选择 \(2\) 操作,就逼迫 \(\tt Bob\) 接受了输的状态,另一种情况 \(\tt Alice\) 也不会输。

所以博弈游戏要考虑有没有反悔机制!

根据这个大结论的指导,我们可以给出 \(\tt Alice\) 具体的不败策略:

不难发现平局仅出现在 \(11001\) 之类的情况,也就是 \(\tt Alice\) 选择非对称 \(0\) 之后只能赚 \(1\) 元。

D. MEX Tree

题目描述

点此看题

给定一棵 \(n\) 个点的树,从 \(0\) 开始编号,统计路径上点集 \(mex\) 为 \(0\leq i\leq n\) 的路径个数。

\(2\leq n\leq 2\cdot 10^5\)

解法

这个东西差分的话会好做一些,具体来说设 \(ans[i]\) 为 \(mex\geq i\) 的路径条数,那么 \(mex\) 为 \(i\) 的路径条数是:

\[ans[i]-ans[i+1] \]

那么怎么统计 \(ans[i]\) 呢?我们可以强制点 \([0,i-1]\) 出现在路径中,那么就一定满足路径的 \(mex\geq i\)

稍微想一下就知道这些点构成了一条链的形状,所以我们可以维护链的左端点和右端点,然后每次只需要新加入点 \(i\) 就可以强制 \([0,i]\) 出现在路径中了。考虑 \(i\) 合法的位置有三种:左端点的子树内;右端点的子树内;链上。如果不在合法的位置直接跳出循环即可。

然后就是讨论一下就行了,注意一定要单独考虑一个是另一个祖先的情况,要不然会被搞昏。

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 200005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,tot,Ind,siz[M],ans[M],f[M];
int l[M],r[M],a[M],dep[M],fa[M][20];
struct edge
{
	int v,next;
}e[2*M];
void dfs(int u,int p)
{
	dep[u]=dep[p]+1;fa[u][0]=p;
	for(int i=1;i<20;i++)
		fa[u][i]=fa[fa[u][i-1]][i-1];
	l[u]=++Ind;siz[u]=1;
	for(int i=f[u];i;i=e[i].next)
	{
		int v=e[i].v;
		if(v==p) continue;
		dfs(v,u);
		siz[u]+=siz[v]; 
	}
	r[u]=Ind;
}
int get(int u,int v)
{
	for(int i=19;i>=0;i--)
		if(dep[fa[u][i]]>dep[v])
			u=fa[u][i];
	return u;
}
int in(int x,int y)
{
	return l[x]<=l[y] && l[y]<=r[x]; 
}
signed main()
{
	T=read();
	while(T--)
	{
		n=read();Ind=tot=0;
		for(int i=0;i<=n+1;i++)
			a[i]=ans[i]=f[i]=0;
		for(int i=1;i<n;i++)
		{
			int u=read(),v=read();
			e[++tot]=edge{v,f[u]},f[u]=tot;
			e[++tot]=edge{u,f[v]},f[v]=tot;
		}
		dfs(0,0);
		ans[0]=n*(n-1)/2;
		for(int i=f[0],s=1;i;i=e[i].next)
		{
			int v=e[i].v;
			ans[1]+=s*siz[v];s+=siz[v];
		}
		int x=0,y=0;a[0]=1;
		for(int i=1;i<n;i++)
		{
			if(a[i])//this node has already appeared
			{
				ans[i+1]=ans[i];//none effect
				continue;
			}
			int rx=in(x,y),ry=in(y,x),t1=get(y,x),t2=get(x,y);
			if((x==y) || (rx && in(x,i) && !in(t1,i)) || (!rx && in(x,i)))
			{
				int t=i;
				while(t!=x) a[t]=1,t=fa[t][0];
				x=i;
			}
			else if((ry && in(y,i) && !in(t2,i)) || (!ry && in(y,i)))
			{
				int t=i;
				while(t!=y) a[t]=1,t=fa[t][0];
				y=i;
			}
			else break;//the answer is always 0
			t1=get(y,x);t2=get(x,y);
			if(in(x,y)) ans[i+1]=(n-siz[t1])*siz[y];
			else if(in(y,x)) ans[i+1]=(n-siz[t2])*siz[x];
			else ans[i+1]=siz[x]*siz[y];
		}
		for(int i=0;i<=n;i++)
			printf("%lld ",ans[i]-ans[i+1]);
		puts("");
	}
}

E. Partition Game

题目描述

点此看题

解法

最后一道反而是最简单的一道

因为 \(k\) 很小所以可以分层 \(dp\),设 \(dp[i]\) 表示划分到 \(i\) 的最小代价,可以写出转移:

\[dp[i]=\min(dp'[j-1]+cost(j,i)) \]

发现就是代价比较难算,多半是用数据结构维护了。考虑 \(i-1\) 移动到 \(i\) 的过程,设和 \(i\) 同颜色的前驱位置是 \(x\),那么这种颜色在 \(j\in (x,i]\) 的贡献都是不变的,在 \(j\in[1,x]\) 的贡献都会增加 \(i-x\),所以用线段树维护即可,时间复杂度 \(O(nk\log n)\)

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 35005;
const int inf = 0x3f3f3f3f;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k,a[M],b[M],s[4*M],tag[4*M],dp[105][M];
void down(int i)
{
	if(!tag[i]) return ;
	s[i<<1]+=tag[i];
	s[i<<1|1]+=tag[i];
	tag[i<<1]+=tag[i];
	tag[i<<1|1]+=tag[i];
	tag[i]=0; 
}
void add(int i,int l,int r,int L,int R,int x)
{
	if(L>r || l>R) return ;
	if(L<=l && r<=R)
	{
		tag[i]+=x;s[i]+=x;
		return ;
	}
	int mid=(l+r)>>1;down(i);
	add(i<<1,l,mid,L,R,x);
	add(i<<1|1,mid+1,r,L,R,x);
	s[i]=min(s[i<<1],s[i<<1|1]);
}
int ask(int i,int l,int r,int L,int R)
{
	if(L>r || l>R) return inf;
	if(L<=l && r<=R) return s[i];
	int mid=(l+r)>>1;down(i);
	return min(ask(i<<1,l,mid,L,R),ask(i<<1|1,mid+1,r,L,R));
}
signed main()
{
	n=read();k=read();
	for(int i=1;i<=n;i++)
		a[i]=read();
	memset(dp,0x3f,sizeof dp);
	dp[0][0]=0;
	for(int l=1;l<=k;l++)
	{
		memset(s,0,sizeof s);
		memset(tag,0,sizeof tag);
		memset(b,0,sizeof b);
		for(int i=1;i<=n;i++)
		{
			add(1,0,n,i-1,i-1,dp[l-1][i-1]);
			if(b[a[i]]) add(1,0,n,0,b[a[i]]-1,i-b[a[i]]);
			dp[l][i]=ask(1,0,n,0,i-1);
			b[a[i]]=i;
		}
	}
	printf("%d\n",dp[k][n]);
}

标签:return,int,tt,Codeforces,Alice,721,Div,include,dp
来源: https://www.cnblogs.com/C202044zxy/p/14798544.html