1. 程式人生 > 其它 >abc222_e Red and Blue Tree(樹上差分+01揹包)

abc222_e Red and Blue Tree(樹上差分+01揹包)

題目連結

題目大意

  給你一棵樹,和一個序列,序列描述了在樹上走的路徑\((a_{i-1} -> a_i)\),你可以給一條邊染成紅色或者藍色,
問走過的紅色邊的條數-藍色邊的條數等於K的方案數。

解題思路

  先將式子轉換一下,設sum表示所有邊的經過次數,那麼\(R+B=sum, R-B=K\),可以得到\(R = \frac{sum-K}{2}\),很明顯\(sum-K\)得是偶數並且非負。每條邊的經過次數可以直接暴力算出來,把邊轉換成點,每個點的權值表示這個點的父親與之相連的邊路過的次數,當然也可以樹上差分(寫起來很長,也沒必要,但是順道練練)。然後每條邊相當於一個物品,求01揹包剛好裝成R時的方案數就行了。

程式碼

const int maxn = 1e3+10;
const int maxm = 1e5+10;
vector<int> e[maxn];
int arr[maxn], sub[maxn], dep[maxn], f[maxn][20];
void dfs(int u) {
    for (auto v : e[u]) {
        if (dep[v]) continue;
        dep[v] = dep[u]+1;
        f[v][0] = u;
        for (int i = 1; i<20; ++i) f[v][i] = f[f[v][i-1]][i-1];
        dfs(v);
    }
}
int lca(int x, int y) {
    if (dep[x]<dep[y]) swap(x, y);
    for (int i = 19; i>=0; --i)
        if (dep[f[x][i]]>=dep[y]) x = f[x][i];
    if (x==y) return x;
    for (int i = 19; i>=0; --i)
        if (f[x][i]!=f[y][i]) x = f[x][i], y = f[y][i];
    return f[x][0];
}
vector<int> w;
int sum, fx = 1;
void dfs2(int u, int p) {
    for (auto v : e[u]) {
        if (v==p) continue;
        dfs2(v, u);
        sub[u] += sub[v];
    }
    if (sub[u]) w.push_back(sub[u]);
    else if (u!=1) fx = fx*2%MOD;
    sum += sub[u];
}
int dp[maxm];
int main() {
    IOS;
    int n, m, k; cin >> n >> m >> k;
    for (int i = 1; i<=m; ++i) cin >> arr[i];
    for (int i = 1, a, b; i<n; ++i) {
        cin >> a >> b;
        e[a].push_back(b);
        e[b].push_back(a);
    }
    dep[1] = 1;
    dfs(1);
    for (int i = 2; i<=m; ++i) {
        ++sub[arr[i-1]];
        ++sub[arr[i]];
        sub[lca(arr[i-1], arr[i])] -= 2;
    }
    dfs2(1, -1);
    if ((sum+k)%2 || sum+k<0 || sum-k<0) {
        cout << 0 << endl;
        return 0;
    }
    sum = min(sum+k, sum-k);
    sum /= 2;
    dp[0] = 1;
    for (auto v : w)
        for (int i = sum; i>=v; --i)
            dp[i] = (dp[i-v]+dp[i])%MOD;
    cout << 1LL*dp[sum]*fx%MOD << endl;
    return 0;
}