2022杭电多校联赛第十场 题解

比赛传送门
作者: fn

目录

  • 签到题
    • 1007题 Even Tree Split / 分割偶数树
    • 1003题 Wavy Tree / 波浪树
  • 基本题
    • 1009题 Painting Game / 绘画游戏
    • 1004题 Average Replacement / 平均置换
  • 进阶题
    • 1001题 Winner Prediction / 预测赢家


签到题

1007题 Even Tree Split / 分割偶数树

题目大意
给定一个具有 n n n 个节点的树,保证 n n n 是偶数。

您将删除一些边(至少1条),并且必须让每个剩余的连通块具有偶数个顶点。

计算删除的方法数,模998244353。

考察内容
dfs,复杂度优化

分析
除了根节点,每一个偶数大小的子树可以带来一条可以切的边。
预处理子树大小,顺便统计偶数大小子树的个数 c n t cnt cnt
答案为 2 c n t − 1 − 1 2^{cnt-1}-1 2cnt11

用O3和快读优化一下。

#pragma GCC optimize(3)
#include
#define ll long long
#define int long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e5+3;
const ll mod=998244353;
ll read(ll &n){
	char ch=' '; ll q=0,w=1;
	for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
	if(ch=='-')w=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar())q=q*10+ch-48;
	n=q*w; return n;
}
ll power(ll a,ll b){ // a^b%mod
	ll c=1;
	for(;b;b>>=1){
		if(b&1)c=c*a%mod;
		a=a*a%mod;
	}
	return c;
}
ll n;

vector<ll> g[N];
ll sz[N]; // 子树大小 
ll cnt=0;

ll dfs(ll x,ll fa){ // 预处理子树大小,并统计偶数大小子树的个数
	sz[x]=1;
	for(auto a1:g[x]){ // 枚举子树 
		if(a1==fa)continue; // 跳过父亲结点 
		
		sz[x]+=dfs(a1,x);
	} 
	
	if(sz[x]%2==0){ //  子树大小为偶数 
		cnt++; // 统计偶数大小子树的个数 
	}
	return sz[x];
} 

void init(ll n){
	
	memset(sz,0,sizeof(sz[0])*(n+1));
	for(int i=0;i<=n;i++){
		g[i].clear();
	}
}

signed main(){ 
	int t; read(t);
	while(t--){ // t<=30
		read(n);
		init(n); // 初始化 

		ll u,v;
		for(int i=1;i<=n-1;i++){ // 读入一棵树 
			read(u); read(v);
			g[u].push_back(v);
			g[v].push_back(u);
		}
		
		cnt=0; 
		ll temp=dfs(1,0); // 预处理子树大小,并统计偶数大小子树的个数 
		cnt--; // 可以删的点的数量为cnt-1 
		
		ll ans=(power(2,cnt)%mod+mod-1)%mod; 
		cout<<ans<<endl;
	}
	return 0;
}
/*
1
4
1 2
1 3
1 4

*/ 

1003题 Wavy Tree / 波浪树

题目大意
给定一个长度为 n n n 的整数数组 b b b
每次操作可以使 b b b 中的一个元素+1或-1,希望使阵列呈"波浪状"。

计算使序列波浪状的最小操作数。

考察内容
动态规划,贪心

分析
线性dp。
不难发现,要变成波浪形,要么先上升再下降,要么先下降再上升。在两个答案中取最小值即可。

状态:
f [ N ] : f[N] : f[N]: 先上升再下降,前 i i i 个的答案
g [ N ] : g[N] : g[N]: 先下降再上升,前 i i i 个的答案

边界:
f [ 1 ] = 0 f[1]=0 f[1]=0
g [ 1 ] = 0 g[1]=0 g[1]=0
只有1个时不需要操作

