【Codeforces】【图论】【数量】【哈密顿路径】Fake bullions (CodeForces - 804F)
题意
有n个黑帮(gang),每个黑帮有siz[i]个人,黑帮与黑帮之间有有向边,并形成了一个竞赛完全图(即去除方向后正好为一个无向完全图)。在很多年前,有一些人参与了一次大型抢劫,参与抢劫的人都获得了一个真金条。
在这些年间,不同的黑帮之间进行了交易。具体过程是:
在时刻i,假如有一条边是u->v,那么u帮派中的i mod siz[u]号如果有金条(无论真假),并且v帮的i mod siz[v]没有任何金条,那么u中的这个人就会向v中的这个人一个假金条。
经过无数年的交易之后,各个帮派的金条拥有情况就确定了。他们开始向外面的世界倾销金条。真金条一定能够卖出去,而假金条可能买的出去,也可能卖不出去。定义一个帮派的力量值(strength)为成功卖出去的金条的数量。将所有的帮派按照力量值从大到小进行排序,从前a个帮派中选b个帮派。求所有可能的选出来的集合的个数。答案模1e9+7。
1<=b<=a<=n<=510^3
siz[i]<=210^6,\(\sum{siz[i]}<=2*10^6\)
输入格式
第一行为n,a,b。
接下来是一个n*n的邻接矩阵,无自环,(i,j)为1表示有边i->j,且G[i][j]+G[j][i]=1.
接下来有n行,每行开头是一个整数siz[i],接下来一个长为siz[i]的二进制串,如果第j(0 <=j< siz[i])位是1表示第j个人最开始有真金条,否则最开始没有金条。
输出格式
一个整数表示答案。
思路
首先原题的题目打错了,并不是0表示有金条,而是1表示有金条...虽然CF发了更正通知,但是题目并没有更改过来...
这道题显然是两部分:一个是求最后的金条拥有情况,一个是根据帮派的最大销售量mx[i]和最小销售量mn[i]求答案。
第一部分
首先有几个性质:
性质一
对于u->v这条边而言,u中的i能够对v中的j造成贡献,当且仅当\(i \equiv j(mod\) \(gcd(siz[u],siz[v]))\)(具体的证明博主表示不想说了,大概就是欧几里得搞一下)。
并由此能够推出一个引理,就是对于一条路径:u->p1->p2->p3->...->v,u中的i能够对v中的j造成贡献,当且仅当\(i \equiv j(mod\) \(gcd(siz[u],siz[p1],siz[p2]...siz[v]))\)(具体的证明等我看懂了再补吧...)
因此一个黑帮对另一个黑帮造成贡献时,当然是经过的中间点越多越好。
性质二
对于一条链的情况上面已经说过了,不难想到对于一个强连通分量而言,假设其中所有的黑帮的siz的gcd为bgcd(block gcd),那么其中的某一个黑帮的i号有金条当且仅当\(i \equiv j(mod\) \(bgcd)\),且j(另外一个黑帮的成员)有金条。
那么我们就可以将整个强连通分量看做是一个整体(即一个黑帮),一共有bgcd个人,其中第i个人有金条当且仅当\(i \equiv j(mod\) \(bgcd)(j是这个块中的某一个黑帮中的成员且j有金条)\)。
这样缩点之后我们就得到了一个有向无环的竞赛完全图。
性质三
假设我们处理完之后,怎么通过一个强连通分量在外部得到的信息来更新块内的信息呢??
这个还是比较好想的。
假设num[i]为第i个黑帮最后拥有的真假金条的总和。设它属于第k个块。这个块的虚拟黑帮最后有blonum[k]个真假金条。那么有:
\]
这样就可以只看最后的那个DAG了。
性质四
在得到的DAG中,一定存在着一条哈密顿路径。因为假设不存在,那么一定有大于等于2个点,它们的出度为0。但是由于这是一个竞赛完全图,两两之间都有连边,因此这是不可能的。那么就存在了一条哈密顿路径。
其次,在这条哈密顿路径上,从起点开始,出度递减,入度递增。
这是因为加入存在一个点的出度大于它之前的那个点,那么它必然能够指向之前那个点的一条入边的来源点(因为当前这个点连向的点的数量大于前面的点的入边的来源点的数量),这样就形成环了(众所周知,环是一个强连通分量)。
又因为入边+出边=n-1,所以对于入边也是成立的。
性质五
这是和那个DAG有关的。沿着那条哈密顿路径,其中的点i只能够对之后的点造成贡献,而不能够先回到之前的点,因为这样子也有环了。
又因为一个黑帮对另一个黑帮造成贡献时,经过的中间点越多越好,所以只能够沿着哈密顿路径进行贡献。
并且哈密顿路径的起点到终点的新分配的强联通编号是递减的(从dfs树上就能看出)。
做法
有了以上的性质之后,就好搞了。
首先进行Tarjan缩点,对于每一个黑帮的强连通分量建立一个虚拟黑帮。然后将第i个黑帮的金条信息利用性质二放进去。
然后就得到了一个DAG。我们在这个DAG中找出一条哈密顿路径。然后从起点开始,不断地将当前强联通分量的信息向着下一个点进行转移(记住每一次转移的剩余系都是在mod gcd(blo[i],blo[i-1]下的)),然后依次做下来,就是O(n^2)的了。
这样子就得到了最终的金条分别情况了。心累
第二部分
假设i号黑帮的最大销售量为mx[i],最低为mn[i]。那么我们枚举i,并且考虑i号黑帮是选出来的b个黑帮中最差劲(力量值最低)的那个。
首先,对于mn[j]>mx[i]的j号,他肯定是要排在i号前面的,先统计起来为cnt1.然后就是mn[j]<=mx[i]的,由于i号是最差的为了防止重复,我们定义“最差”为:力量值为唯一最小或(不唯一最小,但是编号最小)。那么我们就统计这些mn[j]<=mx[i]且(mx[j]>mx[j]||(mx[j]==mx[i]&&j>i))的数量cnt2.
然后我们考虑枚举b中有j个人来自cnt2,那么来自cnt1的就有b-j-1个人。
然后对于j有一些限制:
1.j<=cnt2
2.j<=a-1-cnt1
3.j>=0
4.j+1+cnt1>=b
限制的具体原因就不提及的,请聪明的读者自行死尻思考。
然后将C(cnt2,j)*C(cnt1,b-1-j)累加起来就好了。
代码
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#define MAXN 10000
#define MAXL 2000000
#define MO 1000000007
using namespace std;
int n,a,b,c;
char str[MAXL+5];
vector<int> G[MAXN+5];
vector<int> per[MAXN+5];
int siz[MAXN+5],dfn[MAXN+5],low[MAXN+5],dcnt;
int blo[MAXN+5],bgcd[MAXN+5],blocnt;
int stk[MAXN+5],t;
int num[MAXN+5],mx[MAXN+5],mn[MAXN+5];
bool instk[MAXN+5];
int fact[MAXN+5],inv[MAXN+5];
int PowMod(int x,int y)
{
int ret=1;
while(y)
{
if(y&1)
ret=1LL*ret*x%MO;
x=1LL*x*x%MO;
y>>=1;
}
return ret;
}
int gcd(int x,int y)
{
if(y==0)
return x;
return gcd(y,x%y);
}
void InPut()
{
scanf("%d %d %d",&n,&a,&b);
for(int i=1;i<=n;i++)
{
scanf("%s",str+1);
for(int j=1;j<=n;j++)
if(str[j]=='1')
G[i].push_back(j);
}
for(int i=1;i<=n;i++)
{
scanf("%d",&siz[i]);
per[i].resize(siz[i]+1);
scanf("%s",str);
for(int j=0;j<siz[i];j++)
if(str[j]=='1')
per[i][j]=1;
else if(str[j]=='0')
per[i][j]=0;
}
}
void Tarjan(int u,int fa)
{
dfn[u]=low[u]=++dcnt;
instk[u]=true;stk[t++]=u;
for(int i=0;i<(int)G[u].size();i++)
{
int v=G[u][i];
if(dfn[v]==0)
{
Tarjan(v,u);
low[u]=min(low[u],low[v]);
}
else if(instk[v]==true)
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
int fro=0;blocnt++;
do
{
fro=stk[--t];instk[fro]=false;
blo[fro]=blocnt;
bgcd[blocnt]=gcd(bgcd[blocnt],siz[fro]);
}while(fro!=u);
}
}
void Part1()
{
for(int i=1;i<=n;i++)
if(dfn[i]==0)
Tarjan(i,-1);
for(int i=1;i<=blocnt;i++)
per[i+n].resize(bgcd[i]+1);//预先设定大小
for(int i=1;i<=n;i++)
for(int j=0;j<siz[i];j++)
per[blo[i]+n][j%bgcd[blo[i]]]|=per[i][j];//存到虚拟黑帮中
int nw;
for(int i=blocnt;i>1;i--)//自然形成了哈密顿路径
{
nw=gcd(bgcd[i],bgcd[i-1]);//当前的剩余系
for(int j=0;j<bgcd[i];j++)
if(per[i+n][j])
{
per[i-1+n][j%nw]|=1;//对下一个点进行更新
num[i]++;//num就是总的金条数
}
}
for(int j=0;j<bgcd[1];j++)
num[1]+=per[1+n][j];//记得一定要统计block1的值
for(int i=1;i<=n;i++)
{
mx[i]=mn[i]=0;
mx[i]=1LL*num[blo[i]]*siz[i]/bgcd[blo[i]];根据性质三进行还原
for(int j=0;j<siz[i];j++)
mn[i]+=per[i][j];//直接利用初始信息算
}
}
void Prepare()
{
fact[0]=1;
for(int i=1;i<=MAXN;i++)
fact[i]=1LL*fact[i-1]*i%MO;
inv[MAXN]=PowMod(fact[MAXN],MO-2);
for(int i=MAXN-1;i>=0;i--)
inv[i]=1LL*inv[i+1]*(1LL*i+1LL)%MO;
}
inline int C(int x,int y)
{
return 1LL*fact[x]*inv[y]%MO*inv[x-y]%MO;
}
int Part2()
{
Prepare();
int cnt1=0,cnt2=0,ret=0;
for(int i=1;i<=n;i++)
{
cnt1=cnt2=0;
for(int j=1;j<=n;j++)
{
if(i!=j&&mn[j]>mx[i])
cnt1++;
if(i!=j&&mn[j]<=mx[i]&&(mx[j]>mx[i]||(mx[j]==mx[i]&&j<i)))//避免重复
cnt2++;
}
if(cnt1>a-1)
continue;
for(int j=min(b,min(a-1-cnt1,cnt2));j>=0&&j+1+cnt1>=b;j--)//一堆限制
ret=(1LL*ret+1LL*C(cnt2,j)*C(cnt1,b-j-1)%MO)%MO;
}
return ret;
}
int main()
{
InPut();
Part1();
int ans=Part2();
printf("%d\n",ans);
return 0;
}
值得一提的是,为了迎合题目的限制,这里我使用了vector(赖皮)
可能有些地方并非原创,部分借鉴了网上的题解和CF的官方题解。
但是题解(除了性质一)绝对是详细的。
祝您玩的愉快
【Codeforces】【图论】【数量】【哈密顿路径】Fake bullions (CodeForces - 804F)的更多相关文章
- Codeforces Beta Round #32 (Div. 2, Codeforces format)
Codeforces Beta Round #32 (Div. 2, Codeforces format) http://codeforces.com/contest/32 A #include< ...
- Codeforces Beta Round #31 (Div. 2, Codeforces format)
Codeforces Beta Round #31 (Div. 2, Codeforces format) http://codeforces.com/contest/31 A #include< ...
- Codeforces Beta Round #29 (Div. 2, Codeforces format)
Codeforces Beta Round #29 (Div. 2, Codeforces format) http://codeforces.com/contest/29 A #include< ...
- [codeforces 804F. Fake bullions]
题目大意: 传送门. 给一个n个点的有向完全图(即任意两点有且仅有一条有向边). 每一个点上有$S_i$个人,开始时其中有些人有真金块,有些人没有金块.当时刻$i$时,若$u$到$v$有边,若$u$中 ...
- codeforces 556B. Case of Fake Numbers 解题报告
题目链接:http://codeforces.com/problemset/problem/556/B 题目意思:给出 n 个齿轮,每个齿轮有 n 个 teeth,逆时针排列,编号为0 ~ n-1.每 ...
- CodeForces - 556B Case of Fake Numbers
//////////////////////////////////////////////////////////////////////////////////////////////////// ...
- Codeforces Round #411 A. Fake NP
A. Fake NP time limit per test 1 second memory limit per test 256 megabytes Tavak and Seyyed a ...
- CodeForces 556 --Case of Fake Numbers
B. Case of Fake Numbers time limit per test 2 seconds memory limit per test 256 megabytes input stan ...
- B. Case of Fake Numbers( Codeforces Round #310 (Div. 2) 简单题)
B. Case of Fake Numbers time limit per test 2 seconds memory limit per test 256 megabytes input stan ...
随机推荐
- postgreSQL学习(二):pgsql的一些基础操作
在上一篇文章中我们学习了怎么安装pgsql,安装好了后,我们来学习一下怎么对pgsql进行创建操作以及相关的crud的操作啦 一 创建数据库 $ createdb test 然后你可能会遇到如下的错误 ...
- GO语言系列(二)- 基本数据类型和操作符
一.文件名 & 关键字 & 标识符 1.所有go源码以.go结尾 2.标识符以字母或下划线开头,大小写敏感 3._是特殊标识符,用来忽略结果 4.保留关键字 二.Go程序的基本结构 p ...
- go 的包
- 分布式监控系统开发【day37】:服务端生成配置数据(四)
一.目录结构 二.引子与代码 1.客户端获取服务列表接口 1.解决了什么问题 客户端要给我获取服务列表的的时候,他肯定要告诉他是谁?他怎么告诉我,客户端必须有一个id号 Saltsack你装一个客户端 ...
- pip换源安装
pip install --index-url https://pypi.tuna.tsinghua.edu.cn/simple 要安装的 有些工具安装太慢, 换源安装一下, 速度一下子飞起
- react实战项目开发(2) react几个重要概念以及JSX语法
前言 前面我们已经学习了利用官方脚手架搭建一套可以应用在生产环境下的React开发环境.那么今天这篇文章主要先了解几个react重要的概念,以及讲解本文的重要知识JSX语法 React重要概念 [思想 ...
- 微信获取企业token流程
1.获取服务商Accesstoken(每10分钟企业微信会推送一次,两个小时后过期) 2.根据suitid.accesstoken.第三方企业corpid.第三方企业permanentcode,得到第 ...
- 论文笔记系列-Well Begun Is Half Done:Generating High-Quality Seeds for Automatic Image Dataset Construction from Web
MARSGGBO♥原创 2019-3-2
- RabbitMQ环境搭建
消息 RabbitMQ 1.安装erlang环境 2.安装rabbitmq 3.参考资料 AMQP协议,可以跨语言通信 Advance Message Queuing Protocol Rabbit ...
- Web从入门到放弃<7>
从这章开始读<javascript高级程序设计> <1>typeof 返回字符串 / 类型 未定义:undefined 布尔:boolean 字符串:string 数值:num ...