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


    比赛传送门
    作者: fn


    签到

    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
    
    */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    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; // 往下移 
    				}
    			}
    		}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    完整代码:

    #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即可 
    */ 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69

    基本题

    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
    
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78

    进阶题

    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


  • 相关阅读:
    利用flask-socketio实现前后端实时通信的功能
    海思和Sigmastar ISP开发异同点
    【Java集合类】之 HashSet以及底层逻辑分析
    如何选择在线平台?
    CSS特殊学习网址
    SystemVerilog Assertions应用指南 第一章(1.23章节 “intersect”运算符)
    【配置】如何在打包Spring Boot项目时按需使用日常、测试、预发、正式环境的配置文件
    Nwafu-OJ-1503 Problem 6 2019阶段1考试 题目5
    Linux命令--expect spawn的用法(实现人机交互自动化操作)
    react实战系列 —— 起步(mockjs、第一个模块、docusaurus)
  • 原文地址:https://blog.csdn.net/weixin_51937688/article/details/126411479