Solution1:
Solution2:
其实第一种解法是先AC的,但是因为输出格式弄错找了半天错误,换了一种解法才发现自己输出错了。。。
https://www.luogu.com.cn/problem/P2272
定义一张半联通子图为满足在这张图内的任意两点 i , j i,j i,j, i i i可以到达 j j j或者 j j j可以到达 i i i
求一张有 n n n个节点 m m m条边的有向图的最大半联通子图及其方案数
数据范围:
n ≤ 1 0 5 , m ≤ 1 0 6 n\leq 10^5,m\leq 10^6 n≤105,m≤106
显然一张联通子图必然是半联通子图
所以我们先用 T a r j a n Tarjan Tarjan缩点,缩点后可能会产生自环或重边,它们各有处理方法,代码中会有注释
然后稍加分析,容易发现,最大半联通子图实际上是缩点后这张 D A G DAG DAG上的最长链!
那么我们就可以在这个 D A G DAG DAG上 d p dp dp啦
设 f i , 0 / 1 f_{i,0/1} fi,0/1表示以第 i i i个点结尾的最长链长度及其对应的方案数,用 d f s dfs dfs或者拓扑序 d p dp dp处理均可
时间复杂度:
用离散化处理重边复杂度是 O ( n + m l o g m ) O(n+mlogm) O(n+mlogm)
用数组保存访问的点处理重边复杂度是 O ( n + m ) O(n+m) O(n+m)
代码1采用前者,代码2采用后者
#include
#include
#include
#include
#include
#define N 100010
#define M 2000010
#define LL long long
using namespace std;int n,m,le[N],lg[N],tote,totg,a,b,mod;
struct node{
int next,to;}e[M],g[M];
inline void adde(int u,int v){
e[++tote]=(node){
le[u],v};le[u]=tote;return;}
inline void addg(int u,int v){
g[++totg]=(node){
lg[u],v};lg[u]=totg;return;}
bool vis[N];
inline LL read()
{
char c;LL d=1,f=0;
while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+(c^48);
while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+(c^48);
return d*f;
}
int low[N],dfn[N],stk[N],top,cnt,which[N],k,large[N];
inline void Tarjan(int x)
{
low[x]=dfn[x]=++cnt;
stk[++top]=x;vis[x]=true;
for(register int i=le[x];i;i=e[i].next)
{
int y=e[i].to;
if(dfn[y]==0)
{
Tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(vis[y]) low[x]=min(low[x],low[y]);
}
if(dfn[x]==low[x])
{
int y;
which[x]=++k;large[k]++;
while(y=stk[top--])
{
vis[y]=false;
if(x==y) break;
large[k]++;which[y]=k;
}
return;
}
}
struct edge{
int id,u,v;}E[M];
inline bool cmp(edge x,edge y) {
return x.u<y.u||x.u==y.u&&x.v<y.v;}
int len,rd[N],cd[N],f[N][2],maxn;
inline void dfs(int x)//dfs遍历,顺便做dp
{
vis[x]=true;
if(cd[x]==0)
{
f[x][0]=large[x];f[x][1]=1;
maxn=max(maxn,f[x][0]);
return;
}
for(register int i=lg[x];i;i=g[i].next)
{
int y=g[i].to;
if(vis[y]==0) dfs(y);
if(f[y][0]+large[x]>f[x][0]) f[x][0]=f[y][0]+large[x],f[x][1]=f[y][1];
else if(f[y][0]+large[x]==f[x][0]) (f[x][1]+=f[y][1])%=mod;
maxn=max(maxn,f[x][0]);
}
return;
}
signed main()
{
n=read();m=read();mod=read();
for(register int i=1;i<=m;i++) a=read(),b=read(),adde(a,b);
for(register int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i);
for(register int i=1;i<=n;i++)
for(register int j=le[i];j;j=e[j].next)
{
int x=which[i],y=which[e[j].to];
if(x==y) continue;//处理自环
E[++len]=(edge){
len,x,y};//把边存下来,其实第一维是没必要的,当时以为要用而已。。。
}
sort(E+1,E+1+len,cmp);
for(register int i=1;i<=len;i++)
if(E[i].u!=E[i-1].u||E[i].v!=E[i-1].v) addg(E[i].u,E[i].v),++rd[E[i].v],++cd[E[i].u];//处理重边
for(register int i=1;i<=k;i++) if(rd[i]+vis[i]==0) dfs(i);
int res=0;
for(register int i=1;i<=k;i++) if(f[i][0]==maxn) (res+=f[i][1])%=mod;
printf("%d\n%d",maxn,res);
}
#include
#include
#include
#include
#include
#define N 100010
#define M 2000010
#define LL long long
using namespace std;int n,m,le[N],lg[N],tote,totg,a,b,mod,fa[N];
struct node{
int next,to;}e[M],g[M];
inline void adde(int u,int v){
e[++tote]=(node){
le[u],v};le[u]=tote;return;}
inline void addg(int u,int v){
g[++totg]=(node){
lg[u],v};lg[u]=totg;return;}
bool vis[N];
inline LL read()
{
char c;LL d=1,f=0;
while(c=getchar(),!isdigit(c)) if(c=='-') d=-1;f=(f<<3)+(f<<1)+(c^48);
while(c=getchar(),isdigit(c)) f=(f<<3)+(f<<1)+(c^48);
return d*f;
}
int low[N],dfn[N],stk[N],top,cnt,which[N],k,large[N];
inline void Tarjan(int x)
{
low[x]=dfn[x]=++cnt;
stk[++top]=x;vis[x]=true;
for(register int i=le[x];i;i=e[i].next)
{
int y=e[i].to;
if(dfn[y]==0)
{
Tarjan(y);
low[x]=min(low[x],low[y]);
}
else if(vis[y]) low[x]=min(low[x],low[y]);
}
if(dfn[x]==low[x])
{
int y;
which[x]=++k;large[k]++;
while(y=stk[top--])
{
vis[y]=false;
if(x==y) break;
large[k]++;which[y]=k;
}
return;
}
}
struct edge{
int u,v;}E[M];
inline bool cmp(edge x,edge y) {
return x.u<y.u||x.u==y.u&&x.v<y.v;}
int f[N][2],maxn;
signed main()
{
n=read();m=read();mod=read();
for(register int i=1;i<=m;i++) a=read(),b=read(),adde(a,b);
for(register int i=1;i<=n;i++) if(!dfn[i]) Tarjan(i);
for(register int i=1;i<=n;i++)
{
f[i][0]=large[i];f[i][1]=1;
for(register int j=le[i];j;j=e[j].next)
{
int x=which[i],y=which[e[j].to];
if(x==y) continue;//处理自环
addg(x,y);
}
}
for(register int x=k;x>=1;x--)//缩点的顺序就是逆拓扑序,可以直接倒序循环
for(register int i=lg[x];i;i=g[i].next)
{
int y=g[i].to;
if(fa[y]==x) continue;//处理重边,保证每条边最多被用来转移一次
fa[y]=x;
if(f[y][0]<f[x][0]+large[y]) f[y][0]=f[x][0]+large[y],f[y][1]=f[x][1];
else if(f[y][0]==f[x][0]+large[y]) (f[y][1]+=f[x][1])%=mod;
}
int res=0;
for(register int i=1;i<=k;i++)
if(f[i][0]>maxn) maxn=f[i][0],res=f[i][1];
else if(f[i][0]==maxn) (res+=f[i][1])%=mod;
printf("%d\n%d",maxn,res);
}