Codeforces 804F Fake bullions
題意
有N個幫派,每個幫派有Si個人,其中一些人有真金條
給出一張競賽圖,若圖上存在一條邊u->v,那麼如果u中一人i,v中一人j,滿足
且i有金條(無論真假)且j無金條,i就會給j一個假金條
在足夠長的時間後,所有人的金條數會達到穩定(0或1)
然後每個幫派都會賣金條,真金條一定能賣出,假金條可能能賣出可能不能賣出,然後取賣出金條數最多的a個(並列任取),再在其中選出b個,問這b個的集合有多少種,對1e9+7取模
1<=b<=a<=n<=5e3,S的和<=2e6
題解
首先我們要求出每個幫派最小和最大的賣出的金條數
顯然最小的就是真金條數,最大的就是真金條+假金條數
那麼下面的問題就是最後每個幫派會得到多少個假金條
性質1:假如存在一條邊u->v,那麼u中編號為i的會給v中編號為j的當且僅當i=j(mod gcd(Su,Sv))
定義f(u,g)表示u在mod g剩餘系下的金條分佈
性質2:假如有著兩條邊u->v,v->w,那麼f(v,gcd(Su,Sv,Sw))->w
性質3:如果存在一條鏈,那麼這條鏈首對鏈尾的貢獻是f(top,整條鏈的gcd)->tail
性質4:對於一個環,mxv=mxu*Sv/Su(u=f(v,gcd))
這堆性質怎麼證呢
em…咕咕咕
根據性質4,我們就可以跑一遍Tarjan,求出強連通分量,每個強連通分量可以用一個新的黑幫v來替換他們,v的大小是gcd(S),i號有金條的充要條件是i在f(v,g)中有金條
然後就成了個DAG
然後就要用到競賽圖縮環後的性質了
1.縮環後肯定還是個競賽圖
2.所有的入度一定是0,1,2…,n-1
(證明:可以先證明一定有且僅有一個是n-1,然後n-2…就證完了)
3.沿著拓撲序,一定是個哈密頓路徑
而且啊,一定是沿著哈密頓路徑轉移是最優的,可以通過上面的性質三考慮,肯定gcd越小越好,那麼點數就越多越好
然後我們就可以按拓撲序轉移了
最後再利用性質4將值分配到每個點上
第二部分呢,就是怎麼求方案數
首先,我們必須是直接考慮b,最終的集合,而非考慮a再乘上C(a,b),否則肯定會有重複
那麼怎麼考慮呢?我們可以欽定b中最小的那個幫派是誰
通過O(n)可以算出有多少個一定大於b的金條數(mn[j]>mx[i])記為c1
有多少個可能大於b的金條數(mx[j]>mx[i]),記為c2
注意到可能有並列的,我們需要對mx[i]=mx[j]特殊處理,比如限制i<j時才加入c2
然後呢?
我們可以列舉加入b集合的個數j,那麼對於j有如下限制 (似乎看著都很顯然的樣子)
1.j>=0
2.j+c1+1>=b
3.j+1<=b
4.j<=c2
5.j+c1+1<=a
其中第5個是最容易忽略的
然後呢?我們就可以直接讓ans+=C(c2,j)*C(c1,b-j-1)了
好了,就醬
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=5e3+5;
const int M=4e6+5;
const int mod=1e9+7;
typedef long long ll;
struct node{
int v,nxt;
}edge[N*N];
int head[N*2],mcnt;
void add_edge(int u,int v){
mcnt++;
edge[mcnt].v=v;
edge[mcnt].nxt=head[u];
head[u]=mcnt;
}
int ksm(int x,int y){
int res=1;
while(y){
if(y&1)
res=1ll*res*x%mod;
x=1ll*x*x%mod;
y>>=1;
}
return res;
}
int gcd(int x,int y){
return y?gcd(y,x%y):x;
}
int scnt,scc[N],sgcd[N];
int S[N],top;
int dfn[N],low[N],tot;
bool instack[N];
int sz[N];
void Tarjan(int u){
low[u]=dfn[u]=++tot;
S[++top]=u;
instack[u]=1;
for(int i=head[u];i;i=edge[i].nxt){
int v=edge[i].v;
if(!dfn[v]){
Tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(instack[v])
low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
scnt++;
int x;
do{
x=S[top--];
instack[x]=false;
scc[x]=scnt;
sgcd[scnt]=gcd(sgcd[scnt],sz[x]);
}while(x!=u);
}
}
int fact[M+5],inv[M+5];
void Init(){
fact[0]=1;
for(int i=1;i<M;i++)
fact[i]=1ll*fact[i-1]*i%mod;
inv[1]=1;
for(int i=2;i<M;i++)
inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
inv[0]=1;
for(int i=1;i<M;i++)
inv[i]=1ll*inv[i-1]*inv[i]%mod;
}
int C(int x,int y){
if(x<0||x<y)
return 0;
return 1ll*fact[x]*inv[y]%mod*inv[x-y]%mod;
}
char s[M];
char truegold[M];
int gold[M];
int sum1[N],sum2[N];
int num[N];
int mx[N],mn[N];
int n,a,b;
int main()
{
Init();
scanf("%d%d%d",&n,&a,&b);
for(int i=1;i<=n;i++){
scanf("%s",s+1);
for(int j=1;j<=n;j++)
if(s[j]=='1')
add_edge(i,j);
}
for(int i=1;i<=n;i++){
scanf("%d",&sz[i]);
sum1[i]=sum1[i-1]+sz[i-1];
scanf("%s",truegold+sum1[i]);
}
sum1[n+1]=sum1[n]+sz[n];
for(int i=0;i<sum1[n+1];i++)
truegold[i]-='0';
for(int i=1;i<=n;i++)
if(!dfn[i])
Tarjan(i);
for(int i=1;i<=scnt;i++)
sum2[i]=sum2[i-1]+sgcd[i-1];
for(int i=1;i<=n;i++){
for(int j=sum1[i];j<sum1[i+1];j++){
if(truegold[j]){
gold[sum2[scc[i]]+(j-sum1[i])%sgcd[scc[i]]]=true;
}
}
}
for(int i=scnt;i>1;i--){
int G=gcd(sgcd[i],sgcd[i-1]);
for(int j=0;j<sgcd[i];j++)
if(gold[sum2[i]+j]){
num[i]++;
gold[sum2[i-1]+j%G]=true;
}
}
for(int j=0;j<sgcd[1];j++)
if(gold[j])
num[1]++;
for(int i=1;i<=n;i++){
mx[i]=1ll*num[scc[i]]*sz[i]/sgcd[scc[i]];
for(int j=sum1[i];j<sum1[i+1];j++)
mn[i]+=truegold[j];
}
int ans=0;
for(int i=1;i<=n;i++){
int c1=0,c2=0;
for(int j=1;j<=n;j++)
if(mn[j]>mx[i])
c1++;
if(c1>=a)
continue ;
for(int j=1;j<=n;j++)
if(mn[j]<=mx[i]&&(mx[i]<mx[j]||(mx[i]==mx[j]&&i>j)))
c2++;
//for(int j=min(b-1,min(c2,a-1-c1));b-j-1<=c1&&j>=0;j--){
for(int j=max(b-c1-1,0);j<=b-1&&j<=c2&&j<=a-c1-1;j++){
ans=(ans+1ll*C(c2,j)*C(c1,b-j-1)%mod)%mod;
}
}
printf("%d\n",ans);
}