bzoj 1093 缩点+DP
首先比较明显的是如果存在一个半连通子图,我们将其中的环缩成点,那么该图仍为半连通子图,这样我们就可以先将整张图缩点,重新构图,新图为拓扑图,记录每个新的点表示的强连通分量中点的个数num[i],那么我们就可以DP了,新图中的每一条链都为原图的半连通子图,这样我们找到新图中的最长链就行了,找入度为0的点dfs做树上DP,这样我们可以知道每个点的len[i]代表从这个点开始的最长链的长度,len[i]=max(len[son of i])+num[i],然后我们求出来了第一问,对于第二问,我们需要找len[i]=ans1的点做dfs,然后设ans[i]为以i为根的子树的方案数,那么ans[i]+=ans[son of i] (len[i]=len[son of i]+num[i]),因为我们需要找最长链上的点来更新答案,这样最后再累计答案就好了。
反思:开始题中说没有重边,但是没有考虑到重构图之后的图是可能有重边的,这样第二问的答案就可能会被重复累加,所以我们DP的时候可以维护一个栈,和每个栈中元素的父亲,这样对于一个点枚举子节点,如果子节点没有在栈中出现过,那么就累加答案,该子节点进栈,dfs最后的时候再弹出所有栈中x的子节点。
/************************************************************** Problem: 1093 User: BLADEVIL Language: C++ Result: Accepted Time:1992 ms Memory:45028 kb ****************************************************************/ //By BLADEVIL #include <cstdio> #include <algorithm> #define maxn 200020 #define maxm 4000040 using namespace std; int n,m,d39,l,tot,time,size; int last[maxn],other[maxm],pre[maxm],stack[maxn],low[maxn],dfn[maxn],flag[maxn],col[maxn],num[maxn],in[maxn]; int len[maxn],ans[maxn],father[maxn]; int p1,p2; void connect(int x,int y){ pre[++l]=last[x]; last[x]=l; other[l]=y; //if (x>n||y>n) printf("%d %d\n",x,y); } void tarjan(int x){ //printf("%d %d\n",x,fa); low[x]=dfn[x]=++time; stack[++tot]=flag[x]=x; //for (int i=1;i<=tot;i++) printf("%d ",stack[i]); printf("\n"); for (int p=last[x];p;p=pre[p]){ if (!dfn[other[p]]) tarjan(other[p]),low[x]=min(low[x],low[other[p]]); else if (flag[other[p]]) low[x]=min(low[x],dfn[other[p]]); } if (low[x]==dfn[x]){ int cur=-1; while (cur!=x){ cur=stack[tot--]; flag[cur]=0; col[cur]=size; num[size]++; } size++; } } void dfs(int x){ int cur=0; for (int p=last[x];p;p=pre[p]){ if (!len[other[p]]) dfs(other[p]); cur=max(cur,len[other[p]]); } len[x]=cur+num[x]; //printf(" %d %d\n",x,len[x]); } void work(int x){ for (int p=last[x];p;p=pre[p]) if (len[other[p]]+num[x]==len[x]) { if (flag[other[p]]) continue; if (!ans[other[p]]) work(other[p]); ans[x]+=ans[other[p]]; stack[++tot]=other[p]; flag[other[p]]=1; father[other[p]]=x; } if (!ans[x]) ans[x]=1; ans[x]%=d39; while (father[stack[tot]]==x) flag[stack[tot--]]=0; } int main(){ int x,y; scanf("%d%d%d",&n,&m,&d39); size=n+1; for (int i=1;i<=m;i++) scanf("%d%d",&x,&y),connect(x,y); for (int i=1;i<=n;i++) if (!low[i]) tarjan(i); //for (int i=1;i<=n;i++) printf("%d %d %d\n",col[i],low[i],dfn[i]); for (int i=1;i<=n;i++) for (int p=last[i];p;p=pre[p]) if (col[i]!=col[other[p]]) connect(col[i],col[other[p]]),in[col[other[p]]]++; //for (int i=n+1;i<size;i++) printf("|%d %d\n",i,num[i]); for (int i=n+1;i<size;i++) if (!in[i]) dfs(i); //for (int i=n+1;i<size;i++) printf("|%d %d\n",i,len[i]); for (int i=n+1;i<size;i++) p1=max(p1,len[i]); for (int i=n+1;i<size;i++) if (len[i]==p1) work(i); for (int i=n+1;i<size;i++) if (len[i]==p1) (p2+=ans[i])%=d39; //for (int i=n+1;i<size;i++) printf("|%d %d\n",i,ans[i]); printf("%d\n%d\n",p1,p2); return 0; }