转移:
转移的同时贪心一下,调整当前数字的大小。

		for(int i=2;i<=n;i++){
			f[i]=f[i-1];
			g[i]=g[i-1];
			
			if(i%2==1){ // 奇数个,f下降,g上升 
				if(a[i]>=a[i-1]){
					f[i]+=a[i]-a[i-1]+1;
					a[i]=a[i-1]-1; // 往下移 
				}
				if(b[i]<=b[i-1]){
					 g[i]+=b[i-1]-b[i]+1;
					 b[i]=b[i-1]+1;
				}
			}
			else{ // 偶数个,f上升,g下降 
				if(a[i]<=a[i-1]){
					f[i]+=a[i-1]-a[i]+1;
					a[i]=a[i-1]+1;
				}
				if(b[i]>=b[i-1]){
					 g[i]+=b[i]-b[i-1]+1;
					 b[i]=b[i-1]-1; // 往下移 
				}
			}
		}

完整代码:

#include
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
const int N=1e6+10;
ll n,m,a[N];
ll b[N]; 
ll f[N]; // 先上升再下降,前i个的答案 
ll g[N]; // 先下降再上升,前i个的答案

int main(){ // dp
	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
	int t; cin>>t;
	while(t--){
		cin>>n;
		for(int i=1;i<=n;i++){
			cin>>b[i];
			a[i]=b[i];
		}
		
		f[1]=0; // 只有1个时不需要操作
		g[1]=0;
		
		for(int i=2;i<=n;i++){
			f[i]=f[i-1];
			g[i]=g[i-1];
			
			if(i%2==1){ // 奇数个,f下降,g上升 
				if(a[i]>=a[i-1]){
					f[i]+=a[i]-a[i-1]+1;
					a[i]=a[i-1]-1; // 往下移 
				}
				if(b[i]<=b[i-1]){
					 g[i]+=b[i-1]-b[i]+1;
					 b[i]=b[i-1]+1;
				}
			}
			else{ // 偶数个,f上升,g下降 
				if(a[i]<=a[i-1]){
					f[i]+=a[i-1]-a[i]+1;
					a[i]=a[i-1]+1;
				}
				if(b[i]>=b[i-1]){
					 g[i]+=b[i]-b[i-1]+1;
					 b[i]=b[i-1]-1; // 往下移 
				}
			}
		}
		cout<<min(f[n],g[n])<<endl;
	}
	return 0;
}
/*
1
6
1 1 4 5 1 4

4


1
3
1 2 100

输出:
2
// 1->3, 3 2 100即可 
*/ 

基本题

1009题 Painting Game / 绘画游戏

题目大意
有一个纸条,分为 n n n 个空白网格。
Alice和Bob轮流行动。 每次将剩余的一个空白网格涂成黑色,同时保证没有两个黑色网格相邻。

当一名玩家无法绘制任何网格时,游戏结束。艾丽丝想要绘制格子最少,而鲍勃想要绘制格子最多。
给定 n n n 和先手方,找出两人最佳发挥时的最终得分。

考察内容
博弈论

分析

Alice 的一种最优策略是:选某个连续段的左数第二个格子涂黑;
Bob 的一种最优策略是:选某个连续段的左数第三个格子涂黑。

所以可以推得,每7个格子必定有3个被涂黑。剩下的几个格子分类讨论一下即可。

#pragma GCC optimize(3)
#include
#define ll long long
#define int long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
int t,n;
string s;
int sum = 0;
signed main(){ 
	cin>>t;
	while(t--){
		cin>>n;
		cin>>s;
		sum = 0;
		int num1 = n/7; 
		sum+=num1*3;
		
		int num2 = n%7;
		if(s=="Alice"){
			if(num2>=6) sum+=3;
			else if(num2>=4) sum+=2;
			else if(num2>=1) sum++;
		}
		else{ // Bob先手 
			if(num2>=5) sum+=3;
			else if(num2>=3)sum+=2;
			else if(num2>=1)sum++;
		}
		cout<<sum<<endl;
	}
}
/*
1
5 Bob

*/

1004题 Average Replacement / 平均置换

题目大意
n n n 人,其中有 m m m 对朋友。目前,每个人都在帽子上写一个整数。
每一轮游戏中,每个人都用自己和所有朋友的平均号码替换帽子上的号码。

请注意,数字可能会变成非整数。
可以证明,通过玩越来越多的游戏,每个数字最终都收敛到某个值。
给定写在帽子上的初始数字,计算这些最终值。

