【bzoj1093】【zjoi2007】【最大半联通子图】【缩点+dp】

Description

Input

第一行包含两个整数N,M,X。N,M分别表示图G的点数与边数,X的意义如上文所述。接下来M行,每行两个正整数a, b,表示一条有向边(a, b)。图中的每个点将编号为1,2,3…N,保证输入中同一个(a,b)不会出现两次。

Output

应包含两行,第一行包含一个整数K。第二行包含整数C Mod X.

Sample Input

6 6 20070603
1 2
2 1
1 3
2 4
5 6
6 4

Sample Output

3
3

HINT

对于100%的数据, N ≤100000, M ≤1000000;对于100%的数据, X ≤10^8。

题解:缩点之后发现最长路就是答案。跑的过程中记录一下方案数。跑最长路的时候需要判重。

具体方法是记录一下每个点上次是由哪个点更新过来的,如果一样就跳过。

一开始数组开小了。。看了两遍竟然没看出来。。。。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define N 100010
#define M 1000010
int s[N],last[N],cnt,n,m,pre[N],a,b,low[N],point[N],next[M],scc[N],ss,top,size[N];
int point2[N],next2[M],cnt2,temp,d[N],p,l[N*10],h,t,dis[N],sum[N],ans2,ans;
bool f[N];
using namespace std;
struct use{int st,en;}e[M];
struct use2{int st,en;}c[M];
inline int read()  
{  
    int x=0,f=1;char ch=getchar();  
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}  
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}  
    return x*f;  
}  
void add(int x,int y)
{
   next[++cnt]=point[x];point[x]=cnt;
   e[cnt].st=x;e[cnt].en=y;
}
void add2(int x,int y)
{
   d[y]++;
   next2[++cnt2]=point2[x];point2[x]=cnt2;
   c[cnt2].st=x;c[cnt2].en=y;	
}
void dfs(int x)
{
  pre[x]=low[x]=++ss;s[++top]=x;
  for (int i=point[x];i;i=next[i])
   {
     int u=e[i].en; 
     if (!pre[u]){dfs(u);low[x]=min(low[x],low[u]);}
     else if (!scc[u]){low[x]=min(low[x],pre[u]);}
   }
  if (low[x]==pre[x])
   {
     scc[0]++;
     while (top)
     {
	   scc[s[top--]]=scc[0];
	   size[scc[0]]++;
       if (s[top+1]==x) break;
     }
   }
}
void tarjan()
{
  memset(scc,0,sizeof(scc));
  for (int i=1;i<=n;i++) if (!pre[i]) dfs(i);	
}
void rebuild()
{
  for (int i=1;i<=n;i++)
   {
    for (int j=point[i];j;j=next[j])
      if (scc[i]!=scc[e[j].en])
			 add2(scc[i],scc[e[j].en]);
   }	
}
void getans()
{
   h=0;t=1;
   memset(dis,0,sizeof(dis));
   for (int i=1;i<=scc[0];i++)if (d[i]==0) {dis[i]=size[i];sum[i]=1;l[++t]=i;}
   while (h<t)
   {
   	 int u=l[++h];
	 for (int i=point2[u];i;i=next2[i])
   	  {
   	     d[c[i].en]--;
		 if (d[c[i].en]==0) l[++t]=c[i].en;    
		 if (u==last[c[i].en]) continue;
		 last[c[i].en]=u;
		 if (dis[c[i].en]==dis[u]+size[c[i].en])
		  (sum[c[i].en]+=sum[u])%=p;
		 if (dis[c[i].en]<dis[u]+size[c[i].en]) 
		  {
		    dis[c[i].en]=dis[u]+size[c[i].en];
		    sum[c[i].en]=sum[u];
		  }    
      }
  }
}
int main()
{
   n=read();m=read();p=read();
   for (int i=1;i<=m;i++)
    {
      a=read();b=read();
      add(a,b);
    }
   tarjan();
   rebuild();
   getans();
   for (int i=1;i<=scc[0];i++) ans=max(ans,dis[i]);
   for (int i=1;i<=scc[0];i++) if (ans==dis[i]) (ans2+=sum[i])%=p;
   cout<<ans<<endl<<ans2<<endl;
}


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值