容斥原理
就是全集减去其他不满足的集合的并集,E-(E1∪E2∪E3∪.....∪Ek)=E-E1-E2-E3-...+E1∩E2+E2∩E3..意思是奇数个的符号就是-,偶数个就是+,一般用二进制枚举来求所有的其他∩,一个数中二进制的1的个数就是元素∩的个数,然后看个数在判断符号即可
1.Devu和鲜花
1.假设全集
不满足的情况:
S1相当于先给A1集合选A1+1支花,然后其余的限制跟全集一样做就行就是C(M+N-1-(Ai+1))(N-1)
S1∩S2就是先在A1集合选A1+1,A2集合选A2+1花,然后其余的限制跟全集一样做就行了就是C(M+N-1-(A1+1)-(A2+1))(N-1)
然后用二进制枚举集合就行了,1代表有这个集合,假如1的个数是奇数就-,偶数就+
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=25,mod=1e9+7;
ll n,m;
ll A[N];
int down=1;
int qmi(int a,int k,int p)//快速幂求逆元
{
int res=1;
while(k)
{
if(k&1) res=(ll)res*a%mod;
a=(ll)a*a%mod;
k>>=1;
}
return res;
}
int C(ll a,ll b)//求C(a,b)
{
if(a<b) return 0;
ll up=1;
for(ll i=a;i>a-b;i--) up=(ll)i%mod*up%mod;//分子的数
return (ll)up*down%mod;//分子除分母,相当于乘逆元
}
int main()
{
scanf("%lld%lld",&n,&m);
for(int i=0;i<n;i++) scanf("%lld",&A[i]);
for(int i=1;i<=n-1;i++) down=(ll)i*down%mod;//先预处理出来n-1的阶层
down=qmi(down,mod-2,mod);//求逆元
int res=0;
for(int i=0;i<(1<<n);i++)//二进制枚举
{
ll a=n+m-1,b=n-1;//获取整个的大小
int sign=1;//符号,假如及数个就-,偶数就+
for(int j=0;j<n;j++)//枚举那个集合被选了
if((i>>j)&1)//假如这位是1
{
a-=A[j]+1;//先给这个集合分配a[j]+1个
sign*=-1;//符号变一下
}
res=(res+sign*C(a,b))%mod;
}
cout<<(res+mod)%mod<<endl;
return 0;
}
2.破译密码
莫比乌斯函数,用来解决容斥原理的符号问题
在筛质数的时候求出来
相当于问(x’,y')=1,也即互质的数的个数,可以用欧拉函数求出来,这里用容斥原理来求
用总的个数-有一个公因子的个数+有两个公因子的个数-...+...
最多分成2根号a段数,每一段的数都是相等的
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=50010;
int primes[N],cnt;
bool st[N];
int mobius[N],sum[N];//mobius函数跟他的前缀和
void init(int n)//筛质数
{
mobius[1]=1;
for(int i=2;i<=n;i++)
{
if(!st[i])
{
primes[cnt++]=i;
mobius[i]=-1;//只有一个质因子
}
for(int j=0;primes[j]*i<=n;j++)
{
int t=primes[j]*i;
st[t]=true;
if(i%primes[j]==0)
{
mobius[t]=0;
break;
}
mobius[t]=mobius[i]*-1;
}
}
for(int i=1;i<=n;i++) sum[i]=sum[i-1]+mobius[i];//求前缀和
}
int main()
{
init(N-1);
int T;
scanf("%d",&T);
while(T--)
{
int a,b,d;
scanf("%d%d%d",&a,&b,&d);
a/=d,b/=d;//先映射到a',b'
int n=min(a,b);
ll res=0;
for(int l=1,r;l<=n;l=r+1)//枚举l,r
{
r=min(n,min(a/(a/l),b/(b/l)));//(a/l),(b/l)就是g(x)
res+=(sum[r]-sum[l-1])*(ll)(a/l)*(b/l);
}
printf("%lld\n",res);
}
return 0;
}
概率与数学期望
就是弄出递推公式,然后用记忆化搜索来做,递推公式就是期望公式
1.绿豆蛙的归宿
#include<bits/stdc++.h>
using namespace std;
const int N=100010,M=200010;
int n,m;
int h[N],e[M],ne[M],w[M],idx;
int dout[N];//记录出度,也即连到他的边有多少
double f[N];
void add(int a,int b,int c)
{
e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}
double dp(int u)
{
if(f[u]>=0) return f[u];//假如算过了,直接返回
f[u]=0;
for(int i=h[u];~i;i=ne[i])
{
int j=e[i];
f[u]+=(w[i]+dp(j))/dout[u];//递推公式
}
return f[u];
}
int main()
{
scanf("%d%d",&n,&m);
memset(h,-1,sizeof h);
for(int i=0;i<m;i++)
{
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a,b,c);
dout[a]++;
}
memset(f,-1,sizeof f);//double让他为-1,也即全部都是false,也即没数的意思
printf("%.2lf\n",dp(1));
return 0;
}
2.扑克牌
#include<bits/stdc++.h>
using namespace std;
const int N=14;
const double INF=1e20;
int A,B,C,D;
double f[N][N][N][N][5][5];
double dp(int a,int b,int c,int d,int x,int y)
{
if(a>13||b>13||c>13||d>13) return INF;//假如越界
double &v=f[a][b][c][d][x][y];
if(v>=0) return v;//假如算过了
int as=a+(x==0)+(y==0);//算第一个集合的数个数
int bs=b+(x==1)+(y==1);//算第二个集合的数个数
int cs=c+(x==2)+(y==2);//算第三个集合的数个数
int ds=d+(x==3)+(y==3);//算第四个集合的数个数
if(as>=A&&bs>=B&&cs>=C&&ds>=D) return v=0;//假如找到了这个终点,返回0
int sum=a+b+c+d+(x!=4)+(y!=4);
sum=54-sum;//算剩下牌的个数
if(sum<=0) return v=INF;//假如不够了,直接返回
v=1;//初始化为1,因为是乘,所以初始化为1
if(a<13) v+=(13.0-a)/sum*dp(a+1,b,c,d,x,y);//假如选A集合
if(b<13) v+=(13.0-b)/sum*dp(a,b+1,c,d,x,y);//假如选B集合
if(c<13) v+=(13.0-c)/sum*dp(a,b,c+1,d,x,y);//假如选C集合
if(d<13) v+=(13.0-d)/sum*dp(a,b,c,d+1,x,y);//假如选D集合
if(x==4)//假如小王能选
{
double t=INF;
for(int i=0;i<4;i++) t=min(t,1.0/sum*dp(a,b,c,d,i,y));//枚举选哪个集合
v+=t;
}
if(y==4)//假如大王能选
{
double t=INF;
for(int i=0;i<4;i++) t=min(t,1.0/sum*dp(a,b,c,d,x,i));//枚举选哪个集合
v+=t;
}
return v;
}
int main()
{
cin>>A>>B>>C>>D;
memset(f,-1,sizeof f);//初始化一个不存在的数
double t=dp(0,0,0,0,4,4);//从初始状态开始跑
if(t>INF/2) t=-1;
printf("%.3lf\n",t);
return 0;
}