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;
}

 

posted on 2014-02-28 18:55  BLADEVIL  阅读(281)  评论(0编辑  收藏  举报