“蔚来杯“2022牛客暑期多校训练营10

H.Wheel of Fortune

题意:
给出A和它的几个随从的生命值以及B和它的几个随从的生命值。
每一次会造成10点的伤害,如果最后B<=0,你赢,否则对手赢。
请输出你赢的概率。

思路:
其实我们发现与随从的生命值毫无关系。
如果A==B,那么双方赢的概率相等,为1/2;
否则,在这里插入图片描述
代码:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod = 998244353;
ll A,B,a[10],b[10];

ll qk(ll a,ll b){
	ll ans=1;
	a%=mod;
	b%=mod;
	while(b){
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
		b%=mod;
	}
	return ans;
}

ll inv(ll a){//逆元 
	return qk(a,mod-2);
}


int main(){
	scanf("%lld",&A);
	for(int i=1;i<=7;i++) scanf("%lld",&a[i]);
	scanf("%lld",&B);
	for(int i=1;i<=7;i++) scanf("%lld",&b[i]);
	if(A==B) {
		printf("499122177\n");
	}
	else{
		ll u=ceil(A/10.0),v=ceil(B/10.0);
		ll p=inv(qk(2,v)),q=1ll; 
		ll t=v,tt=2ll;
		for(ll i=1;i<=u-1;i++){
			ll num=t*inv(tt)%mod;
			t=(t*(v+i)%mod*inv(i+1))%mod;
			tt=tt*2%mod;
			q=(q+num)%mod;
		}
		ll ans=p*q%mod;
		printf("%lld\n",ans);
	}
	return 0;
}

F.Shannon Switching Game?

题意:
给出一个可能有重边的图和起点和终点,Join可以删除边u-v并且从u移到v,Cut只能删边,如果Join曾到过t,Join就赢。否则,Cut赢。

思路:
等价于问是否有一条从s到t的路径,如果将重边的条数成为边权,那么等价于这条路上的每个边权都要>=2。
根据博弈论,一个状态是必胜状态当且仅当它至少有一个后继是必败状态。
将终点t作为必胜态,沿着w>=2的路径走,最后看看w[s]是否>=2,即是否也进入了必胜态集合中。

代码:



#include <bits/stdc++.h>
using namespace std;
const int N = 10010;
int n,m,s,t,w[N];
vector<int> g[N];

void dfs(int u){
	w[u]++; 
	if(w[u]!=2) return;//只能沿着至少有2个必胜态的路走 
    for(auto v:g[u]){
    	dfs(v);
	}
	
}
int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%d%d",&n,&m,&s,&t);
		memset(w,0,sizeof(w));
		for(int i=0;i<=n;i++) g[i].clear();
		for(int i=1;i<=m;i++){
			int u,v;
			scanf("%d%d",&u,&v);
			g[u].push_back(v);
			g[v].push_back(u);
		}
		w[t]=1;//终点为必胜态,因此初始w[t]=2
		dfs(t);//从终点/必胜态开始搜素 
		if(w[s]>=2) puts("Join Player"); 
		else puts("Cut Player");
	}
	return 0;
}

(2)因为无环,所以可以用拓扑排序的逆序来进行,那么每个结点的后继都会被遍历到。
维护一个队列,先将终点t加入队列,每次选择w==2的点加入队列。

#include <bits/stdc++.h>
using namespace std;
const int N = 110;
int n,m,s,t,w[N];
vector<int> g[N];


int main(){
	int T;
	scanf("%d",&T);
	while(T--){
		scanf("%d%d%d%d",&n,&m,&s,&t);
		for(int i=0;i<=n;i++) g[i].clear();
		memset(w,0,sizeof(w));
		for(int i=1;i<=m;i++){
			int u,v;
			scanf("%d%d",&u,&v);
			g[u].push_back(v);
			g[v].push_back(u);
		}
	    w[t]=2;
	    queue<int> q;
	    q.push(t);
	    int x,y;
	    while(!q.empty()){
	    	x=q.front();
	    	q.pop();
	    	for(auto y:g[x]){
	    		w[y]++;
	    		if(w[y]==2) q.push(y);
			}
		}
		if(w[s]>=2) puts("Join Player");
		else puts("Cut Player");
	}
	return 0;
}

