• 2022牛客多校联赛第十场 题解


    比赛传送门
    作者: fn


    签到

    H题 Wheel of Fortune / 命运之轮

    题目大意
    炉石背景
    给定双方英雄血量,随机释放炎爆(打10)直到一名玩家死亡,求获胜概率。

    考察内容
    数学

    分析
    分类讨论,设对手的血量最多抗 n u m x numx numx 次,自己最多抗 n u m y numy numy 次,
    获胜的概率即对手抗了 n u m x numx numx 次炎爆且自己恰好抗了 0 0 0 次炎爆, 1 1 1 次炎爆,…, n u m y − 1 numy-1 numy1 次炎爆的概率之和。最后一发炎爆留给对手。

    计算每种情况的概率并求和即可。

    #pragma GCC optimize(3)
    #include
    #define ll long long
    #define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
    #define endl '\n'
    using namespace std;
    const int N=1e6+10; // 抗炎爆的次数<=1e6
    const ll mod=998244353;
    
    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,m,x,y,a[10],b[10];
    ll f[N];
    ll po2[N*2];
    
    int main(){ 
    	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    	cin>>x;
    	for(int i=1;i<=7;i++){
    		cin>>a[i];
    	}
    	cin>>y;
    	for(int i=1;i<=7;i++){
    		cin>>b[i];
    	}
    	
    	swap(x,y); // 
    	
    	ll numx=(x+10-1)/10; // 可以抗炎爆的次数 
    	ll numy=(y+10-1)/10;
    
    	ll ans=0;
    	
    	po2[0]=1;
    	for(int i=1;i<=numx+numy;i++){ // 预处理2的幂次 
    		po2[i]=po2[i-1]*2%mod; 
    	}
    	
    	ll sumx=1; // 1*2*...*(numx-1) 
    	for(int i=2;i<=numx-1;i++){
    		sumx=sumx*i%mod;
    	}
    	ll invsumx=power(sumx,mod-2); // sumx的逆元 
    	
    	ll sum=sumx;
    	for(int i=0;i<=numy-1;i++){ // 枚举y被打中的次数 
    		ll cnt=sum*invsumx%mod;  // C(i+numx-1,numx-1)%mod;
    		sum=sum*(i+1+numx-1)%mod;
    		sum=sum*power(i+1,mod-2)%mod; // 除以(i+1)
    		
    		cnt=cnt*power(po2[numx-1+i],mod-2)%mod;
    		
    		ans+=cnt;
    		ans%=mod;
    	}
    	
    	cout<<ans%mod*499122177%mod<<endl; // *(1/2)
    	return 0;
    }
    /*
    30 0 0 0 0 0 0 0
    30 0 0 0 0 0 0 0
    
    20 0 0 0 0 0 0 0
    30 0 0 0 0 0 0 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

    基本题

    F题 Shannon Switching Game? / 香农切换游戏?

    题目大意
    给定一个可以包含重边的无向图,初始在点 s s s ,两个玩家轮流行动,先手每次可以移除一条和所在位置相邻的边, 后手每次可以沿着一条未删除边移动,如果可以移动到 t t t 则后手获胜,否则先手获胜。

    求双方最优策略下的胜者。

    考察内容
    博弈论,bfs

    分析
    终点是一个必胜态。
    如果当前点 x x x 到终点 t t t 有两条边,则无论切掉哪一条都能走到终点 t t t ,所以 x x x 也是必胜态,加入必胜的点的集合。

    如果当前点 y y y 到必胜的点有两条边,则 y y y 也是必胜的点,加入必胜的点的集合。

    从终点 t t t 开始跑bfs,找到所有必胜的点的集合,剩下的就是必败的点。判断 s s s 是否在集合中即可。

    #pragma GCC optimize(3)
    #include
    #define ll long long
    #define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
    #define endl '\n'
    using namespace std;
    const int N=105;
    ll n,m,s,t;
    vector<ll> g[N];
    
    int vis[N]; // 是否在取胜集合中 
    ll num[N]; // 取胜的路径数量 
    
    queue<ll> q;
    
    void bfs(){
    	vis[t]=1; // 进入取胜集合 
    	q.push(t);
    	while(!q.empty()){
    		ll t1=q.front();
    		q.pop();
    		
    		for(auto a1:g[t1]){
    			if(vis[a1])continue;
    			
    			num[a1]++;
    			if(num[a1]>=2){ // 路径数量>=2 
    				q.push(a1);
    				vis[a1]=1;
    			} 
    		} 
    	}	
    }
    
    void init(ll n){ // 初始化 
    	memset(vis,0,sizeof(vis[0])*(n+1));
    	memset(num,0,sizeof(num[0])*(n+1));
    	
    	for(int i=1;i<=n;i++){
    		g[i].clear();
    	}
    }
    
    int main(){ // bfs
    	ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    	int t0; cin>>t0;
    	while(t0--){
    		cin>>n>>m>>s>>t;
    		
    		init(n);
    		
    		for(int i=1;i<=m;i++){
    			ll u,v;
    			cin>>u>>v;
    			g[u].push_back(v);
    			g[v].push_back(u);
    		}
    		
    		bfs();
    		
    		if(!vis[s])cout<<"Cut Player"<<endl; // 不在取胜集合中,先手获胜 
    		else cout<<"Join Player"<<endl; // 后手获胜 
    	}
    	return 0;
    }
    /*
    1
    4 6 1 4
    1 2
    1 3
    2 4
    2 4
    3 4
    3 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

    进阶题

    I题 Yet Another FFT Problem? / 还有另一个FFT问题?

    题目大意
    给定两个序列 a , b a,b a,b ,找出下标 i , j , k , l i,j,k,l i,j,k,l ,使得 a [ i ] + b [ l ] = a [ j ] + b [ k ] a[i]+b[l]=a[j]+b[k] a[i]+b[l]=a[j]+b[k] i ≠ j , k ≠ l i≠j,k≠l i=j,k=l 。若不存在则输出-1。
    0 ≤ a [ i ] , b [ i ] ≤ 1 0 7 0 \leq a[i],b[i] \leq 10^7 0a[i],b[i]107

    考察内容
    鸽笼原理,枚举

    分析
    亲测直接FFT或NTT等复杂度带log的做法过不了。。。(又被标题骗了。。。)

    假设 a , b a,b a,b 中均无重复元素。
    由鸽笼原理, 因为 a [ i ] + b [ l ] ≤ V ≤ 2 ∗ 1 0 7 a[i]+b[l] \leq V \leq 2*10^7 a[i]+b[l]V2107 ,所以遍历不同的元素 O ( V ) O(V) O(V) 次就一定可以找到一组答案。

    a 、 b a、b ab 去重之后进行枚举即可。注意考虑两对相同元素的情况。
    时间复杂度 O ( V ) O(V) O(V)

    #pragma GCC optimize(3)
    #include
    #define ll long long
    #define cer(x) cerr<<(#x)<<" = "<<(x)<<'\n'
    #define endl '\n'
    using namespace std;
    const int N=1e6+5;
    const int MM=2e7+5;
    
    int n,a[N],b[N],m;
    int n1,A[N]; // A为去重后的a数组的下标 
    int nx[MM],ny[MM]; 
    int vis[MM>>1]; // vis[i]记录i在a/b中的下标 
    int ggx=0,ggy; // 记录a中一对相同元素的下标 
    bool ok=0;
    
    int main(){ // 鸽笼原理。复杂度O(V)。AC
    	scanf("%d%d",&n,&m);
    	for(int i=1;i<=n;i++) {
    		scanf("%d",&a[i]);
    		
    		if(vis[a[i]]){ // a[i]之前已经出现过
    			if(ggx==0){ // 以前没发现对子 
    				ggx=vis[a[i]]; // 上一个a[i]的下标 
    				ggy=i; // 这个a[i]的下标 
    			}
    		} 
    		else{ // a[i]第一次出现 
    			A[++n1]=i; // 记录去重后的a数组的下标
    		} 
    		vis[a[i]]=i; // 记录a[i]出现的下标 
    	}
    	for(int i=1;i<=m;i++) { 
    		scanf("%d",&b[i]); // 读入b[i]
    	}
    	
    
    	memset(vis,0,sizeof vis); // 清空vis,接下来vis[i]用来记录i在b中的下标
    	for(int i=1;i<=m;i++) { // 遍历b[i] 
    		if(vis[b[i]]) { // b[i]出现过 
    			if(ggx) { // a中存在"对子"
    				printf("%d %d %d %d\n",ggx,ggy,vis[b[i]],i); // 输出两对相同元素
    				ok=1; // 找到了 
    				break; 
    			}
    			continue;
    		}
    		
    		// vis[b[i]]==0,b[i]第一次出现 
    		vis[b[i]]=i;
    		for(int j=1;j<=n1;j++) { // 枚举a中的元素(已通过A[i]去重) 
    			int u=1e7; // 偏移量 
    			int o=b[i]-a[A[j]]+u; // 计算nx的下标 
    			if(nx[o]){ // 找到一组"碰撞" 
    				printf("%d %d %d %d\n",nx[o],A[j],ny[o],i);
    				ok=1;
    				break;
    			}
    	 	
    			nx[o]=A[j];
    			ny[o]=i;
    		}
    		
    		if(ok) break; // 已经找到,提前退出 
    	}
    	
    	if(!ok) puts("-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
    • 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

    E题 Reviewer Assignment / 审稿人分配

    题目大意
    n n n 个审稿人和 m m m 篇论文,给出每个审稿人能审论文的集合,给每个审稿人安排一篇论文。
    f ( i ) f(i) f(i) 表示被至少 i i i 个审稿人审过的论文数量,要求求出一种分配方案,使得 ( f ( 1 ) , f ( 2 ) , . . . , f ( n ) ) (f(1),f(2),...,f(n)) (f(1),f(2),...,f(n)) 字典序最大。

    1 ≤ n , m ≤ 400 1≤n,m≤400 1n,m400

    考察内容
    二分图匹配,匈牙利算法,最大流,费用流

    分析

    解法一(最小费用最大流):
    建最小费用流图:
    源点向每个审稿人连接容量为 1 1 1 ,花费为 0 0 0 的边;
    每个审稿人向能审的论文连接容量为 1 1 1 ,花费为 0 0 0 的边;
    每篇论文向汇点连接 n n n 条容量为 1 1 1 ,花费为 1 , 2 , . . . , n 1,2,...,n 1,2,...,n 的边。
    然后计算最小费用最大流。

    解法二(二分图匹配):
    注意到要求 f ( 1 ) f(1) f(1) 最大就是求二分图最大匹配,可以用匈牙利算法或最大流解决。
    对于 i = 1 , 2 , . . . , n i=1,2,...,n i=1,2,...,n ,在不改变 f ( 1 ) , f ( 2 ) , . . . f ( i − 1 ) f(1),f(2),...f(i-1) f(1),f(2),...f(i1) 的情况下最大化 f ( i ) f(i) f(i)

    解法二代码:

    #include
    using namespace std;
    bool Mark[405];
    bool Vis[405];
    int Fa[405],Cnt[405];
    vector<int> Edge[405];
    
    bool Hungary(int u) { // 匈牙利算法(增广路算法) 
    	for(int i=0; i<Edge[u].size(); i++) {
    		int v=Edge[u][i];
    		if(!Mark[v]) {
    			Mark[v]=1;
    			if(Fa[v]==-1 || Hungary(Fa[v])) {
    				Fa[v]=u;
    				return true;
    			}
    		}
    	}
    	return false;
    }
    
    int n,m;
    string S;
    int main() { // 匈牙利算法求二分图匹配 
    	memset(Fa,-1,sizeof(Fa));
    	scanf("%d%d",&n,&m);
    	bool flag=1;
    	for(int i=1; i<=n; i++) {
    		cin>>S;
    		bool f=0;
    		for(int j=0; j<m; j++){
    			if(S[j]=='1'){ // 可以看这篇论文 
    				Edge[j+1].push_back(i); // 连边 
    				f=1;
    			}
    		}	
    		flag&=f;
    	}
    	if(!flag) {
    		puts("-1");
    		return 0;
    	}
    
    	int mx=0; // 记录cnt的最小值 
    	bool f=1;
    	while(f) {
    		f=0;
    		for(int i=1; i<=m; i++) { // 枚举每一篇论文 
    			if(Cnt[i]!=mx)continue;
    			
    			memset(Mark,0,sizeof(Mark));
    			if(Hungary(i)){
    				Cnt[i]++;
    				f=1;
    			}
    		}
    		mx++;
    	}
    	
    	for(int i=1; i<=n; i++)
    		printf("%d ",Fa[i]);
    	puts("");
    }
    
    • 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

    2022暑期多校联赛完结撒花!

  • 相关阅读:
    12-1- GAN -简单网络-线性网络
    PWN --- ret2shellcode
    [PAT练级笔记] 06 Basic Level 1008
    【JavaSE】内部类
    CSS3选择器详解 前端开发入门笔记(六)
    原码、反码、补码相关知识
    如何确定 <board_name>名字
    Java.lang.Class类 isAnnotationPresent()方法有什么功能呢?
    基于51单片机步进电机节拍步数正反转LCD1602显示( proteus仿真+程序+原理图+设计报告+讲解视频)
    Python经典书籍有哪些?这份书单送给你_黑马程序员
  • 原文地址:https://blog.csdn.net/weixin_51937688/article/details/126442908