1. 程式人生 > >UVa #1380 A Scheduling Problem (例題9-26)

UVa #1380 A Scheduling Problem (例題9-26)

居然一次就過了。。做了兩天,淚流滿面啊。不過程式碼跑得很慢。。有機會優化一下

這道題想清楚了還是很簡單的,但是一開始的思路不容易理順。下面文中我的f和g與Rujia書中的定義相反,請注意區分。

題目裡說,把所有的無向邊去掉之後,最終答案則一定是由剩下的有向邊組成的最長路徑長度k或者k+1。

所以我們的工作就變成了:給無向邊分配方向,使得最後得到的樹裡,最長路徑長度不大於k。若可以做到則答案為k,否則答案為k+1

對於樹中某一個以結點a為根的子樹來說(即不考慮a的祖先),經過a的最長有向路經s等於下行最長路+上行最長路。我們設這兩條路的長度分別為f和g,則經過a的最長路經s的結點數為f+g+1。

轉化無向邊的過程分為兩種情況:

a. 如果一個子樹中不存在無向邊,則經過子樹的根結點的最長有向路結點數為f+g+1(recall that f是下行的最長路長度,g是上行的最長路長度)。

b. 如果一個結點a的子結點存在無向邊,則我們先遞迴求出所有a的子結點的f和g,然後暴力列舉將所有無向邊轉化為上行/下行有向邊,對於每一種列舉,按照上面不存在無向邊時的方法,求出f,  g和f+g+1,檢查是否大於k。列舉的過程中,記下所有成功的列舉中最小的f和g,把它們和原本就是有向邊子結點的最小的f和g比較,取較大值。(f和g本身是最長路的長度,這裡要取的是不同成功的列舉情況下,遇到的最小的f和g。概念有點繞,可能要多考慮一下)

但是如果我們完全列舉無向邊的轉換方法,則複雜度為O(2^n),n為子結點中無向邊的數量。這裡有一個非常棒的優化:

求出所有子結點的f和g之後,把無向邊的f和g值存到一個數組中,按照f值排序。

之後我們從第一位開始考慮,將無向邊換為下行有向邊,考慮到第p位的時候,我們可以將前p-1位的無向邊一併換為下行有向邊。因為第p位的f值大於前p-1位無向邊的f值,將前p-1位同時換為下行有向邊,整個樹的f值不會變(f為最長路的長度,而排序後,前p-1位形成的路都不會比第p位長),而g值有可能變小。這時,我們找出第p+1位到第n位中最大的g值,求出f+g+1,檢查是否小於等於k即可。一旦有某一個p滿足了要求就可以得出結果並終止列舉。複雜度為線性。

求g的過程相同,按照g值排序、列舉即可。

需要注意的細節包括

1、容易混淆邊的長度和邊上結點的數量

2、f、g值在計算的時候什麼時候要+1,什麼時候不應該+1,要仔細考慮

3、dir陣列的大小問題

4、列舉的邊界值 - 全部換成下行/上行的情況

Run Time: 0.022s

#define UVa  "LT9-26.1380.cpp"		//A Scheduling Problem
char fileIn[30] = UVa, fileOut[30] = UVa;

#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#include<iostream>

using namespace std;

//Global Variables. Reset upon Each Case!
typedef pair<int,int> Pair;
const int maxn = 200 + 5, DOWN = 0, UP = 1, UND = 2, INF = 1<<30;
int dir[150];
vector<int> G[maxn];
vector<int> Gdir[maxn];
int f[maxn], g[maxn];
int n, x, k;
int vis[maxn], len[maxn][2], lenvis[maxn][2];
/////

int dfs(int u, int direction);
void readSon(int fa);
void readG(int u);
void clearG();

