Codeforces Round #411 (Div. 1)-F. Fake bullions

题目链接

Codeforces Round #411 (Div. 1)-F. Fake bullions

解析

  • 首先我们可以把问题分割成两个独立的子问题,即先求出每个点上最小的真金子的数量与最大的真金子的数量,再组合计数
  • 考虑题目给的条件,对于图中的一条有向边(u,v),如果节点u上i位置有金子,那么节点v上的所有满足 j ≡ i ( m o d   g c d ( s u , s v ) ) j\equiv i(mod\ gcd(s_{u},s_{v})) ji(mod gcd(su,sv))的位置j上都会有金子(证明:裴蜀定理)
  • 从有向边拓展,有结论对于一个强联通分量内的点,不妨设所有 s s s g c d gcd gcd d d d,那么有如果某节点u上i位置有金子,那么对于该强联通分量上的任意一点v上所有满足 j ≡ i ( m o d   d ) j\equiv i(mod\ d) ji(mod d)的位置j上都会有金子(感性理解)
  • 在缩点之后我们在DAG上暴力跑会T,所以我们需要优化.观察到原图是一个竞赛图,那么根据结论则必然有一条哈密尔顿路径,所以我们直接构造哈密尔顿路径即可,这么做可以保证每个点都接受到了能到达他的点的信息
  • 这样我们求出了每个点每个点上最小的真金子的数量与最大的真金子的数量,不妨设其为mn,mx,下面我们考虑如何计数
  • 我们考虑对于一种可行方案,强行使前A大的点都是其mx,考虑如果两个点上金子数量相同,不妨钦定编号大的更小。
  • 在上述前提下,我们枚举选出集合中最小的点为i,那么我们考虑将剩余点分类:第一类是无论如何都比点i大,即mn(j)>mx(i);第二类是既存在比i大的情况,也存在比i小的情况;第三类不会对答案产生贡献.我们可以求出每类点的数量
  • 我们继续枚举第二类点在最终集合中的点的个数,最后组合数计算即可
  • 注:以下代码略有错误博主懒得改
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int mod=1e9+7;
const int N=5e3+100;
int low[N],dfn[N],sta[N],col[N],b[N],gmin[N],up[N],down[N],nxt[N],sum[N];
int *mat[N];
string c[N];
char s[N];
bool a[N][N],d[N][N];
int n,A,B,dfstime,cnt,top,root;
ll fac[N],inv[N],ans;
int gcd(int x,int y){
	if (!x||!y) return x+y;
	int z=x%y;
	while (z){
		x=y;y=z;z=x%y;
	}
	return y;
}
void tarjan(int now){
	sta[++top]=now; dfn[now]=++dfstime; low[now]=dfstime;
	for (int i=1;i<=n;i++) if (a[now][i]) {
		if (dfn[i]&&!col[i]) low[now]=min(low[now],dfn[i]);
		else if (!col[i]){
			tarjan(i); low[now]=min(low[now],low[i]);
		}
	} 
	if (low[now]>=dfn[now]) {
		int x=0; cnt++; int r=top;
		while (sta[top]!=now) {
			col[sta[top]]=cnt; x=gcd(x,b[sta[top]]); top--;
 		}
		col[sta[top]]=cnt; x=gcd(x,b[sta[top]]); top--;
		mat[cnt]=new int[x]; gmin[cnt]=x;
		memset(mat[cnt],0,sizeof(mat[cnt]));
		for (int i=top+1;i<=r;i++) {
			int u=sta[i];
			for (int j=0;j<b[u];j++) if (c[u][j]=='1') mat[cnt][j%x]=1; 
		}
	}
}
void init(){
	fac[0]=1; for(int i=1;i<N;i++) fac[i]=(1ll*fac[i-1]*i)%mod;
	inv[0]=1; inv[1]=1; for(int i=2;i<N;i++) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
	for (int i=1;i<N;i++) inv[i]=(inv[i-1]*inv[i])%mod;
}
ll C(int x,int y){
	if (x<0||y<0||x<y) return 0;
	return fac[x]*inv[y]%mod*inv[x-y]%mod;
}
int main(){
	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') a[i][j]=1; else a[i][j]=0;
	}
	for (int i=1;i<=n;i++){
		scanf("%d",&b[i]);
		cin >> c[i];
	}
	for (int i=1;i<=n;i++) if (!dfn[i]) dfstime=0,tarjan(i);	
	for (int i=1;i<=n;i++){
		for (int j=1;j<=n;j++) if (a[i][j]&&col[i]!=col[j]) d[col[i]][col[j]]=1;
	}
	int head=1,tail=1;
	for (int i=2;i<=cnt;i++) {
		if (d[i][head]) nxt[i]=head,head=i; else if (d[tail][i]) nxt[tail]=i,tail=i;
		else {
			int x=head;
			while (x!=tail) {
				if (d[x][i]&&d[i][nxt[x]]) {nxt[i]=nxt[x],nxt[x]=i;break;} else x=nxt[x];
			}
		}
	}
	while (head!=tail){
		int x=gcd(gmin[head],gmin[nxt[head]]);
		if (gmin[head]) for (int i=0;i<gmin[head];i++) if (mat[head][i]) mat[nxt[head]][i%x]=1;
		head=nxt[head];
	} 
	for (int i=1;i<=cnt;i++) 
		for (int j=0;j<gmin[i];j++) if (mat[i][j]) sum[i]++;
	for (int i=1;i<=n;i++) {
		for (int j=0;j<b[i];j++) if (c[i][j]=='1') down[i]++;
		up[i]=sum[col[i]]*(b[i]/gmin[col[i]]);
	}	
	init();
//	for (int i=1;i<=n;i++) cout << down[i] << ' ' << up[i] << endl;
	for (int i=1;i<=n;i++)  {
		int cnt1=0,cnt2=0;
		for (int j=1;j<=n;j++) if (j!=i&&(down[j]>up[i])) cnt1++;
		else if (j!=i&&(up[j]>up[i]||((up[j]==up[i])&&(j<i)))) cnt2++;
//		cout << cnt1 << ' ' << cnt2 << endl;
		for (int j=0;j<=min(B-1,cnt2);j++) if (j+cnt1+1<=A) ans=(ans+C(cnt2,j)*C(cnt1,B-j-1)%mod)%mod; 
//		cout << ans << ' ' << i << endl;
	}
	printf("%lld\n",ans); 
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值