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;

}

 

你可能感兴趣的:(ZOJ)