20211105 校内练习赛题解

20211105 校内练习赛题解

T1「牛客CSP2019-S赛前集训营1」仓鼠的石子游戏

画图发现,若 \(a_i\)\(\ge3\) 的奇数,那么先手必败。若 \(a_i\) 为偶数,那么先手在本轮不可能赢,最后仍必败。而特例是 \(a_i=1\) 的情况,这时先手必胜。因此只要统计 \(ai=1\) 的圈的个数。若有奇数个,那么先手必胜,否则必败。

int ans=0;
F(i,1,n) {
	a[i]=read();
	ans^=(a[i]==1);
}
puts(ans?"rabbit":"hamster");

T2「牛客CSP2019-S赛前集训营1」乃爱与城市拥挤程度

最暴力的就是 \(n^2\) 统计每个点前 \(k\) 层的贡献。树上前缀和就好了。由于前 \(70\%\) 数据随机生成,树长的比较平衡,每个点 \(k\) 层内期望点数约为 \(2^k-1\) 个。复杂度为 \(\Omega (n\times (2^k-1))\)。而对于退化成链的第 \(10\) 个点,复杂度为 \(\mathcal{O}(n\times 2k)\),显然也能通过。所以暴力就能拿 \(80\)好耶!

观察发现 \(1\le k \le 10\),明显的树形 dp。记 \(dp_{i,j}\)\(i\) 点前 \(j\) 层拥挤程度的乘积,\(cnt_{i,j}\)\(i\) 点前 \(j\) 层的点数之和。我们用 up and down 的方法处理本题。即先从下往上 dp 一次,然后再从上往下 dp 一次推出答案。

从下往上 dp 比较简单,这里略过,直接来看第二次 dp。

\(x\) 转移给 \(y\) 时。有:

\[cnt_{y,j+1}=\begin{cases}cnt_{y,j+1}+cnt_{x,j}&j=0\\cnt_{y,j+1}+cnt_{x,j}-cnt_{y,j-1}&j>0\end{cases} \]

即距离他父亲 \(\le j\) 的个数,再减去 \(y\) 的子树内重复计算的(这一部分就是在 \(x\) 的子树内,但在 \(y\) 的子树外的)。然后加上子树内的就是距 \(y\)\(\le j+1\) 的点的个数。

对于 \(dp_{y,j+1}\) 有:

