题意:
给出A和它的几个随从的生命值以及B和它的几个随从的生命值。
每一次会造成10点的伤害,如果最后B<=0,你赢,否则对手赢。
请输出你赢的概率。
思路:
其实我们发现与随从的生命值毫无关系。
如果A==B,那么双方赢的概率相等,为1/2;
否则,
代码:
#include
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;
}
题意:
给出一个可能有重边的图和起点和终点,Join可以删除边u-v并且从u移到v,Cut只能删边,如果Join曾到过t,Join就赢。否则,Cut赢。
思路:
等价于问是否有一条从s到t的路径,如果将重边的条数成为边权,那么等价于这条路上的每个边权都要>=2。
根据博弈论,一个状态是必胜状态当且仅当它至少有一个后继是必败状态。
将终点t作为必胜态,沿着w>=2的路径走,最后看看w[s]是否>=2,即是否也进入了必胜态集合中。
代码:
#include
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
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;
}
题意:
给出两个数组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
using namespace std;
typedef pair 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 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