B. Palindrome Game
题目描述
解法
考试时候推规律没推出来,直接上了个 (O(n^2)) 暴力 (dp)
回文串是很好做的,那不是回文怎么办。有一个开天辟地的结论是:( t Alice) 不会输。
证明这个结论是很简单的,考虑 (2) 操作的本质其实是跳过一次操作。
那么如果 ( t Alice) 选了 (1) 操作输了,那么他可以选择 (2) 操作,就逼迫 ( t Bob) 接受了输的状态,另一种情况 ( t Alice) 也不会输。
所以博弈游戏要考虑有没有反悔机制!
根据这个大结论的指导,我们可以给出 ( t Alice) 具体的不败策略:
- 如果还有较多的非对称 (0),那么 ( t Alice) 选择跳过操作,那么 ( t Bob) 只能拿走这些非对称 (0),要不然会增加更多的非对称 (0)
- 如果非对称 (0) 只有一个,那么 ( t Alice) 看是否有对称 (0),如果有的话他必赚 (2) 元或者 (1) 元。如果没有的话那么 ( t Alice) 还是跳过操作可以赚 (1) 元。
不难发现平局仅出现在 (11001) 之类的情况,也就是 ( t Alice) 选择非对称 (0) 之后只能赚 (1) 元。
D. MEX Tree
题目描述
给定一棵 (n) 个点的树,从 (0) 开始编号,统计路径上点集 (mex) 为 (0leq ileq n) 的路径个数。
(2leq nleq 2cdot 10^5)
解法
这个东西差分的话会好做一些,具体来说设 (ans[i]) 为 (mexgeq i) 的路径条数,那么 (mex) 为 (i) 的路径条数是:
那么怎么统计 (ans[i]) 呢?我们可以强制点 ([0,i-1]) 出现在路径中,那么就一定满足路径的 (mexgeq 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) 的最小代价,可以写出转移:
发现就是代价比较难算,多半是用数据结构维护了。考虑 (i-1) 移动到 (i) 的过程,设和 (i) 同颜色的前驱位置是 (x),那么这种颜色在 (jin (x,i]) 的贡献都是不变的,在 (jin[1,x]) 的贡献都会增加 (i-x),所以用线段树维护即可,时间复杂度 (O(nklog 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
",dp[k][n]);
}