1. 程式人生 > >Codeforces 804F Fake bullions

Codeforces 804F Fake bullions

題意

有N個幫派,每個幫派有Si個人,其中一些人有真金條
給出一張競賽圖,若圖上存在一條邊u->v,那麼如果u中一人i,v中一人j,滿足 i   m o d   S

u = j   m o d   S v
i\ mod \ S_u=j\ mod \ S_v 且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);
}