考察内容
并查集,找规律,复杂度优化

分析
先写个暴力找规律。

规律:

  1. 每一个连通块中的答案是一样的。
  2. 答案是 ∑ a i ∗ ( g [ i ] . s i z e ( ) + 1 ) / ∑ ( g [ i ] . s i z e ( ) + 1 ) ∑a_i*(g[i].size()+1)/∑(g[i].size()+1) ai(g[i].size()+1)/(g[i].size()+1)

用O3和快读/scanf优化一下。

#pragma GCC optimize(3)
#include
#define ll long long
#define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
#define endl '\n'
using namespace std;
int read(int &n){
	char ch=' '; int q=0,w=1;
	for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
	if(ch=='-')w=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar())q=q*10+ch-48;
	n=q*w; return n;
} 
ll read(ll &n){
	char ch=' '; ll q=0,w=1;
	for(;(ch!='-')&&((ch<'0')||(ch>'9'));ch=getchar());
	if(ch=='-')w=-1,ch=getchar();
	for(;ch>='0'&&ch<='9';ch=getchar())q=q*10+ch-48;
	n=q*w; return n;
}
const int N=1e5+5;
ll n,m;
ll a[N];
ll sum[N]; // 
ll sz[N]; // 边数+1求和 
int fa[N];
vector<ll> g[N]; // 记录无向图 

void init(ll n) {	// 初始化
	for (int i = 0; i <= n; i++){
		g[i].clear(); // 清空g (勿忘!)
		fa[i]=i;
		sum[i]=0;	
		sz[i]=0;
	}
}
int get(int x) {	// 查找节点集合
	if(x==fa[x])return x;
	return fa[x]=get(fa[x]); // 路径压缩 
}
void unionNode(int x, int y) {	// 合并节点
	fa[get(x)] = get(y); 
}

ll sum0[N];

int main(){ // AC
	int t; read(t);
	while(t--){
		read(n); read(m);
		init(n); // 初始化 
		
		for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
		
		int u,v;
		for(int i=1;i<=m;i++){
			scanf("%d%d",&u,&v);
			g[u].push_back(v);
			g[v].push_back(u);
			
			unionNode(u,v); // 合并 
		}
		
		for(int i=1;i<=n;i++){
			ll d1=g[i].size()+1;
			
			int x=get(i);
			sum[x]+=a[i]*d1; 
			sz[x] += d1;
		}
	
		for(int i=1;i<=n;i++){
			int x=get(i);
			printf("%.6f\n",sum[x]*1.0/sz[x]);
		}
	}
	return 0;
}

进阶题

1001题 Winner Prediction / 预测赢家

题目大意
举行由 n n n 名参赛者组成的锦标赛。玩家编号从 1 1 1 n n n 。每场比赛都在两名参赛者之间进行,没有平局。赢得最多比赛的参与者赢得整个锦标赛;如果有多个参赛者在第一名并列,他们都会赢得比赛。

在当前状态下,一些比赛已经结束,而其他比赛尚未开始。您将获得所有已结束匹配的结果。
确定玩家1是否有可能赢得比赛。

考察内容
网络流,最大流

分析
先让 1 1 1 号选手赢下所有和他有关的比赛。

设此时选手 i i i 赢了 a i a_i ai 场比赛。
如果存在某个 a i > a 1 a_i>a_1 ai>a1 1 1 1 号选手不可能成为冠军。否则选手 i i i 至多还能再赢 a i − a i a_i-a_i aiai 场比赛。

建立一张网络流图:每场未进行的比赛在图中用一个点表示,源点向它连容量为 1 1 1 的边,它向它的两个参赛选手的对应点各自连容量为 1 1 1 的边;选手 i i i 的对应点向汇点连容量为 a i − a i a_i-a_i aiai 的边。

计算该图最大流,若源点出发的边满流则答案为 YES ,否则为 NO 。

最大流模板题:洛谷P3376


你可能感兴趣的:(多校联赛,算法,icpc,acm竞赛,算法竞赛,c++)