Yet Another FFT Problem?

题意:
给出两个数组a和b,问是否存在|a[i]-a[j]|=|b[k]-b[l]|,如果存在,输出这四个下标。

思路:
首先如果原本a数组就存在相等的数字,b数组也存在相等的数字,那么就记录下位置,特判输出。
否则,就将式子变一下形,变成a[i]+b[l]=a[j]+b[k],如果存在这样的,就输出下标。
那么在这种情况下,我们需要先对数组进行排序去重,并且在去重之前就对值和对应的下标进行映射,去重之后,记录一下每次的和是否出现过,如果是第一次出现,就记录一下当前这两个数字对应在原数组中的下标。如果之前就已经出现过,就输出之前记录的位置和当前的位置。

考察;
容斥原理和鸽巢原理

注意:
容易超时,因此用快读进行读入,不能用map进行值和下标的映射,改用数组。

关于排序和去重:
unique的功能是去除相邻的重复元素(只保留一个),其实它并不真正把重复的元素删除,是把重复的元素移到后面去了,然后依然保存到了原数组中,然后 返回去重后最后一个元素的地址,因为unique去除的是相邻的重复元素,所以一般用之前都会要排一下序。

    sort(p.begin(),p.end());//排序
	p.erase(unique(p.begin(),p.end()),p.end());//去重

具体见:unique学习笔记

代码

#include <bits/stdc++.h>
using namespace std;
typedef pair<int,int> PII;
#define fi first
#define se second
#define pb push_back
const int N = 2e7+10;
int n,m,a[N],b[N],mpa1[N],mpa2[N],mpb1[N],mpb2[N],w[N];
int pa1,pa2,pb1,pb2;
vector<int> p,q;

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;
} 

int main(){
	read(n),read(m);
	for(int i=1;i<=n;i++){
		read(a[i]);
		if(mpa1[a[i]]){//如果a数组中出现了相同数字 
			pa1=mpa1[a[i]];//之前这个值的下标 
			pa2=i;//这个值的当前下标 
		}
		else{
			mpa1[a[i]]=i;//将值与下标进行映射 
			p.pb(a[i]);//加入到p中 
		}
	}
	for(int i=1;i<=m;i++){
		read(b[i]);
		if(mpb1[b[i]]){
			pb1=mpb1[b[i]];
			pb2=i;
		}
		else{
			mpb1[b[i]]=i;
			q.pb(b[i]);
		}
	}
	//排序
	sort(p.begin(),p.end());
	sort(q.begin(),q.end());
	//因为unique是去重的,但是它是把重复的元素移到后面去了,没有真正地去掉
	//因此这里把后面的重复元素删除 
	p.erase(unique(p.begin(),p.end()),p.end());
	q.erase(unique(q.begin(),q.end()),q.end());
	//如果原本a和b数组中就存在相等的数字,直接输出位置就好了 
	if(pa1&&pb1){
		printf("%d %d %d %d\n",pa1,pa2,pb1,pb2);
		return 0;
	}
	//否则就看是否存在ai+bl=aj+bk的,将两个数字的和进行一下映射,看是否出现了两次 
	for(int i=0;i<p.size();i++){
		for(int j=0;j<q.size();j++){
			int x=p[i]+q[j];//和 
			if(mpa2[x]){//如果之前就出现过了,那么输出
			//mpa1[p[i]]是最初的,mpa2[x]是当前的 
				printf("%d %d %d %d\n",mpa1[p[i]],mpa2[x],mpb1[q[j]],mpb2[x]);
				return 0;
			}
			//如果之前没有出现过,那么对当前遍历到的元素的值记录一下对应的下标到mpa2和mpb2中 
			mpa2[x]=mpa1[p[i]];
			mpb2[x]=mpb1[q[j]];
		}
	}
	puts("-1");
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值