8.12 训练总结

前言

考试考炸了,以 15pts 的成绩获得倒数第二。

为啥空调开这么冷?为啥没有草稿纸?一定不是我的问题对对对。

T1. 黑暗幻想

题面

我之前做过但我忘了。

想法有点复杂,据说有简单方法。

首先考虑 \(k>\lfloor\frac{n}{2}\rfloor\),此时 Alice 显然只能操作一次,因此我们直接看 Bob 所在的点的度数就行了。

考虑 \(k\le\lfloor\frac{n}{2}\rfloor\),此时 Alice 可以操作多次。如果 Bob 所在的节点的度数比 \(k\) 小那么显然 Alice 获胜。

我们假设 Alice 每次都会选择 Bob 当前所在节点中子树大小最大的 \(k\) 个子树的根节点进行标记,那么我们知道第 \(k+1\) 棵子树的大小必然小于等于 \(\frac{n}{k+1}\),因此我们可以知道 Bob 每移动一次,子树大小的上限就会除以一个 \(k+1\),所以 Bob 最多可以移动 \(\log_{k+1}n\) 次。

我们考虑这么多次数下 Alice 到底选择了几个点,显然是 \(k\log_{k+1}n\),那 \(k\log_{k+1}n\) 和 \(n\) 之间谁大呢?