\[dp_{y,j+1}=\frac{dp'_{y,j+1}}{cnt'_{y,j+1}}\times\frac{dp_{x,j}\times (cnt_{x,j}-cnt_{y,j-1})}{cnt_{x,j}\times dp'_{y,j-1}}\times cnt_{y,j} \]

由于转移顺序问题,此处 \(cnt'\)\(dp'\) 指本次更新前的 \(cnt\)\(dp\) 的值。此处注意 \(j=0\) 时的边界即可。

首先计算 \(\frac{dp'_{y,j+1}}{cnt'_{y,j+1}}\),目的是先消去第 \(j\) 层的影响,最后再把新的乘回来即可。然后看第二项。\(\frac{dp_{x,j}}{cnt_{x,j}}\) 的含义是,以 \(x\) 为第一层,其前 \(2\sim j\) 层各结点被访问次数的乘积(即不包含 \(x\) 本身)。由此,\(\frac{dp_{x,j}}{cnt_{x,j}\times dp'_{y,j-1}}\) 的含义就是在 \(x\) 子树内(不包含 \(x\)),但不包含 \(y\) 及其子树的结点的被访问次数的乘积。这时只需加上 \(x\)\(y\) 的贡献。显然 \(x\) 的被访问次数是 \((cnt_{x,j}-cnt_{y,j-1})\)。于是我们就通过 \(x\) 算出了 \(y\) 的全部答案。

还是放一下代码吧。

#include <bits/stdc++.h>
#define ll long long
#define Min(x, y) ((x) > (y) and ((x) = (y)))
#define Max(x, y) ((x) < (y) and ((x) = (y)))
#define F(i, a, b) for (int i = (a); i <= (b); ++i)
#define PF(i, a, b) for (int i = (a); i >= (b); --i)
#define For(i, x) for (int i = head[(x)]; i; i = net[(i)])
using namespace std;
bool beginning;
inline int read();
void print(int x);
const int N=1e5+5,E=N<<1,mod=1e9+7;
int n,k;
int ver[E],net[E],head[N],tot;
inline void add(int x,int y) {
	ver[++tot]=y,net[tot]=head[x],head[x]=tot;
}
int p1[N],p2[N];
inline void OutAns() {
	F(i,1,n) {
		print(p1[i]);
		if(i!=n)putchar(' ');
	}
	putchar('\n');
	F(i,1,n) {
		print(p2[i]);
		if(i!=n)putchar(' ');
	}
}
namespace sub1 {
	int sum[N],num;
	ll ans;
	void dfs(int x,int fa,int d) {
		++sum[x],++num;
		if(!d)return;
		For(i,x) {
			if(ver[i]==fa)continue;
			dfs(ver[i],x,d-1);
		}
		For(i,x) {
			if(ver[i]==fa)continue;
			sum[x]+=sum[ver[i]];
		}
		ans=ans*sum[x]%mod;
	}
	void main() {
		F(i,1,n) {
			memset(sum,0,sizeof(sum));
			ans=1,num=0;
			dfs(i,0,k);
			p2[i]=ans,p1[i]=num;
		}
		OutAns();
	}
}
namespace sub2 {
	int son[N];
	void dfs(int x,int fa) {
		For(i,x) {
			int y=ver[i];
			if(y==fa)continue;
			dfs(y,x);
			++son[x];
		}
	}
	void main() {
		dfs(1,0);
		p1[1]=son[1]+1;
		F(i,2,n)p1[i]=son[i]+2;
		F(i,1,n)p2[i]=p1[i];
		OutAns();
	}
}
namespace sub3 {
	int cnt[N][15];
	ll dp[N][15];
	void dfs(int x,int fa) {
		F(i,0,k)dp[x][i]=cnt[x][i]=1;
		For(i,x) {
			int y=ver[i];
			if(y==fa)continue;
			dfs(y,x);
			F(j,1,k) {
				cnt[x][j]+=cnt[y][j-1];
				dp[x][j]=dp[x][j]*dp[y][j-1]%mod;
			}
		}
		F(i,0,k)dp[x][i]=dp[x][i]*cnt[x][i]%mod;
	}
	inline ll pw(ll x,int y=mod-2) {
		ll ans=1;
		while(y) {
			if(y&1)ans=ans*x%mod;
			x=x*x%mod,y>>=1;
		}
		return ans;
	}
	int t1[N],t2[N];
	void dfs2(int x,int fa) {
		int p,d;
		For(i,x) {
			int y=ver[i];
			if(y==fa)continue;
			F(j,0,k)t1[j]=dp[y][j],t2[j]=cnt[y][j];
			PF(j,k-1,0) {
				int d=cnt[x][j]-(j?cnt[y][j-1]:0);
				
				cnt[y][j+1]+=d;
				
				dp[y][j+1]=dp[y][j+1]*pw(t2[j+1])%mod;
				dp[y][j+1]=dp[y][j+1]*dp[x][j]%mod*d%mod*pw(cnt[x][j])%mod*(j?pw(t1[j-1]):1)%mod;
				dp[y][j+1]=dp[y][j+1]*cnt[y][j+1]%mod;
				
			}
			dfs2(y,x);
		}
	}
	void main() {
		dfs(1,0);
		dfs2(1,0);
		F(i,1,n) {
			print(cnt[i][k]);
			if(i!=n)putchar(' ');
		}
		putchar('\n');
		F(i,1,n) {
			print(dp[i][k]);
			if(i!=n)putchar(' ');
		}
	}
}
bool ending;
int main() {
	cerr<<1.0*(&beginning-&ending)/1024/1024<<"MB\n----------\n";
	freopen("city.in","r",stdin);
	freopen("city.out","w",stdout);
	n=read(),k=read();
	int x,y;
	F(i,2,n) {
		x=read(),y=read();
		add(x,y),add(y,x);
	}
	if(n<=1000)sub1::main();
	else if(k==1)sub2::main();
	else sub3::main();
	return 0;
}
inline int read() {
	int x = 0;
	bool f = 1;
	char c = getchar();
	while (!isdigit(c)) {
		f = c ^ 45;
		c = getchar();
	}
	while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
	return f ? x : -x;
}
void print(int x) {
	if(x>9)print(x/10);
	putchar((x%10)^48);
}


T3「牛客CSP2019-S赛前集训营1」小w的魔术扑克

一张牌的正反面是不能同时选的,于是可以把数字当做点,在正反面的数字间连边。然后二分图网络流什么的随便乱搞。然后加上测试点 \(4\) 的连续段,预计能得 \(40\) 分。

对于满分还是得观察性质。上面连的一条边表示边两端的数字不能同时选,且一个点(代表一个数字)只能选一次。我们发现如果一个连通块是树形的,那么它无论如何也不能取到所有的点。考虑最简单的树的情形如下。

1 2
2 3
3 4

手模发现必定有一个点选不到。此时若增加一条边,那么必定可以通过某种方式取到所有的点。所以我们只需要对树进行特殊处理即可。

细节看代码:

#include <bits/stdc++.h>
#define Min(x, y) ((x) > (y) and ((x) = (y)))
#define Max(x, y) ((x) < (y) and ((x) = (y)))
#define F(i, a, b) for (int i = (a); i <= (b); ++i)
#define PF(i, a, b) for (int i = (a); i >= (b); --i)
#define For(i, x) for (int i = head[(x)]; i; i = net[(i)])
using namespace std;
bool beginning;
inline int read();
const int N=1e5+5,E=N<<1;
int n,k,Q,d[N],p[N];
int ver[E],net[E],head[N],tot;
inline void add(int x,int y) {
	ver[++tot]=y,net[tot]=head[x],head[x]=tot;
}
bool mk[N];
int Mx,Mi,Node,Edge;
void dfs(int x) {
	if(mk[x])return;
	Min(Mi,x),Max(Mx,x);
	mk[x]=1;
	++Node;
	For(i,x)++Edge,dfs(ver[i]);
}
bool ending;
int main() {
	cerr<<1.0*(&beginning-&ending)/1024/1024<<"MB\n----------\n";
	freopen("card.in","r",stdin);
	freopen("card.out","w",stdout);
	n=read(),k=read();
	int x,y;
	F(i,1,k) {
		x=read(),y=read();
		add(x,y),add(y,x);
		++d[x],++d[y];
	}
	memset(p,0x3f,sizeof(p));

	F(i,1,n)if(!d[i])p[i]=i;

	F(i,1,n) {
		if(mk[i])continue;
		Edge=Node=Mx=0;
		Mi=n+1;
		dfs(i);
		if(Node>Edge/2)p[Mi]=Mx;
	}
	PF(i,n-1,1)Min(p[i],p[i+1]);
	Q=read();
	while(Q--) {
		x=read(),y=read();
		puts(p[x]<=y?"No":"Yes");
	}
	return 0;
}
inline int read() {
	int x = 0;
	bool f = 1;
	char c = getchar();
	while (!isdigit(c)) {
		f = c ^ 45;
		c = getchar();
	}
	while (isdigit(c)) x = (x << 3) + (x << 1) + (c ^ 48), c = getchar();
	return f ? x : -x;
}

T4打拳

(先留个坑)

posted @ 2021-11-07 16:42  Maplisky  阅读(42)  评论(0编辑  收藏  举报