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