「同时出现」与「同时不出现」,还有「恰好出现一次」
「THUWC 2017」随机二分图
题目叙述
一个两边都有 \(n\) 个点的二分图,一共有三类种边组:
- 这种边组一共只有一条边。这条边有 50% 的概率出现。
- 这种边组有两条边,有 50% 的概率同时出现,50% 的概率同时不出现。
- 这种边组有两条边,有 50% 的概率第一条边出现而第二条边不出现,50%的概率反过来。
题解
考虑一个暴力,枚举每个完美匹配然后计算这个完美匹配出现的概率。
然后观察到,这是一种同时出现和同时不出现还有这两条边之中恰好一条出现的问题。
比较一下,如果把所有边都当成 1 类边,和剩下两种的区别只有在同时选上的时候会出现问题。
所以考虑增加一些特殊边组,表示这两条边如果同时选择,概率会增加 p 。
这样就相当于 2 类边两条边同时出现增加 25% 的概率,3 类边减少 25% 的概率。
直接 dp 转移即可,记得使用 lowbit 转移,否则会算重。
朴素动态规划的问题在于,在添加一条边的时候需要在状态中记录前面所有边,这是不好的。但是不是很容易想到搞一个边组这样。
总结
- 比较能平凡的和特殊的问题之间的区别,不一定要思考怎么转化,因为不见得是完全转化出来的。
- 「同时出现」与「同时不出现」,还有「恰好出现一次」,可以考虑用这种神秘方式做掉。
代码
#include <cstdio>
#include <iostream>
#include <unordered_map>
#include <cassert>
#define macro_expand(x) #x
#define print_macro(x) printf("%s\n",macro_expand(x))
#define FOR(i,l,r) for(int i=(l),i##ADJK=(r);i<=i##ADJK;++i)
#define ROF(i,r,l) for(int i=(r),i##ADJK=(l);i>=i##ADJK;--i)
using namespace std;
typedef long long LL;
const int MN=20,Mod=1e9+7;
int ad(int x,int y){return ((x+y)>=Mod)?(x+y-Mod):(x+y);}
int dc(int x,int y){return ((x-y)<0)?(x-y+Mod):(x-y);}
int ml(int x,int y){return (LL)x*y%Mod;}
int add(int &x,int y){return ((x+=y)>=Mod)?(x-=Mod):x;}
int dec(int &x,int y){return ((x-=y)<0)?(x+=Mod):x;}
int N,M;
struct E{
int s,p,nxt;
E():s(0),p(0),nxt(0){}
E(int _s,int _p,int _nxt):s(_s),p(_p),nxt(_nxt){}
}edge[MN*MN];
int head[MN],tote;
void AddE(int u,int s,int p){
// cerr<<"u:"<<u<<" s:"<<s<<" p:"<<p<<endl;
edge[++tote]=E(s,p,head[u]);
head[u]=tote;
}
int inv2,inv4;
unordered_map<int,int> f;
int lowpos(int s){
FOR(i,0,N-1)if((s>>i)&1)return i;
assert(1);
return -1;
}
int dp(int s){
if(s==0)return 1;
auto tmp=f.find(s);
if(tmp!=f.end())return tmp->second;
int u=lowpos(s)+1,ret=0;
for(int p=head[u];p;p=edge[p].nxt)
if((s&edge[p].s)==edge[p].s)
add(ret,ml(edge[p].p,dp(s^edge[p].s)));
f[s]=ret;
return ret;
}
int main(){
inv2=(Mod+1)/2;
inv4=(Mod+1)/4;
scanf("%d%d",&N,&M);
FOR(i,1,M){
int t=0,a1=0,b1=0,a2=0,b2=0;
scanf("%d%d%d",&t,&a1,&b1);
if(t==1||t==2)scanf("%d%d",&a2,&b2);
b1+=N,b2+=N;
int a=(1<<(a1-1)),b=(1<<(b1-1)),c=(1<<(a2-1)),d=(1<<(b2-1));
if(t==0){
AddE(a1,a|b,inv2);
}else if(t==1){
AddE(a1,a|b,inv2);
AddE(a2,c|d,inv2);
if(a1==a2||b1==b2)continue;
AddE(a1,a|b|c|d,inv4);
AddE(a2,a|b|c|d,inv4);
}else{
AddE(a1,a|b,inv2);
AddE(a2,c|d,inv2);
if(a1==a2||b1==b2)continue;
AddE(a1,a|b|c|d,dc(0,inv4));
AddE(a2,a|b|c|d,dc(0,inv4));
}
}
printf("%d\n",ml(dp((1<<(2*N))-1),(1<<N)));
return 0;
}
冠军BP
题目叙述
一共有 \(n\) 个英雄,每个英雄有一个权值 \(x\) ,还有若干 combo 和 counter 关系。
x 和 y 构成 combo 的意思是两个英雄如果都选了就会多产生 \(z\) 的贡献。
x 和 y 构成 counter 的意思是两个英雄如果其中一个选了另一个没选,就会多产生 \(z\) 的贡献。
两个人按照第一个人先选一个,后面两个人每两次换一次人。每个人会尽量让自己的得分尽量大,问先手最多比后手多得多少分。
题解
姑且先不考虑 combo 和 counter 的关系。
考虑 combo 就给权值设为 \(x+z/2\) ,另一边设为 \(y+z/2\) ,这样如果分到一组和为 \(z\) ,分到两组差为 0 。
counter 类似构造即可。
总结
- 好像和上一个题不是有多类似。。但也是先当成平凡的情况再调整。
代码
// Problem: D. 冠军BP
// Contest: UOJ - 2021 CSP/NOIP挑战赛 Contest09
// URL: http://noi.ac/contest/596/problem/2533
// Memory Limit: 1024 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
//And in that light,I find deliverance.
#include<bits/stdc++.h>
// #pragma GCC optimize("Ofast")
// #pragma GCC optimize("unroll-loops")
// #pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2,tune=native")
using namespace std;
#define int long long
inline int read(){
int s=0,w=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9') s=s*10+ch-'0',ch=getchar();
return s*w;
}
int a[1000003];
signed main()
{
int n=read(),m1=read(),m2=read();
for(int i=1; i<=n; ++i) a[i]=read()<<1;
for(int i=1,x,y,z; i<=m1; ++i)
x=read(),y=read(),z=read(),a[x]+=z,a[y]+=z;
for(int i=1,x,y,z; i<=m2; ++i)
x=read(),y=read(),z=read(),a[x]+=z,a[y]-=z;
sort(a+1,a+n+1);
int S=0;
for(int i=1; i<=n; ++i) if(((n-i)&3)==1||((n-i)&3)==2) S-=a[i]; else S+=a[i];
printf("%lld\n",S>>1);
return 0;
}

浙公网安备 33010602011771号