ZOJ 3795 Grouping 强联通+偏序集

题目链接:http://acm.zju.edu.cn/onlinejudge/showProblem.do?problemCode=3795

http://vjudge.net/contest/view.action?cid=52151#problem/H

浙大月赛题


1.题意:

有N个人,M条边,边(u,v)表示u的年龄不小于v,若把这N个人分为很多组,要求每一组中的年龄无法相互比较,求组数最小值。

2.题解:

(1)掌握强联通、偏序集(链和反链)的基础知识。

(2)设(X,<=)是一个有限偏序集。反链是X的一个子集A,它的任意两个元素都不可比。相比之下,是X的一个子集C,他的每个元素都可比。本题就是求反链数的最小值。

(3)定理设(X,<=)是一个有限偏序集,而设r是的链的最大大小。则X可以被划分成r个反链,但不能划分成少于r个反链。

证明:假如已经找到了p个反链,自然就可以找到>=p个反链,只要把当前其中任意一个大于2的反链拆成2个反链,就可行。所以只要证明X可以分为r个反链即可。令X1=X,并设A1是X的极小元的集合。从X1中删除A1个元素的到X2,对于X2中的每一个元素,存在A1的某个元素在这个偏序之下在这个元素下方(同一条链上的元素有偏序关系,每个非极小元都可以找到对应的极小元)。所以这样分下去可以得到A1|A2|A3|...|Ap,对于Aj中的每个元素,总可以从Aj-1中找到某个偏序关系在它下方的元素(2<=j<=p)。这样就得到一个链a1<a2<a3<...<ap。由于r是链的最大长度,所以p<=r,又因为x可以被划分为p个反链,所以有r<=p。因此r=p定理得证。(这句话要仔细想想)

(4)所以本题就是找一个最长链,用长度r就是答案。

(5)但是题目可以有环,因为偏序关系是“不小于”。所以一开始用强联通缩点,缩点后每个强连通分量的点权就是这个强联通分量的点数。

(6)强联通分量用Tarjan,找最长链就是一个DFS(DP)。


code:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <stack>
using namespace std;
const int MAXN=111111,MAXM=333333;
struct Edge{
    int from,to,next;
    Edge(){}
    Edge(int f,int t,int n):from(f),to(t),next(n){}
};
struct SCC{
    Edge edge[MAXM];
    int head[MAXN],tot,n,pre[MAXN],lowlink[MAXN],sccno[MAXN],dfs_clock,scc_cnt;
    stack<int>S;
    void init(){
        memset(head,-1,sizeof head);
        tot=0;
    }
    void add(int f,int t){
        edge[tot]=Edge(f,t,head[f]);
        head[f]=tot++;
    }
    void dfs(int u){
        pre[u]=lowlink[u]=++dfs_clock;
        S.push(u);
        for(int p=head[u];~p;p=edge[p].next){
            int v=edge[p].to;
            if(!pre[v]){
                dfs(v);
                lowlink[u]=min(lowlink[u],lowlink[v]);
            }else if(!sccno[v]){
                lowlink[u]=min(lowlink[u],pre[v]);
            }
        }
        if(lowlink[u]==pre[u]){
            scc_cnt++;
            for(;;){
                int x=S.top();S.pop();
                sccno[x]=scc_cnt;
                if(x==u)break;
            }
        }
    }
    void find_scc(int _n){
        n=_n;
        dfs_clock=scc_cnt=0;
        memset(sccno,0,sizeof sccno);
        memset(pre,0,sizeof pre);
        while(!S.empty()) S.pop();
        for(int i=1;i<=n;i++)
            if(!pre[i])dfs(i);
    }
}ga;
struct Graph{
    Edge edge[MAXM];
    int head[MAXN],tot,dp[MAXN],w[MAXN];
    bool vis[MAXN];
    void init(){
        memset(head,-1,sizeof head);
        memset(dp,0,sizeof dp);
        memset(vis,0,sizeof vis);
        memset(w,0,sizeof w);
        tot=0;
    }
    void add(int f,int t){
        edge[tot]=Edge(f,t,head[f]);
        head[f]=tot++;
    }
    int dfs(int u){
        if(vis[u]) return dp[u];
        vis[u]=1;
        int ret=0;
        for(int p=head[u];~p;p=edge[p].next){
            int v=edge[p].to;
            ret=max(ret,dfs(v));
        }
        return dp[u]=ret+w[u];
    }
}gb;

int main()
{
//    freopen("data.in","r",stdin);
    int N,M,u,v;
    while(scanf("%d%d",&N,&M)==2){
        ga.init();
        for(int i=0;i<M;i++){
            scanf("%d%d",&u,&v);
            ga.add(u,v);
        }
        ga.find_scc(N);

        gb.init();
        for(int u=1;u<=N;u++){
            for(int p=ga.head[u];~p;p=ga.edge[p].next){
                int x=ga.sccno[u];
                int y=ga.sccno[ga.edge[p].to];
                if(x!=y)
                    gb.add(x,y);
            }
        }
        for(int u=1;u<=N;u++){
            int x=ga.sccno[u];
            gb.w[x]++;
        }

        int ans=0;
        for(int u=1;u<=ga.scc_cnt;u++)
            ans=max(ans,gb.dfs(u));
        printf("%d\n",ans);
    }
    return 0;
}


你可能感兴趣的:(dp,强联通,偏序)