P2272 [ZJOI2007]最大半连通子图
这道题真的是把我恶心坏了。
开了很多很多不同用处的数组,来完成这个题目。加上各种STL库的容器才弄完这个鬼东西。
根据题目意思我们可以知道,任何一个强连通分量都是半连通分量。
然后我们假象tarjan之后的图,稍加思考可以进一步得到如下结论。
如果一个强连通分量与另外一个强连通分量之间有一条边直接相连,那么这两个强连通分量可以合成一个更大的半连通分量
在tarjan完之后的图中体现为,树的一条链,所以也就是tarjan缩点之后,从一个叶子节点开始逐步向上累加直到根节点,就是当前叶子节点的最大半连通分量。然后我们可以通过拓扑排序来实现这一个操作。
if(f[now]+num[v]>f[v]){//
f[v]=f[now]+num[v];
g[v]=g[now];
}
else if(f[now]+num[v]==f[v]){
g[v]+=g[now];
g[v]%=mod;
}
f[i]:以强连通分量i为根的子树的最大值
g[i]:以强连通分量i为根的子树的最大值的方案数目
num[i]:强连通分量i的节点个数
算法整体流程
1.输入并建图
2.tarjan缩点
3.重新建图,重新建图的过程需要注意,可能会存在重复的边
举个例子,a和b处于同一个强连通分量,但是c与a不处于同一个强连通分量。
a可以有一条边指向c,b也可以有一条边指向c,在后续建图可能会重复。
4.拓扑排序
5.统计并输出答案。
这里第一次建图使用链式前向星,第二次使用vector来建图。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n,m,mod,cnt,tot,sum;
ll maxnum,maxx;
struct node {
int v,next;
};
struct node edge[1000002];
stack<int>q;
int du[100002],low[100002],dfn[100002],scc[100002],ins[100002],num[100002],head[100002];
ll g[100002],f[100002];
vector<int>link[100002];
map<int,int>mp[100002];
void add(int u,int v){
edge[tot].v=v;
edge[tot].next=head[u];
head[u]=tot;
tot++;
}
void init(){
tot=0;sum=0;cnt=0;
for(int i=1;i<=n;i++){
head[i]=-1;dfn[i]=-1;
}
}
void tarjan(int now){//正常tarjan,没有其它的操作
sum++;
dfn[now]=low[now]=sum;
q.push(now);
ins[now]=1;
for(int i=head[now];i!=-1;i=edge[i].next){
int v=edge[i].v;
if(dfn[v]==-1){
tarjan(v);;
low[now]=min(low[now],low[v]);
}
else if(ins[v])low[now]=min(low[now],dfn[v]);
}
if(dfn[now]==low[now]){
cnt++;
int v=-1;
do{
v=q.top();q.pop();
ins[v]=0;scc[v]=cnt;num[cnt]++;
}while(v!=now);
}
}
void topsort(){
queue<int>q;
for(int i=1;i<=cnt;i++){//初始化队列和转移数组
if(du[i]==0){
q.push(i);
f[i]=num[i];
g[i]=1;
}
}
while(!q.empty()){
int now=q.front();q.pop();
for(int i=0;i<link[now].size();i++){
int v=link[now][i];
du[v]--;
if(du[v]==0)q.push(v);
if(f[now]+num[v]>f[v]){
f[v]=f[now]+num[v];
g[v]=g[now];
}
else if(f[now]+num[v]==f[v]){
g[v]+=g[now];
g[v]%=mod;
}
}
}
}
int main (){
#ifdef LOCAL
freopen(".\\a.in", "r", stdin);
#endif
scanf("%d%d%d",&n,&m,&mod);
init();
for(int i=1;i<=m;i++){
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
}
for(int i=1;i<=n;i++){
if(dfn[i]==-1)tarjan(i);
}
for(int i=1;i<=n;i++){//遍历原来的所有边
for(int j=head[i];j!=-1;j=edge[j].next){
int u=i;int v=edge[j].v;
u=scc[u];v=scc[v];//重新建图是相对于缩点之后的强连通分量而言的,要注意转换
if(u!=v&&mp[u][v]==0){
link[u].push_back(v);
mp[u][v]=1;//重复边的标记
du[v]++;
}
}
}
topsort();
for(int i=1;i<=cnt;i++)maxx=max(maxx,f[i]);
printf("%lld\n",maxx);
for(int i=1;i<=cnt;i++){
if(f[i]==maxx)maxnum+=g[i];
maxnum%=mod;
}
printf("%lld\n",maxnum);
return 0;
}