F
對於一張比賽圖,經過縮點,會得到dag,且它一定是transitive的,因此我們能直接把比賽圖縮成一個有向鏈。鏈頭作爲一個強連通分量,裏面的所有點都是勝利的
定義F(win)表示win集合作爲贏家的概率,我們有
顯然win集合內的點構成一個強連通分量,並作爲鏈頭。win集合內的點一定向集合外的每個點連邊
考慮如何求解F(win)
我們定義H(win)表示在win集合內的點構成的子圖中,win集合成爲一個強連通分量的概率。
容易發現這個子圖仍然滿足比賽圖的性質!
考慮去掉win不是一個強連通分量的情況,那麼一定存在win的非空真子集sub,sub是子圖鏈頭,此時 sub內的所有點 向 win/sub內的所有點 連邊。我們得到
其中\(G(x,y)\)表示x集合向y集合的點直接連邊的概率
求出的H可以推F了,win想每個集合外的點連邊
接下來考慮如何求出G
原題n=14,可以接受\(O(n3^{n})\)的做法,我們預處理出每個點連向一個集合的概率\(f(i,s)\),那麼每次調用G的時間是\(O(n)\)
題解提供了一個\(O(3^{n})\)的方法,讓每次調用G是\(O(1)\)的
首先是預處理每個點連向集合的概率,我們處理出每個數最高位的1的位置,就可以在\(O(n2^{n})\)遞推出\(f(i,s)\)
考慮把n分成大小不超過1的兩個集合left,right
現在有一個詢問\(G(x,y)\),x,y分別在left和right集合內存在一部分lx,ly,rx,ry,答案是
\(G(x,y)=G(lx,ly)G(lx,ry)G(rx,ly)G(rx,ry)\)
這四部分貢獻可以分別預處理,由於left和right的大小隻有原來的一半,每個數組處理的時間只有\(O(n2^{n})\)
tips:經可可提醒,這個優化套路在[ctsc2017]吉夫特,有機會去做一下
#include<bits/stdc++.h>
using namespace std;
#define r(x) read(x)
#define gc c=getchar()
#define ll long long
#define ffl fflush(stdout)
#define it set<string>::iterator
const int N1=(1<<14)+5, p=1e9+7, maxn=2e6;
const int M1=(1<<7)+5;
const ll inf=0x3f3f3f3f3f3f3fll;
template<typename T>
inline void read(T &x){
x=0;T k=1;char gc;
while(!isdigit(c)){if(c=='-')k=-1;gc;}
while(isdigit(c)){x=x*10+c-'0';gc;}x*=k;
}
int n,m,L,R;
int a[16],bcnt[N1],lg[N1];
ll F[N1],f[16][N1],H[N1],inv[maxn+5];
ll gl[M1][M1],gr[M1][M1],glr[M1][M1],grl[M1][M1];
ll G(int X,int Y){
int lx=X&L, rx=(X&R)>>m, ly=Y&L, ry=(Y&R)>>m;
return gl[lx][ly]*gr[rx][ry]%p*glr[lx][ry]%p*grl[rx][ly]%p;
}
// inline int lowbit(int x){ return x&(-x); }
int main(){
// freopen("1.in","r",stdin);
// freopen(".out","w",stdout);
// int Te; read(Te);
// while(Te--) printf("%lld\n",solve());
read(n);
if(n==1){ puts("1"); return 0; }
for(int i=0;i<n;i++) read(a[i]);
inv[1]=1;
for(int i=2;i<=maxn;i++) inv[i]=1ll*(p-p/i)*inv[p%i]%p;
int all=(1<<n)-1;
lg[1]=0;
for(int i=2;i<(1<<n);i++) lg[i]=lg[i>>1]+1;
for(int s=1;s<(1<<n);s++) bcnt[s]=bcnt[s>>1]+(s&1);
// for(int s=0;s<(1<<n);s++){
// for(int i=0;i<n;i++) if( !((1<<i)&s) ){
// f[i][s]=1;
// for(int j=0;j<n;j++) if((1<<j)&s) (f[i][s]*=a[i]*inv[a[i]+a[j]]%p)%=p;
// }
// }
//每次去掉highbit 也就是lg 可以O(n*2^n)內遞推
for(int i=0,j;i<n;i++){
f[i][0]=1;
for(int s=1;s<(1<<n);s++) if(!((1<<i)&s)){
j=lg[s];
f[i][s]=f[i][s^(1<<j)]*a[i]%p*inv[a[i]+a[j]]%p;
}
}
m=(n+1)/2; L=(1<<m)-1; R=all^L;
for(int lx=0;lx<=L;lx++){
int s=L^lx;
for(int t=s;;t=(t-1)&s){
gl[lx][t]=1;
for(int i=0;i<m;i++) if((1<<i)&lx)
(gl[lx][t]*=f[i][t])%=p;
if(!t) break;
}
}
for(int rx=0;rx<=(R>>m);rx++){
int s=(R>>m)^rx;
for(int t=s;;t=(t-1)&s){
gr[rx][t]=1;
for(int i=0;i<n-m;i++) if((1<<i)&rx)
(gr[rx][t]*=f[i+m][t<<m])%=p;
if(!t) break;
}
}
for(int lx=0;lx<=L;lx++){
for(int ry=0;ry<=(R>>m);ry++){
glr[lx][ry]=1;
for(int i=0;i<m;i++) if((1<<i)&lx)
(glr[lx][ry]*=f[i][ry<<m])%=p;
}
}
for(int rx=0;rx<=(R>>m);rx++){
for(int ly=0;ly<=L;ly++){
grl[rx][ly]=1;
for(int i=0;i<n-m;i++) if((1<<i)&rx)
(grl[rx][ly]*=f[i+m][ly])%=p;
}
}
ll ans=0;
for(int win=1;win<(1<<n);win++){
H[win]=1; ll tmp;
for(int sub=(win-1)&win;sub;sub=(sub-1)&win){
// tmp=1;
// for(int i=0;i<n;i++) if((1<<i)&sub){
// (tmp*=f[i][win^sub])%=p;
// }
H[win]=(H[win]-H[sub]*G(sub,win^sub)%p+p)%p;
}
// tmp=1;
// assert(bcnt[win]!=2||H[win]==0);
// for(int i=0;i<n;i++) if((1<<i)&win) (tmp*=f[i][all^win])%=p;
// F[win]=tmp*H[win]%p;
F[win]=G(win,all^win)*H[win]%p;
(ans+=F[win]*bcnt[win]%p)%=p;
}
printf("%lld\n",ans);
return 0;
}