POJ - 2762 && HDU 6165 Tarjan缩点+dp最长路

题目

      POJ 2762
      HDU 6165

分析

      最开始想的是拓扑排序,因为拓扑序列满足如果一个点u可以到v,那么u一定在v的前面。如果求出了拓扑序列,发现序列中u在v的前面,那么就可以认为u可以到达v了。

      但是随后想到两个问题

  1. 如果有环的怎么办呢,就不能求拓扑序列了。
  2. POJ - 2762 && HDU 6165 Tarjan缩点+dp最长路_第1张图片虽然这个图存在拓扑序列1-2-3,但是我们能仅仅因为1在2的前面就认为1可以到2吗?显然不行。也就是说拓扑排序只能断言u不能到v,但是不能断言u可以到v

      第一个问题(图中有环,无法拓扑排序)很简单,用Tarjan算法把强联通分量缩成一个点即可,缩点后的图肯定是没有环的。Tarjan(黑)算(匣)法(子)模板如下

int DFN[N], LOW[N];
stack<int> Stack;
bool InStack[N];
int belong[N]; // belong[u]为原图u在缩点后的新图中的编号
int scc_num; // 强连通分量的数目
int index;

void dfs(int u)
{
    DFN[u] = LOW[u] = ++index;
    InStack[u] = true;
    Stack.push(u);
    for (unsigned i= 0; i < G1[u].size(); i++)
    {
        int v = G1[u][i];
        if (!DFN[v])
        {
            dfs(v);
            LOW[u] = min(LOW[u], LOW[v]);
        }
        else if (InStack[v])
            LOW[u] = min(LOW[u], DFN[v]);
    }
    if (DFN[u] == LOW[u]) // 找到了一个新的强连通分量
    {
        scc_num++; // 强连通分量数目
        int tmp;
        do // 这个循环可以输出当前强连通分量,tmp为连通分量内的点。
        {
            tmp = Stack.top();
            Stack.pop();
            belong[tmp] = scc_num; // 原图tmp这个点属于第scc_num个分量
            InStack[tmp] = false;
        }while (u != tmp);
    }
}

void Tarjan()
{
    memset(InStack, false, sizeof(InStack));
    while (!Stack.empty()) Stack.pop();
    memset(DFN, 0, sizeof(DFN));
    memset(LOW, 0, sizeof(LOW));
    scc_num = 0;
    index = 0;
    for (int i = 1; i <= n; i++)
        if (!DFN[i])
            dfs(i);
}

      第二个问题(如何判断图的单向联通性。即任意两个点u,v。u到v或v到u的路径至少存在一条)其实也很好解决,虽然拓扑序列不能断言一个点可以到达另一个点,但是我们可以从另一个角度利用拓扑排序。回忆一下拓扑排序的过程,每次选择一个入度为0的点放进拓扑序列,并且把这个点相关的边都删除掉。那么如果我们同时找到了两个入度为0的点说明了什么呢?那就说明这两个点只能通过之前删除的点到达,这两个点互相是不能到达的,这样一来就解决了问题。

      其实判断图的单向联通性还可以通过最长路来判断。我们都知道如果一个图是强联通图,那么一定存在一条经过所有点至少一次的回路。类似的单向联通图有没有什么性质呢?其实有的,那就是单向联通图最长路的长度等于点的数目

      下面来证明一下这个定理。假设有一个9个点的DAG,最长路为8,即1->2->3……->8,如下图。
POJ - 2762 && HDU 6165 Tarjan缩点+dp最长路_第2张图片
      现在想想我们要证明的是什么,我们要证明的是9号点不可能和其他8个点全部单向联通,否则这个图就是单向联通图了。使用反证法,假设9号点和其他8个点全部单向联通,来推出矛盾。
      首先考虑1和9。因为1和9单向联通,所以1和9之间肯定有边,而且方向肯定不是9->1,否则最长路的长度就变成了9,所以边是1->9。
