B. Palindrome Game
题目描述
解法
考试时候推规律没推出来,直接上了个 \(O(n^2)\) 暴力 \(dp\)
回文串是很好做的,那不是回文怎么办。有一个开天辟地的结论是:\(\tt Alice\) 不会输。
证明这个结论是很简单的,考虑 \(2\) 操作的本质其实是跳过一次操作。
那么如果 \(\tt Alice\) 选了 \(1\) 操作输了,那么他可以选择 \(2\) 操作,就逼迫 \(\tt Bob\) 接受了输的状态,另一种情况 \(\tt Alice\) 也不会输。
所以博弈游戏要考虑有没有反悔机制!
根据这个大结论的指导,我们可以给出 \(\tt Alice\) 具体的不败策略:
- 如果还有较多的非对称 \(0\),那么 \(\tt Alice\) 选择跳过操作,那么 \(\tt Bob\) 只能拿走这些非对称 \(0\),要不然会增加更多的非对称 \(0\)
- 如果非对称 \(0\) 只有一个,那么 \(\tt Alice\) 看是否有对称 \(0\),如果有的话他必赚 \(2\) 元或者 \(1\) 元。如果没有的话那么 \(\tt Alice\) 还是跳过操作可以赚 \(1\) 元。
不难发现平局仅出现在 \(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]);
}