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,kle 20,kin ))的正整数序列(a_i),且有

[b_i=sum_{0le j<n}((bitcount((i or j)xor i)+1)mod 2)a_i ]

现在给你(b),请还原序列(a)


这个题做法比较多,说一种我会了的

上面这个式子其实等价于

[b_i=sum_j[bitcount((sim i)& j)&1=0]a_j ]

后面的取反把(b)取反就可以去掉

然后你注意一下异或FWT的本质

[c_i=sum_j(-1)^{bitcount(i&j)}a_j ]

可以惊奇的发现

(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(nlog 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]);
}
原文地址:https://www.cnblogs.com/butterflydew/p/10623713.html