我们设函数 \(f(n)=k\log_{k+1}n-n\),那么可以得到 \(f'(n)=\frac{k}{n\ln(k+1)}-1\),因为我们知道 \(n\ge2k\),所以有 \(\frac{k}{n}\le\frac{1}{2}\),所以我们得到 \(f'(n)\le\frac{1}{2\ln(k+1)}-1\),因为 \(k\ge1\) 且函数 \(\ln(k+1)\) 单调递增,所以当 \(k=1\) 时,\(\frac{1}{2\ln(k+1)}-1\) 取最大 \(\frac{1}{2\ln2}-1\),显然这个数小于 \(0\),所以有 \(f'(n)<0\),所以 \(f(n)\) 单调递减。

因此我们考虑 \(n=1\) 时,此时 \(f(n)\) 有最大值 \(k\log_{k+1}1-1=-1<0\),所以我们得到 \(f(n)\) 在 \([1,+\infty)\) 上全小于 \(0\)。所以我们得到了 \(k\log_{k+1}n<n\) 恒成立。

所以 Alice 显然可以操作这么多次。

所以当 \(k\le\lfloor\frac{n}{2}\rfloor\) 时 Alice 必胜。

代码:

const int N=2e5;
int n,q,rt,k;
vector<int>v[N];
signed main()
{
	n=read();
	for(int i=1,x,y;i<n;i++)
	{
		x=read(),y=read();
		v[x].push_back(y);
		v[y].push_back(x);
	}
	q=read();
	while(q--)
	{
		rt=read(),k=read();
		if(k>n/2)
		{
			if(k>=v[rt].size())
			{
				puts("A");
			}
			else
			{
				puts("B");
			}
			continue;
		}
		puts("A");
	}
	return 0;
}

T2. 甜筒的染色树

题面:

对于一棵若干个结点的有标号有根树(区分儿子顺序),每个结点可以被染成蓝色、绿色或黄色,一个染色方案是好的,当且仅当:

  • 根结点被染成绿色;

  • 对于蓝色和绿色结点组成的点集 \(S_1\),\(S_1\) 中点两两之间的路径上都没有黄色结点;

  • 对于黄色和绿色结点组成的点集 \(S_2\),\(S_2\) 中点两两之间的路径上都没有蓝色结点。

\(q\) 次询问,每次给你一个整数 \(m\),问有几棵恰有 \(m\) 种好的染色方案的树?答案对 \(10^9+7\) 取模。

没找到原题,只能把题面复制过来了。

显然上面的条件转化后就是一个绿色的连通块挂着几棵颜色全相同的子树(读者自证)。

我们先考虑给定一棵树时有多少种染色方法。

设 \(f_x\) 表示钦定 \(x\) 是绿色时的方案数,那么显然有:

\[f_x=\prod_{v\isin\operatorname{son}(x)}(f_v+2) \]

加二是因为一棵子树可以可以全染蓝色或黄色。

所以我们设 \(g_{x}\) 表示方案数是 \(x\) 时树的个数。但是因为它要区分儿子顺序,所以我们考虑这棵树最右边的子树的大小。

设 \(g_{x,y}\) 表示方案数是 \(x\)、树的大小是 \(y\) 时树的个数,那么有:

\[g_{x,y}=\sum_{i=1}^{y-1}\sum_{j|x\land j>2}g_{j-2,i}\times g_{\frac{x}{j},y-i}\times\binom{y}{i} \]

时间复杂度:\(O(n^3\ln n)\)。

到这里你已经可以直接打表了,不过我们还是要讲一下正解。

你可以观察到 \(f_x\) 的值必然时奇数,所以可以优化 \(\frac{1}{2}\) 常数,同时里面 \(j\) 的枚举也可以跳过偶数,总共优化了 \(\frac{1}{4}\) 常数。

然后你又观察到一棵大小为 \(n\) 的树,它的染色的方案数最少是 \(2n-1\),所以枚举 \(i\) 时我们只需要枚举到 \(\lceil\frac{j}{2}\rceil\),枚举 \(y-i\) 只需要枚举到 \(\lceil\frac{x}{2j}\rceil\)。单次枚举复杂度为 \(O(\frac{j}{2}\times\frac{x}{2j})=O(x)\),此时总复杂度为 \(O(n^2\ln n)\)。

因为常数很小,所以可以跑 \(10^4\)。

代码:

const int N=1e4+6,mod=1e9+7;
int fac[N],inv[N],ans[N],f[N][N];
int qpow(int x,int y)
{
	int z=1;
	while(y)
	{
		if(y&1)
		{
			z=z*x%mod;
		}
		x=x*x%mod;
		y>>=1;
	}
	return z;
}
int C(int x,int y)
{
	return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
signed main()
{
	fac[0]=1;
	for(int i=1;i<N;i++)
	{
		fac[i]=fac[i-1]*i%mod;
	}
	inv[N-1]=qpow(fac[N-1],mod-2);
	for(int i=N-2;i>=0;i--)
	{
		inv[i]=inv[i+1]*(i+1)%mod;
	}
	f[1][1]=1;
	ans[1]=1;
	for(int i=3;i<N;i+=2)
	{
		for(int x=3;x*x<=i;x+=2)
		{
			if(i%x==0)
			{
				for(int k=1;k<=(x+1)/2;k++)
				{
					for(int c=1;c<=(i/x+1)/2;c++)
					{
						f[i][k+c]=(f[i][k+c]+f[x-2][k]*f[i/x][c]%mod*C(k+c,k)%mod)%mod;
					}
				}
				if(x*x!=i)
				{
					x=i/x;
					for(int k=1;k<=(x+1)/2;k++)
					{
						for(int c=1;c<=(i/x+1)/2;c++)
						{
							f[i][k+c]=(f[i][k+c]+f[x-2][k]*f[i/x][c]%mod*C(k+c,k)%mod)%mod;
						}
					}
					x=i/x;
				}
			}
		}
		int x=i;
		for(int k=1;k<=(x+1)/2;k++)
		{
			for(int c=1;c<=(i/x+1)/2;c++)
			{
				f[i][k+c]=(f[i][k+c]+f[x-2][k]*f[i/x][c]%mod*C(k+c,k)%mod)%mod;
			}
		}
		for(int j=1;j<N;j++)
		{
			ans[i]=(ans[i]+f[i][j])%mod;
		}
	}
	int q=read();
	while(q--)
	{
		write(ans[read()]);
		putchar('\n');
	}
	return 0;
} 

T3. Monster-Go / 怪兽 Go

题面

诡异构造题。

我们很容易注意到如果存在 \(S_i,S_j\),满足 \(x\isin(S_i\cap S_j)\) 且 \(\forall k\not=i\land k\not=j,S_k\subseteq(S_i\cup S_j-\{x\})\),那么显然 \(S_i\) 和 \(S_j\) 可以同时结束,此时不成立。

因此我们可以想到一种构造方法:假设我有 \(d\) 个数,我将这 \(d\) 个数的所有大小为 \(12\) 的子集全部取出作为答案,此时必然不存在两个集合满足上面的条件。

我们可以把这个东西扩大一点:假设我有 \(d\) 个集合,每个集合的大小为 \(k\),那么我们需要将这 \(d\) 个集合的所有大小为 \(\frac{12}{k}\) 的子集全部取出作为答案一样成立。此时取出的答案数量必然是 \(\binom{d}{\frac{12}{k}}\)。

所以我们可以考虑按照上面的方法进行构造。但是我们没法保证 \(n\) 一定可以被表示成一个组合数的形式,所以我们可以将多个组合数拼在一起,这样在构造时我们只要不取到相同的数就行了。

代码:

const int N=56;
int id;
pair<int,int>f[N][N];
int C(int x,int y)
{
	int ans=1;
	for(int i=1;i<=y;i++)
	{
		ans=ans*(x-i+1)/i;
		if(ans>(int)(1e9))
		{
			return -1;
		}
	}
	return ans;
}
void init()
{
	for(int i=0;i<N;i++)
	{
		for(int j=0;j<N;j++)
		{
			f[i][j]=make_pair(-1,-1);
		}
	}
	f[0][0]=make_pair(0,0);
	for(int k=1;k<=12;k++)
	{
		if(12%k)
		{
			continue;
		}
		for(int d=1;d*k<=50;d++)
		{
			int c=C(d,12/k);
			if(c==-1)
			{
				continue;
			}
			for(int i=0;i+d*k<=50;i++)
			{
				for(int j=0;j+c<=50;j++)
				{
					if(f[i][j].first==-1)
					{
						continue;
					}
					if(f[i+d*k][j+c].first!=-1)
					{
						continue;
					}
					f[i+d*k][j+c]=make_pair(d,k);
				}
			}
		}
	}
}
void solve(int d,int k)
{
	vector<int>v[N];
	for(int i=0;i<d*k;i++)
	{
		v[i/k].push_back(id++);
	}
	for(int s=0;s<(1<<d);s++)
	{
		if(__builtin_popcount(s)!=12/k)
		{
			continue;
		}
		for(int i=0;i<d;i++)
		{
			if((s>>i)&1)
			{
				write(v[i]);
			}
		}
		putchar('\n');
	}
}
signed main()
{
	init();
	int n=read();
	int x=0,y=0;
	for(int i=0;i<=50;i++)
	{
		if(f[i][n].first!=-1)
		{
			x=i,y=n;
			break;
		}
	}
	while(y)
	{
		int d=f[x][y].first,k=f[x][y].second;
		solve(d,k);
		x-=d*k,y-=C(d,12/k);
	}
	return 0;
}

T4. Zeroing the segment

神秘 Hall 定理转化题。

不会留坑。

posted @ 2026-08-12 21:29  cwxcplh  阅读(6)  评论(0)    收藏  举报