P2272 [ZJOI2007]最大半连通子图 tarjan+DP

思路:$tarjan+DP$

提交:1次

题解:首先对于一个强连通分量一定是一个半连通分量,并且形成的半连通分量的大小一定是它的$size$,所以我们先缩点。

这样,我们相当于要在新的$DAG$上找一个最长链(一旦有分叉边就不可能是一个半连通分量)。

于是乎写了个$dfs$,$sz[u]$表示这个(缩完后的)点的包含点的数量,$mx[u]$表示以$u$为起点最长链的长度,$tot[u]$表示方案数。

但是注意这个图有可能不连通。

#include<cstdio>
#include<iostream>
#include<map>
#define ull unsigned long long
#define ll long long
#define R register int
using namespace std;
#define pause (for(R i=1;i<=10000000000;++i))
#define In freopen("NOIPAK++.in","r",stdin)
#define Out freopen("out.out","w",stdout)
namespace Fread {
static char B[1<<15],*S=B,*D=B;
#ifndef JACK
#define getchar() (S==D&&(D=(S=B)+fread(B,1,1<<15,stdin),S==D)?EOF:*S++)
#endif
inline int g() {
    R ret=0,fix=1; register char ch; while(!isdigit(ch=getchar())) fix=ch=='-'?-1:fix;
    if(ch==EOF) return EOF; do ret=ret*10+(ch^48); while(isdigit(ch=getchar())); return ret*fix;
} inline bool isempty(const char& ch) {return (ch<=36||ch>=127);}
inline void gs(char* s) {
    register char ch; while(isempty(ch=getchar()));
    do *s++=ch; while(!isempty(ch=getchar()));
}
} using Fread::g; using Fread::gs;
namespace Luitaryi {
const int N=100010,M=1000010;
map<pair<int,int>,bool> mp;
int n,m,mod;
int vr[M<<1],nxt[M<<1],fir[M<<1],cnt;
inline void add(int u,int v) {vr[++cnt]=v,nxt[cnt]=fir[u],fir[u]=cnt;}
int vv[M<<1],nn[M<<1],ff[M<<1],ct;
inline void adde(int u,int v) {vv[++ct]=v,nn[ct]=ff[u],ff[u]=ct;}
int dfn[N],low[N],c[N],stk[N],sz[N],t[N],num,cc,top;
bool vis[N];
inline void tarjan(int u) {
    dfn[u]=low[u]=++num; stk[++top]=u,vis[u]=true;
    for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
        if(!dfn[v]) {
            tarjan(v); 
            low[u]=min(low[u],low[v]);
        } else if(vis[v]) low[u]=min(low[u],dfn[v]);
    } if(dfn[u]==low[u]) {
        ++cc; R tmp;
        do tmp=stk[top],--top,c[tmp]=cc,++sz[cc],vis[tmp]=false; while(tmp!=u);
    }
}
int anss,ans,mx[N],tot[N];
inline void dfs(int u) {
    vis[u]=true; mx[u]=sz[u],tot[u]=1;
    for(R i=ff[u];i;i=nn[i]) { R v=vv[i];
        if(!vis[v]) dfs(v);
        if(sz[u]+mx[v]>mx[u]) {
            mx[u]=sz[u]+mx[v];
            tot[u]=tot[v];
        } else if(sz[u]+mx[v]==mx[u])
            tot[u]=(tot[u]+tot[v])%mod;
    } ans=max(mx[u],ans);
}
inline void main() {
    n=g(),m=g(),mod=g();
    for(R i=1,u,v;i<=m;++i) u=g(),v=g(),add(u,v);
    for(R i=1;i<=n;++i) if(!dfn[i]) tarjan(i); 
    for(R u=1;u<=n;++u) for(R i=fir[u];i;i=nxt[i]) { R v=vr[i];
        if(c[u]!=c[v]&&mp.find(make_pair(c[u],c[v]))==mp.end()) 
            ++t[c[v]],adde(c[u],c[v]),mp[make_pair(c[u],c[v])]=true;
    } for(R i=1;i<=cc;++i) if(!t[i]||!vis[i]) dfs(i);
    for(R i=1;i<=cc;++i) if(mx[i]==ans) anss=(anss+tot[i])%mod;
    printf("%d\n%d\n",ans,anss);
}
}
signed main() {
    Luitaryi::main();
    return 0;
}

2019.07.22

转载于:https://www.cnblogs.com/Jackpei/p/11223957.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值