POJ - 2762 && HDU 6165 Tarjan缩点+dp最长路_第3张图片
      再来考虑8和9。8,9之间的边肯定不是8->9,否则最长路的长度就变成了9,所以边是9->8。
POJ - 2762 && HDU 6165 Tarjan缩点+dp最长路_第4张图片
再来考虑2和9。2,9之间的边肯定不是9->2,否则最长路的长度就变成了9,所以边是2->9。
POJ - 2762 && HDU 6165 Tarjan缩点+dp最长路_第5张图片
      依次类推,可以推导出下图。
POJ - 2762 && HDU 6165 Tarjan缩点+dp最长路_第6张图片
      但是我们发现,虽然每一步都在防止最长路变成9,但是最后这个图的最长路还是变成了9。所以不论如何,只要我们假定9号点和其他八个点都单向联通,最后肯定会推出矛盾!
      所以最后的结论就是:如果DAG是单向联通图,则该DAG的最长路长度等于点的数目。后面附上最长路的代码。

代码

#include 
#include 
#include 
#include 

using namespace std;
const int INF = 0x3f3f3f3f;
const int N = 1005;
vector<int> G1[N];
vector<int> G2[N];
int flag[N][N];
int dp[N];
int n, m;

int DFN[N], LOW[N];
stack<int> Stack;
bool InStack[N];
int belong[N];
int scc_num;
int index;

void dfs(int u)
{
    DFN[u] = LOW[u] = ++index;
    InStack[u] = true;
    Stack.push(u);
    for (unsigned i= 0; i < G1[u].size(); i++)
    {
        int v = G1[u][i];
        if (!DFN[v])
        {
            dfs(v);
            LOW[u] = min(LOW[u], LOW[v]);
        }
        else if (InStack[v])
            LOW[u] = min(LOW[u], DFN[v]);
    }
    if (DFN[u] == LOW[u])
    {
        scc_num++;
        int tmp;
        do
        {
            tmp = Stack.top();
            Stack.pop();
            belong[tmp] = scc_num;
            InStack[tmp] = false;
        }while (u != tmp);
    }
}

void Tarjan()
{
    memset(InStack, false, sizeof(InStack));
    while (!Stack.empty()) Stack.pop();
    memset(DFN, 0, sizeof(DFN));
    memset(LOW, 0, sizeof(LOW));
    scc_num = 0;
    index = 0;
    for (int i = 1; i <= n; i++)
        if (!DFN[i])
            dfs(i);
}

int DP(int u)
{
    if (dp[u]) return dp[u];
    int ans = 1;
    for (unsigned i = 0; i < G2[u].size(); i++)
    {
        int v = G2[u][i];
        ans= max(ans, DP(v) + 1);
    }
    return dp[u] = ans;
}

int main()
{
    //freopen("test.txt", "r", stdin);
    //freopen("out.txt", "w", stdout);
    int T;
    scanf("%d", &T);
    while (T--)
    {
        scanf("%d%d", &n, &m);
        for (int i = 0; i < N; i++)
        {
            G1[i].clear();
            G2[i].clear();
        }
        for(int i = 0; i < m; i++)
        {
            int u, v;
            scanf("%d%d", &u, &v);
            G1[u].push_back(v);
        }
        Tarjan();
        memset(flag, 0, sizeof(flag));
        for (int u = 1; u <= n; u++)
            for (unsigned i = 0; i < G1[u].size(); i++)
            {
                int v = G1[u][i];
                if (!flag[belong[u]][belong[v]] && belong[u] != belong[v])
                {
                    flag[belong[u]][belong[v]] = 1;
                    G2[belong[u]].push_back(belong[v]);
                }
            }
        int ans = -1;
        memset(dp, 0, sizeof(dp));
        for (int i = 1; i <= scc_num; i++)
            ans = max(ans, DP(i));
        if (ans == scc_num) printf("Yes\n");
        else printf("No\n");
    }
    return 0;
}

你可能感兴趣的:(ACM-基础dp,======图论======)