CF1100G Tree-Tac-Toe 题解
这题在 CF rating 是 3100+,听了讲评之后感觉醍醐灌顶。
如果您不看题解就 AC,那您是真的强。
首先,我们发现,黑不可能赢。
接下来,考虑一种简单的情况:没有任何点初始时有颜色。
情况 1:树中有一个点 \(A\) 的度大于等于 \(4\)。
我们假设它连着 \(B,C,D,E\) 等点。那么,白方下 \(A\),不妨令黑下在 \(B\),白下 \(C\),此时白有两个未被阻挡且连起来的点,白胜。
剩下的情况中,每一个点的度都小于等于 \(3\)。
情况2:树中有一个点度为 \(3\) ,且从这个点伸出至少两个长度至少为 \(2\) 的链。
白下 \(2\),不妨另黑下 \(3\),则白下 \(4\),此时白有两个未被阻挡且连起来的点,白胜。
剩下的情况中,每一个点最多伸出一条长度大于等于 \(2\) 的链,并且容易发现,此时至多有 \(3\) 个度为 \(3\) 的点。否则,可以放入情况 2 中。
此时任选其中的两个点做讨论。此时,它们会出现图示的 \(\text{H}\) 形结构。
一、两条竖杆中间夹着偶数个点。
白下 \(4\),黑只能下 \(2\);白下 \(5\),黑下 \(7\),平。
二、两条竖杆之间夹着奇数个点。
白下 \(4\),不妨另黑下 \(2\);白下 \(7\),不管黑堵住哪一边,白都能走另外一边连成 \(3\) 个子。
我们转化为一般的情况。假设中间被夹着的点是 \(1\) 至 \(n\)。白最优解就是下 \(1\) 和 \(n\),那么黑就会堵住另外两边的点(\(1\) 前面的,\(n\) 后面的);然后白就会隔一个位置下一个子,然后黑为了防止白连成三个子,就会下在白中间的空位。就这样一直往中间下,如果最后白最中间的两个子有一个空位,则白胜(因为此时会留下两个空,无论黑占掉哪一个,白都可以下;另一个空而胜利)。
容易发现,上面这段等价于:\(n\) 为奇数则白胜,否则平。
剩下的情况就是一条链了。这是黑和白会一直纠缠,最终平。上面就是所有情况。
如果最开始有白色的点呢?我们使用化归的思想,把左侧的 \(A\) 转化为右侧的 \(A'\):
其中,右侧所有点未染色,且 \(B,C,D\) 是新添加的。
思考一下意义:如果 \(A'\) 此时被白下,黑为了不负必定下 \(D\),此时 \(C,B\) 就被浪费掉了(如果在这里黑子想连成三个,由于白先下,那么黑也比白慢,必须与白牵制),相当于让白多下了。
没了。注意每次清零的时候别用 memset
,直接循环赋 \(0\),否则 T 飞第二个点。
代码:
#include<bits/stdc++.h>
using namespace std;
#define rep(i,a,b) for(register int i=(a);i<=(b);++i)
#define Rep(i,a,b) for(register int i=(a);i>=(b);--i)
#define read() Read<int>()
#define write Write<int>
#define writesp Writesp<int>
#define writeln Writeln<int>
template<typename T>
inline T Read()
{
bool f=0;T x=0;char ch;
do{ch=getchar();f|=(ch=='-');}while(!isdigit(ch));
do{x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}while(isdigit(ch));
return f?-x:x;
}
template<typename T>
inline void Write(T x)
{
if(x<0){putchar('-');write(-x);return;}
if(x>9)write(x/10);
putchar(x%10+48);
}
template<typename T> inline void Writeln(T x){Write(x);puts("");}
template<typename T> inline void Writesp(T x){Write(x);putchar(' ');}
const int maxn=2e6+5;
int nxt[maxn<<1|1],to[maxn<<1|1],head[maxn],tot=0;
int n;
void addedge(int u,int v)
{
nxt[++tot]=head[u];
head[u]=tot;
to[tot]=v;
}
int deg[maxn];
bool judge1()//判断是否有度>=4的点
{
rep(i,1,n)if(deg[i]>3)return 1;
return 0;
}
//接下来的情况中,所有点的度都<=3
bool judge2()//判断是否有挂着两条长度>=2的链的点
{
rep(u,1,n)
{
int count=0;
if(deg[u]!=3)continue;
for(register int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(deg[v]>1)++count;
}
if(count>1)return 1;
}
return 0;
}
int dis[maxn];
void dfs(int u,int fa)
{
dis[u]=dis[fa]+1;
for(register int i=head[u];i;i=nxt[i])
{
int v=to[i];
if(fa==v)continue;
dfs(v,u);
}
}
//接下来,每一个点只有可能挂着一条长链,剩下的地方都只连接着一个点
bool judge3()//判断“H”形情况
{
int a=0,b=0,c=0;
rep(i,1,n)
{
if(deg[i]>=3)
{
if(a&&b){c=i;break;}
if(a){b=i;}
else a=i;
}
}
if(!b&&!c)return 0;
memset(dis,0,sizeof dis);dfs(a,0);
int len=dis[b];
if(len&1)return 1;
if(!c)return 0;
len=dis[c];
if(len&1)return 1;
memset(dis,0,sizeof dis);dfs(b,0);
len=dis[c];
if(len&1)return 1;
return 0;
}
int main(){
int t=read();
while(t--)
{
n=read();
rep(i,1,n-1)
{
int u=read(),v=read();
addedge(u,v);
addedge(v,u);
++deg[u];++deg[v];
}
int cur=n;
rep(i,1,n){
char ch;cin>>ch;
if(ch=='W')
{
cur+=3;
addedge(i,cur-2);addedge(cur-2,i);
addedge(cur-1,cur-2);addedge(cur-2,cur-1);
addedge(cur,cur-2);addedge(cur-2,cur);
deg[i]++;deg[cur-2]=3;
deg[cur-1]=deg[cur]=1;
}//拆点
}
n=cur;
if(judge1()){puts("White");}
else if(judge2()){puts("White");}
else if(judge3()){puts("White");}
else puts("Draw");
rep(i,1,n)deg[i]=head[i]=0;
tot=0;
}
return 0;
}