P2272 [ZJOI2007]最大半连通子图
传送门
题目简单来说就是给一个有向图,将图转化为DAG图后,求图中最长链及最长链的个数。
思路:
①用 tarjan 缩点重构将原图转换为一个有向无环图,让后在新图上跑 topo 即可求出最长链。
②最长链的个数可以用动态规划,设 e[ i ] 表示新图中以 i 为终点的方案数,那么 e[ i ] 就等于连到 i 且满足距离等于起点到 i 的临时最长距离的点的 e 之和。最后查找距离等于最长链的点,答案就是它们的方案数之和。
标程:
#include<iostream> #include<cstdio> #include<algorithm> #include<cmath> #include<cstring> #include<string> #include<cstdlib> #include<stack> #include<vector> #include<queue> #include<deque> #include<map> #include<set> using namespace std; #define maxn 1000001 int n,m,mod; int x[maxn],y[maxn];//存边 int cnt=0; int de[maxn],to[maxn],head[maxn],nex[maxn];//链式前向星 int ue[maxn]; int t=0,w=0; int ans=0; int e[maxn]; int dis[maxn];//topo int num=0,top=0,col=0; int dfn[maxn],low[maxn],st[maxn],co[maxn],ins[maxn];//tarjan int nu[maxn];//重新记录边(去重) inline int read() { int kr=1,xs=0; char ls; ls=getchar(); while(!isdigit(ls)) { if(!(ls^45)) kr=-1; ls=getchar(); } while(isdigit(ls)) { xs=(xs<<1)+(xs<<3)+(ls^48); ls=getchar(); } return xs*kr; } inline void add(int x,int y) { nex[++cnt]=head[x]; head[x]=cnt; to[cnt]=y; } inline void tarjan(int u)//tarjan缩点 { dfn[u]=low[u]=++num; st[++top]=u; for(int i=head[u];i;i=nex[i]) { int v=to[i]; if(!dfn[v]) { tarjan(v); low[u]=min(low[u],low[v]); } else if(!co[v]) low[u]=min(low[u],dfn[v]); } if(low[u]==dfn[u]) { co[u]=++col; ++ins[col];//ins为强连通分量的大小 while(st[top]!=u) { ++ins[col]; co[st[top]]=col; --top; } --top; } } inline bool cmp(int a,int b) { if(x[a]!=x[b]) return x[a]<x[b]; return y[a]<y[b]; } inline void remove()//去除重边,否则会影响方案数的统计 { for(int i=1;i<=m;i++) { nu[i]=i; x[i]=co[x[i]]; y[i]=co[y[i]]; } sort(nu+1,nu+m+1,cmp); } inline void build_map() { remove(); //去除重边 cnt=0; memset(head,0,sizeof(head)); for(int i=1;i<=m;i++) { int z=nu[i]; if((x[z]!=y[z])&&(x[z]!=x[nu[i-1]]||y[z]!=y[nu[i-1]])) { ++de[y[z]]; add(x[z],y[z]); } } }//重构图 inline void topo() { for(int i=1;i<=col;i++) { if(!de[i]) { ue[++w]=i; dis[i]=ins[i]; e[i]=1; if(dis[ans]<dis[i]) ans=i; } }//topo排序初始入队 while(t<w) { int u=ue[++t]; for(int i=head[u];i;i=nex[i]) { int v=to[i]; --de[v]; if(dis[v]<dis[u]+ins[v])//临时最长距离被更新,重新统计方案数 { dis[v]=dis[u]+ins[v]; e[v]=0; if(dis[ans]<dis[v]) ans=v; } if(dis[v]==dis[u]+ins[v])//满足距离条件,累加方案数 { e[v]=(e[v]+e[u])%mod; } if(!de[v]) ue[++w]=v; } }//topo排序 } int main() { n=read();m=read();mod=read(); for(int i=1;i<=m;i++) { x[i]=read();y[i]=read(); add(x[i],y[i]); } for(int i=1;i<=n;i++) if(!dfn[i]) tarjan(i); build_map();//重构图 topo();//topo寻找最长链和最长链个数 int tot=0; for(int i=1;i<=n;i++) if(dis[i]==dis[ans])//统计答案 tot=(tot+e[i])%mod; printf("%d\n%d\n",dis[ans],tot);//输出最长距离、方案数 return 0; }