void dp(int u, int& ans_f, int& ans_g) {
    if(vis[u]) {
        ans_f = f[u];
        ans_g = g[u];
        return;
    }
    vis[u] = 1;
    f[u] = g[u] = INF;
    if(G[u].size() == 0) {              //leaf node
        ans_f = ans_g = f[u] = g[u] = 0;
        return;
    }

    int f_prime = 0, g_prime = 0, f_w, g_w;
    vector<Pair> und_f, und_g;
    for(int i = 0; i < G[u].size(); i ++) {
        dp(G[u][i], f_w, g_w);
        if(Gdir[u][i] == UND) {
            und_f.push_back(Pair(f_w, g_w));
            und_g.push_back(Pair(g_w, f_w));
        }
        else if(Gdir[u][i] == DOWN)
            f_prime = max(f_prime, f_w+1);
        else if(Gdir[u][i] == UP)
            g_prime = max(g_prime, g_w+1);
    }
    //f_prime and g_prime is len of longest directed path of sons.
    //und_f and und_g is the array of undirected path.
    if(und_f.size() == 0) {
        if(f_prime + g_prime +1 <= k)           //all the '+1' in this kind of inequation is for current node.
            ans_f = f[u] = f_prime, ans_g = g[u] = g_prime;
        else
            ans_f = ans_g = f[u] = g[u] = INF;
        return;
    }

    sort(und_f.begin(), und_f.end());
    sort(und_g.begin(), und_g.end());

    int max_und_f[maxn], max_und_g[maxn];
    max_und_f[und_g.size()-1] = und_g[und_g.size() - 1].second;
    max_und_g[und_f.size()-1] = und_f[und_f.size() - 1].second;
    for(int i = und_f.size() - 2; i >= 0; i --) {
        max_und_f[i] = max(max_und_f[i+1], und_g[i].second);
        max_und_g[i] = max(max_und_g[i+1], und_f[i].second);
    }
    max_und_g[und_f.size()] = max_und_f[und_g.size()] = -1;
    //max_und_g/f is the longest path up/down in element i - n.

    if(f_prime + max(max_und_g[0]+1, g_prime) + 1 <= k) {       //turn all und into up
        f[u] = f_prime;
    }
    else
        for(int p = 0; p < und_f.size(); p ++) {
        if(max(und_f[p].first+1, f_prime) + max(max_und_g[p+1]+1, g_prime) + 1 <= k) {
            f[u] = max(und_f[p].first+1, f_prime);
            break;
        }
    }

    if(max(max_und_f[0]+1, f_prime) + g_prime +1 <= k) {     //turn all und into down
        g[u] = g_prime;
    }
    else
        for(int p = 0; p < und_g.size(); p ++) {
        if(max(max_und_f[p+1]+1, f_prime) + max(und_g[p].first+1, g_prime) + 1 <= k) {
            g[u] = max(und_g[p].first+1, g_prime);
            break;
        }
    }
    ans_f = f[u];
    ans_g = g[u];
}


int main() {
    int u;
    dir['d'] = DOWN, dir['u'] = UP, dir[' '] = UND;

    clearG();
    while(scanf("%d", &u) && u) {
        readG(u);

        //k is the # of vertices of the longest monotonically directed path in the tree.
        //the answer shuold be k or k+1.

        memset(lenvis, 0, sizeof(lenvis));
        memset(len, 0, sizeof(len));
        k = 0;
        for(int i = 1; i <= n; i ++) {
            k = max(k, dfs(i, UP)+dfs(i, DOWN)+1);
        }

        memset(f, -1, sizeof(f));
        memset(g, -1, sizeof(g));
        memset(vis, 0, sizeof(vis));
        int f_root, g_root;
        dp(1, f_root, g_root);
        if(f_root == INF || g_root == INF) k++;
        printf("%d\n", k);
        clearG();

    }

    return 0;
}


int dfs(int u, int direction) {     //calculate the value of k
    if(lenvis[u][direction]) return len[u][direction];
    lenvis[u][direction] = 1;
    len[u][direction] = 0;
    for(int i = 0; i < G[u].size(); i ++) {
        if(Gdir[u][i] == direction){
            len[u][direction] = max(len[u][direction], dfs(G[u][i], direction) + 1);
        }
    }
    return len[u][direction];
}


void readSon(int fa) {
    int son; char c;
    while( scanf("%d%c", &son, &c) && son ) {
        G[fa].push_back(son);
        Gdir[fa].push_back(dir[c]);
    }
}

void readG(int u) {
    readSon(u);
    n = 1;
    while(scanf("%d", &u) && u && n++) readSon(u);
}

void clearG() {
    for(int i = 1; i < maxn; i ++) {
        G[i].clear();
        Gdir[i].clear();
    }
}