Codeforces 804F Fake bullions

版权声明:本文为博主原创文章,未经博主允许也可以转载。 https://blog.csdn.net/qq_35541672/article/details/85276425

题意

有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);
}

猜你喜欢

转载自blog.csdn.net/qq_35541672/article/details/85276425