3.29 考试
3.29
今天考出了这次集训到今天的历史最低排名,值得反思,把解题报告先写一写。
A
题意:给一颗\(n(\le 100000)\)个点的有根树,初始时每个叶子节点有三个状态:\(-1,0,1\)。\(-1\)表示未确定状态,\(0\)表示这个点属于\(A\),\(1\)表示属于\(B\)。\(A\)与\(B\)轮流行动,选择一个\(-1\)状态的叶子节点,标记为自己的状态。非叶子节点的状态是它儿子中出现次数较多的一种(保证儿子是奇数)。两人均采取最优策略。求\(A\)先手是否必胜,若必胜,求出所有保证必胜的第一步可以选择的节点。
每个子树可以视作一个游戏,然后合并到根。
每个子树可以有三个状态\(A\)必胜,\(B\)必胜与先手必胜。
转移的时候,每个人先去选先手必胜的儿子子树,然后可以得到这个点的状态。
这样就解决了第一问。
第二问需要分情况讨论
如果整棵树都是\(A\)必胜,显然每个未确定的叶子节点都可以
否则整颗树是先手必胜,我们进子树去查看状态
如果一个子树是先手必胜就进去,知道进入一个未确定的叶子节点把它加入(未确定的叶子节点就是先手必胜)
如果一个子树是\(B\)必胜,其实也是有情况可以选择的,如果我们可以选一个点把这个子树变成先手必胜的话,那么相当于逼\(B\)去走这一步。什么样子的子树是\(B\)必胜可以转换成先手必胜呢?其实也很简单,\(A\)必胜的儿子比\(B\)必胜的儿子少一个就是条件。
Code:
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
const int N=1e5+10;
#define ll long long
template <class T>
void read(T &x)
{
int f=0;x=0;char c=getchar();
while(!isdigit(c)) f|=c=='-',c=getchar();
while(isdigit(c)) x=x*10+c-'0',c=getchar();
if(f) x=-x;
}
int head[N],to[N],Next[N],cnt;
void add(int u,int v)
{
to[++cnt]=v,Next[cnt]=head[u],head[u]=cnt;
}
int n,yuy[N],s[N],col[N],tot;
void dfs(int now)
{
if(!head[now]) return;
for(int i=head[now];i;i=Next[i])
{
int v=to[i];
dfs(v);
if(col[v]==1) --yuy[now];
else if(col[v]==0) ++yuy[now];
}
if(yuy[now]>0) col[now]=0;
else if(yuy[now]==0) col[now]=-1;
else col[now]=1;
}
void dfs1(int now)
{
if(!head[now]&&col[now]==-1) {s[++tot]=now;return;}
for(int i=head[now];i;i=Next[i])
{
int v=to[i];
if(col[v]==-1||(col[v]==1&&yuy[v]==-1))
dfs1(v);
}
}
void work()
{
memset(head,0,sizeof head);
memset(yuy,0,sizeof yuy);
cnt=0;
read(n);
for(int f,i=1;i<=n;i++) read(f),add(f,i);
for(int i=1;i<=n;i++) read(col[i]);
dfs(1);
if(col[1]==1){puts("-1");return;}
else if(col[1]==0)
{
int ct=0;
for(int i=1;i<=n;i++) ct+=!head[i]&&col[i]==-1;
printf("%d ",ct);
for(int i=1;i<=n;i++)
if(!head[i]&&col[i]==-1)
printf("%d ",i);
puts("");
return;
}
tot=0;
dfs1(1);
std::sort(s+1,s+1+tot);
printf("%d ",tot);
for(int i=1;i<=tot;i++) printf("%d ",s[i]);
puts("");
}
int main()
{
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
int T;
read(T);
while(T--) work();
return 0;
}
B
题意:有一个长为\(n(n=2^k,k\le 20,k\in \Z)\)的正整数序列\(a_i\),且有
现在给你\(b\),请还原序列\(a\)
这个题做法比较多,说一种我会了的
上面这个式子其实等价于
后面的取反把\(b\)取反就可以去掉
然后你注意一下异或FWT的本质
可以惊奇的发现
\(c_i+\sum a=2b_i\)
其实就是系数对应起来是01与-1,1,然后加上整体除个2就是了
于是可以直接求出\(c\),然后对\(c\)做一个FWT逆变换就可以了
Code:
#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define ll long long
template <class T>
void read(T &x)
{
x=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) x=x*10+c-'0',c=getchar();
}
const int N=(1<<20)+10;
int n;
ll b[N];
void FWT(int len)
{
for(int le=1;le<len;le<<=1)
for(int p=0;p<len;p+=le<<1)
for(int i=p;i<p+le;i++)
{
ll x=b[i],y=b[i+le];
b[i]=(x+y)/2;
b[i+le]=(x-y)/2;
}
}
int main()
{
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
read(n);
for(int i=0;i<n;i++) read(b[i]);
for(int i=0;i<n;i++)
if(i<n-1-i)
std::swap(b[i],b[n-1-i]);
for(int i=n-1;~i;i--)
b[i]=(b[i]<<1)-b[0];
FWT(n);
for(int i=0;i<n;i++) printf("%lld ",b[i]);
return 0;
}
C
交互题。要求还原一颗\(n(\le 100000)\)个点的树,\(query(x,y,z)\)表示询问离这三个点距离和最小的点,询问次数不超过\(1000000\)次,树的形态随机。
这里树的形态随机表示prufer序列随机,特点是每个点期望度数是\(O(1)\)的
那么可以随机点分治
具体操作是随机两个端点,然后询问这个联通块所有点,把这条链抽出来,然后把这个联通块的点划分到链上的点去,递归处理子问题,可以证明这是一个小常数的\(O(n\log n)\)
Code:
#include "c.h"
#include <algorithm>
#include <vector>
#include <ctime>
std::vector<int> yuu[100010];
int X;
bool cmp(int x,int y){return query(X,x,y)==x;}
void dfs(std::vector<int> v)
{
if(v.size()==1) return;
if(v.size()==2)
{
check(v[0],v[1]);
return;
}
for(int i=0;i<v.size();i++)
yuu[v[i]].clear();
std::random_shuffle(v.begin(),v.end());
int x=v[0],y=v[1];
std::vector<int> yuy;
for(int i=0;i<v.size();i++)
{
int z=v[i],now=query(x,y,z);
yuu[now].push_back(z);
if(now==z) yuy.push_back(now);
}
X=x;
std::sort(yuy.begin(),yuy.end(),cmp);
for(int i=1;i<yuy.size();i++)
check(yuy[i-1],yuy[i]);
for(int i=0;i<yuy.size();i++)
dfs(yuu[yuy[i]]);
}
void solve(int n)
{
srand(time(0));
for(int i=1;i<=n;i++) yuu[1].push_back(i);
dfs(yuu[1]);
}