1. 程式人生 > >HNOI2018做題筆記

HNOI2018做題筆記

樹形dp unique 之前 道路 bre har oid cout 等價

HNOI2018

尋寶遊戲(位運算、基數排序)

看到位運算就要按位考慮。二進制下,\(\land 1\)\(\lor 0\)沒有意義的,\(\land 0\)強制這一位變為\(0\)\(\lor 1\)強制這一位變為\(1\)

那麽如果某一位的答案要為\(0\),也就意味著:要麽同時不存在\(\land 0\)\(\lor 1\),要麽最後一個\(\land 0\)後面不能有\(\lor 1\)。答案為\(1\)同理。

那麽對於每一位,將所有\(a_i\)在這一位上的值從右往左看作一個二進制數\(x\),將操作序列\(\land\)對應為\(1\)\(\lor\)對應為\(0\),從右往左看作一個二進制數\(op\)

,那麽如果答案的這一位為\(0\),必須滿足\(op \geq x\)。反之則要滿足\(op < x\)

那麽最後的答案就會表現為\(a \leq op < b\)的形式,答案為\(\min\{b-a , 0\}\)

那麽將\(x\)排序,每一次掃一遍找到\(a\)\(b\)就可以了。註意到給出這\(m\)個數的順序正好是從低位到高位依次給出,所以可以基數排序。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<iomanip>
//This code is written by Itst
using namespace std;

const int MOD = 1e9 + 7;
int srt[5007] , val[5007] , tmp[5007] , pot[3];
int N , M , Q;
char s[5007];

int main(){
#ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif
    scanf("%d %d %d" , &N , &M , &Q);
    for(int i = 1 ; i <= M ; ++i)
        srt[i] = i;
    int times = 1;
    for(int i = 1 ; i <= N ; ++i){
        pot[0] = pot[1] = 0;
        scanf("%s" , s + 1);
        for(int j = 1 ; j <= M ; ++j){
            ++pot[s[j] - 47];
            if(s[j] - 48)
                val[j] = (val[j] + times) % MOD;
        }
        for(int j = 1 ; j <= M ; ++j)
            tmp[++pot[s[srt[j]] - 48]] = srt[j];
        memcpy(srt , tmp , sizeof(tmp));
        times = times * 2 % MOD;
    }
    srt[M + 1] = M + 1;
    val[M + 1] = times;
    while(Q--){
        scanf("%s" , s + 1);
        int L = 0 , R = M + 1;
        for(int i = 1 ; i <= M ; ++i)
            if(s[srt[i]] == '1'){
                R = i;
                break;
            }
        for(int i = M ; i ; --i)
            if(s[srt[i]] == '0'){
                L = i;
                break;
            }
        if(L > R)
            puts("0");
        else
            printf("%d\n" , (val[srt[R]] - val[srt[L]] + MOD) % MOD);
    }
    return 0;
}

轉盤(線段樹、單調棧)

很久以前對著yyb的題解做的

所以忘了……回憶一下再來補上

毒瘤(虛樹)

如果\(m=n-1\)就是簡單的樹的獨立集個數統計問題,直接樹形DP即可;但是相比於樹上獨立集,這裏多了\(11\)條約束。

註意到\(11\)比較小,可以暴力枚舉。對於每一條邊的兩個端點有\((0,1),(0,0),(1,0)\)\(1\)表示選,\(0\)表示不選)三種情況,而\((0,0),(0,1)\)兩種情況可以合成一種,即只確定其中一端不選,另一端不做限制。所以一條邊只有兩種情況,所以可以\(2^{11}\)地枚舉每條邊的狀態,每一次都在樹上DP求一遍獨立集個數。

考慮如何優化。註意到每一次只有\(22\)

個點的狀態會發生變化,故將這\(22\)個點拿出來建立虛樹。對於虛樹上的每一條邊求出其轉移系數。求轉移系數的方式就是在原樹上暴跳父親,每一次把沒有關鍵點的子樹直接拿過來合並。求轉移系數對應下面代碼裏的calc函數部分

#include<iostream>
#include<cstdio>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<vector>
#include<cmath>
//This code is written by Itst
using namespace std;

inline int read(){
    int a = 0;
    char c = getchar();
    while(!isdigit(c))
        c = getchar();
    while(isdigit(c)){
        a = a * 10 + c - 48;
        c = getchar();
    }
    return a;
}

const int MAXN = 1e5 + 13 , MOD = 998244353;
struct Edge{
    int end , upEd;
}Ed[MAXN << 1];
int head[MAXN] , DP[MAXN][2] , dep[MAXN] , dfn[MAXN] , jmp[MAXN][19];
int N , M , cntEd = 1 , cnt , ts , mr[21];

inline void addEd(int a , int b){
    Ed[++cntEd].end = b;
    Ed[cntEd].upEd = head[a];
    head[a] = cntEd;
}

#define go(a , b , c) (a = 1ll * a * (b + c) % MOD)
void dfs(int x , int p){
    dep[x] = dep[p] + 1;
    dfn[x] = ++ts;
    jmp[x][0] = p;
    for(int i = 1 ; i <= 16 ; ++i)
        jmp[x][i] = jmp[jmp[x][i - 1]][i - 1];
    DP[x][0] = DP[x][1] = 1;
    for(int i = head[x] ; i ; i = Ed[i].upEd)
        if(Ed[i].end != p)
            if(!dep[Ed[i].end]){
                dfs(Ed[i].end , x);
                go(DP[x][0] , DP[Ed[i].end][0] , DP[Ed[i].end][1]);
                DP[x][1] = 1ll * DP[x][1] * DP[Ed[i].end][0] % MOD;
            }
            else
                if(dep[Ed[i].end] > dep[x])
                    mr[++cnt] = i;
}

bool cmp(int a , int b) {return dfn[a] < dfn[b];}

inline int LCA(int x , int y){
    if(dep[x] < dep[y])
        x ^= y ^= x ^= y;
    for(int i = 16 ; i >= 0 ; --i)
        if(dep[x] - (1 << i) >= dep[y])
            x = jmp[x][i];
    if(x == y)
        return x;
    for(int i = 16 ; i >= 0 ; --i)
        if(jmp[x][i] != jmp[y][i]){
            x = jmp[x][i];
            y = jmp[y][i];
        }
    return jmp[x][0];
}

#define pb push_back
#define add(a , b) (a + b >= MOD ? a + b - MOD : a + b)
vector < int > ch[MAXN] , nd;
int dp[MAXN][2] , xs[MAXN][2][2] , st[101] , top , cntN;
bool mrk[MAXN];

void calc(int x){
    xs[x][0][0] = xs[x][1][1] = 1;
    for(int i = head[x] ; i ; i = Ed[i].upEd)
        if(!mrk[Ed[i].end] && jmp[Ed[i].end][0] == x){
            go(xs[x][0][0] , DP[Ed[i].end][1] , DP[Ed[i].end][0]);
            xs[x][1][1] = 1ll * xs[x][1][1] * DP[Ed[i].end][0] % MOD;
        }
    int p = x;
    while(!mrk[jmp[p][0]] && jmp[p][0]){
        mrk[p = jmp[p][0]] = 1;
        int P = xs[x][0][0] , Q = xs[x][0][1];
        xs[x][0][0] = add(P , xs[x][1][0]);
        xs[x][0][1] = add(Q , xs[x][1][1]);
        xs[x][1][0] = P;
        xs[x][1][1] = Q;
        for(int i = head[p] ; i ; i = Ed[i].upEd)
            if(jmp[Ed[i].end][0] == p && !mrk[Ed[i].end]){
                go(xs[x][0][0] , DP[Ed[i].end][1] , DP[Ed[i].end][0]);
                go(xs[x][0][1] , DP[Ed[i].end][1] , DP[Ed[i].end][0]);
                xs[x][1][0] = 1ll * xs[x][1][0] * DP[Ed[i].end][0] % MOD;
                xs[x][1][1] = 1ll * xs[x][1][1] * DP[Ed[i].end][0] % MOD;
            }
    }
}

void DFS(int x){
    mrk[x] = 1;
    for(int i = 0 ; i < ch[x].size() ; ++i)
        DFS(ch[x][i]);
    calc(x);
}

void init(){
    nd.pb(1);
    for(int i = 1 ; i <= cnt ; ++i){
        nd.pb(Ed[mr[i]].end);
        nd.pb(Ed[mr[i] ^ 1].end);
    }
    sort(nd.begin() , nd.end() , cmp);
    cntN = unique(nd.begin() , nd.end()) - nd.begin();
    for(int i = 0 ; i < cntN ; ++i){
        if(top){
            int t = LCA(st[top] , nd[i]);
            while(top - 1 && dep[t] <= dep[st[top - 1]]){
                ch[st[top - 1]].pb(st[top]);
                --top;
            }
            if(dep[t] < dep[st[top]]){
                ch[t].pb(st[top]);
                st[top] = t;
            }
        }
        st[++top] = nd[i];
    }
    while(top - 1){
        ch[st[top - 1]].pb(st[top]);
        --top;
    }
    DFS(1);
}

int sum , zt[MAXN];
void getans(int x){
    dp[x][0] = zt[x] == 0 || zt[x] == 1;
    dp[x][1] = zt[x] == 0 || zt[x] == 2;
    for(int i = 0 ; i < ch[x].size() ; ++i){
        getans(ch[x][i]);
        go(dp[x][0] , dp[ch[x][i]][0] , dp[ch[x][i]][1]);
        dp[x][1] = 1ll * dp[x][1] * dp[ch[x][i]][0] % MOD;
        dp[ch[x][i]][0] = dp[ch[x][i]][1] = 0;
    }
    int p = dp[x][0] , q = dp[x][1];
    dp[x][0] = (1ll * xs[x][0][0] * p + 1ll * xs[x][0][1] * q) % MOD;
    dp[x][1] = (1ll * xs[x][1][0] * p + 1ll * xs[x][1][1] * q) % MOD;
}

void Dfs(int x){
    if(x > cnt){
        getans(1);
        sum = (0ll + sum + dp[1][0] + dp[1][1]) % MOD;
        dp[1][0] = dp[1][1] = 0;
        return;
    }
    int l = Ed[mr[x]].end , r = Ed[mr[x] ^ 1].end , p = zt[l] , q = zt[r];
    if((p == 0 || p == 1) && (q == 0 || q == 2)){
        zt[l] = 1; zt[r] = 2;
        Dfs(x + 1);
        zt[l] = p; zt[r] = q;
    }
    if(q == 0 || q == 1){
        zt[r] = 1;
        Dfs(x + 1);
        zt[r] = q;
    }
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif
    N = read();
    M = read();
    for(int i = 1 ; i <= M ; ++i){
        int a = read() , b = read();
        addEd(a , b);
        addEd(b , a);
    }
    dfs(1 , 0);
    init();
    Dfs(1);
    cout << sum;
    return 0;
}

遊戲(拓撲排序)

不難想到對於一段中間的門都不需要鑰匙的區間縮成一個點,然後預處理每一個點能夠到達的左右端點

暴力的思路是每一次暴力向左右拓展

但可以知道:對於門\((x,y)\),如果\(y \leq x\),那麽一定是從\(x\)所在區間通過這扇門走向\(x+1\)所在區間,反之亦然。那麽若左邊的某個區間能夠通過這扇門,它一定能夠到達\(x+1\)所在區間能夠到達的所有區間。所以連邊\((x,x+1)\)表示要先拓展\(x+1\),後拓展\(x\)。然後拓撲排序決定拓展順序,一個個拓展就可以過了。復雜度似乎是線性的。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<ctime>
#include<cctype>
#include<algorithm>
#include<cstring>
#include<iomanip>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#include<stack>
#include<vector>
#include<cmath>
#include<cassert>
//This code is written by Itst
using namespace std;

inline int read(){
    int a = 0;
    char c = getchar();
    bool f = 0;
    while(!isdigit(c) && c != EOF){
        if(c == '-')
            f = 1;
        c = getchar();
    }
    if(c == EOF)
        exit(0);
    while(isdigit(c)){
        a = a * 10 + c - 48;
        c = getchar();
    }
    return f ? -a : a;
}

const int MAXN = 1e6 + 7;
vector < int > door , ch[MAXN];
int lft[MAXN] , rht[MAXN] , L[MAXN] , R[MAXN] , in[MAXN];
int N , M;

inline void add(int x , int y){
    ++in[y];
    ch[x].push_back(y);
}

queue < int > q , work;
void TopSort(){
    for(int i = 1 ; i <= M ; ++i)
        if(!in[i])
            q.push(i);
    while(!q.empty()){
        int t = q.front();
        q.pop();
        work.push(t);
        for(int i = 0 ; i < ch[t].size() ; ++i)
            if(!--in[ch[t][i]])
                q.push(ch[t][i]);
    }
}

inline int find(int x){
    return lower_bound(door.begin() , door.end() , x) - door.begin();
}

inline void calc(int x){
    while(L[x] != 1 || door[R[x]] != N){
        if(L[x] != 1 && rht[door[L[x] - 1] + 1] && rht[door[L[x] - 1] + 1] <= door[R[x]]){
            L[x] = L[L[x] - 1];
            continue;
        }
        if(door[R[x]] != N && lft[door[R[x]]] >= door[L[x] - 1] + 1){
            R[x] = R[R[x] + 1];
            continue;
        }
        break;
    }
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("in","r",stdin);
    //freopen("out","w",stdout);
#endif
    N = read();
    M = read();
    int Q = read();
    for(int i = 1 ; i <= M ; ++i){
        int x = read() , y = read();
        y <= x ? lft[x] = y : rht[x + 1] = y;
        door.push_back(x);
    }
    ++M;
    door.push_back(0);
    door.push_back(N);
    sort(door.begin() , door.end());
    for(int i = 1 ; i <= M ; ++i){
        L[i] = R[i] = i;
        if(rht[door[i - 1] + 1])
            add(i - 1 , i);
        if(lft[door[i]])
            add(i + 1 , i);
    }
    TopSort();
    while(!work.empty()){
        calc(work.front());
        work.pop();
    }
    while(Q--){
        int l = find(read()) , r = read();
        puts(door[L[l] - 1] + 1 <= r && door[R[l]] >= r ? "YES" : "NO");
    }
    return 0;
}

排列(貪心、並查集)

首先考慮合法排列的限制條件,也就是在排列\(p\)中,對於\(\forall i \in [1,N]\) , \(a_i\)要出現在\(i\)的前面。我們連邊\((i,a_i)\),表示\(i\)要在\(a_i\)之後出現。如果存在合法的排列,最後連成的一定是一棵樹。

接下來考慮求最大值。有一種比較naive的貪心:每一次都選擇當前可以選擇的點中最小的。這種貪心策略顯然是錯的,反例也很好舉。

但這似乎能給我們一些啟發:對於當前所有點權中最小的數,它一定會在它的父親被選完之後立即選。那麽考慮將它和它的父親縮成一個點,表示這兩個點的選擇是連續的。

那麽接下來的問題就是縮成的這個點的權值是多少。

不妨設當前所有點權中最小點權對應的點權為\(x\),它的父親權為\(y\),有另一個點權為\(z\),並且滿足\(y\)對應的點和\(z\)對應的點現在都可以選。那麽現在有兩種決策:1、先選\(y,x\),後選\(z\),權值和為\(y+2x+3z\);2、先選\(z\),後選\(y,x\),權值和為\(z+2y+3x\)

我們需要取更優的,所以只需要比較它們的大小,故同時減去\(x-z\),除以\(2\),那麽第一種方案權值為\(\frac{x+y}{2}+2z\),第二種方案權值為\(z + 2\frac{x+y}{2}\)

可以發現之前的兩個點\(x,y\)在這個時候可以等價為一個點權為\(\frac{x+y}{2}\)的點。根據這個思路可以得到點權為\(\frac{\text{它所包含的點的點權和}}{\text{它所包含的點的個數}}\)

那麽可以得到一個貪心:每一次選擇一個點權最小的點,把它和它父親合並,可以使用並查集維護合並過程。最後把合並的點拆開計算答案。

#include<bits/stdc++.h>
//This code is written by Itst
using namespace std;

inline int read(){
    int a = 0;
    char c = getchar();
    bool f = 0;
    while(!isdigit(c)){
        if(c == '-')
            f = 1;
        c = getchar();
    }
    while(isdigit(c)){
        a = (a << 3) + (a << 1) + (c ^ '0');
        c = getchar();
    }
    return f ? -a : a;
}

const int MAXN = 5e5 + 10;
struct Edge{
    int end , upEd;
}Ed[MAXN << 1];
int head[MAXN] , pre[MAXN << 1] , fa[MAXN << 1] , ch[MAXN << 1][2] , size[MAXN << 1] , cntEd , cntNode , N , t;
long long pri[MAXN << 1] , ans;
bool vis[MAXN] , mark[MAXN << 1];
struct cmp{
    bool operator ()(int a , int b){
        return pri[a] * size[b] > pri[b] * size[a];
    }
};
priority_queue < int , vector < int > , cmp > q;

int find(int x){
    return fa[x] == x ? x : (fa[x] = find(fa[x]));
}

inline void addEd(int a , int b){
    Ed[++cntEd].end = b;
    Ed[cntEd].upEd = head[a];
    head[a] = cntEd;
}

bool dfs(int x , int p){
    pre[x] = p;
    vis[x] = 1;
    for(int i = head[x] ; i ; i = Ed[i].upEd)
        if(Ed[i].end != p)
            if(vis[Ed[i].end] || dfs(Ed[i].end , x))
                return 1;
    return 0;
}

void color(int x){
    if(x <= N)
        ans += t++ * pri[x];
    else{
        color(ch[x][0]);
        color(ch[x][1]);
    }
}

int main(){
#ifndef ONLINE_JUDGE
    freopen("in" , "r" , stdin);
    //freopen("out" , "w" , stdout);
#endif
    cntNode = N = read();
    size[0] = 1;
    for(int i = 1 ; i <= N ; ++i){
        int a = read();
        addEd(a , i);
        addEd(i , a);
        fa[i] = i;
        size[i] = 1;
    }
    for(int i = 1 ; i <= N ; ++i){
        pri[i] = read();
        q.push(i);
    }
    for(int i = 0 ; i <= N ; ++i)
        if(!vis[i])
            if(dfs(i , -1)){
                puts("-1");
                return 0;
            }
    while(!q.empty()){
        int t = q.top();
        q.pop();
        if(mark[t])
            continue;
        int f = find(pre[t]) , x = ++cntNode;
        fa[f] = fa[t] = fa[x] = x;
        size[x] = size[f] + size[t];
        pri[x] = pri[f] + pri[t];
        ch[x][0] = f;
        ch[x][1] = t;
        pre[x] = pre[f];
        mark[t] = mark[f] = 1;
        if(pre[x] != -1)
            q.push(x);
    }
    color(cntNode);
    cout << ans;
    return 0;
}

道路(樹形DP)

\(f_{i,j,k}\)表示從\(1\)號點到\(i\)號點經過了\(j\)條公路、\(k\)條鐵路時,\(i\)及其子樹的最小不便利值和,從底往上DP,轉移枚舉修哪一條邊。

#include<bits/stdc++.h>
//This code is written by Itst
using namespace std;

inline int read(){
    int a = 0;
    bool f = 0;
    char c = getchar();
    while(c != EOF && !isdigit(c)){
        if(c == '-')
            f = 1;
        c = getchar();
    }
    while(c != EOF && isdigit(c)){
        a = (a << 3) + (a << 1) + (c ^ '0');
        c = getchar();
    }
    return f ? -a : a;
}

const int MAXN = 20010;
long long dp[1010][41][41] , num[MAXN][3];
int ch[MAXN][2] , headSt , N;

void dfs(int now){
    ++headSt;
    if(now < 0){
        now = -now;
        for(int i = 0 ; i <= 40 ; ++i)
            for(int j = 0 ; j <= 40 ; ++j)
                dp[headSt][i][j] = (num[now][0] + i) * (num[now][1] + j) * num[now][2];
        return;
    }
    dfs(ch[now][0]);
    dfs(ch[now][1]);
    for(int i = 0 ; i < 40 ; ++i)
        for(int j = 0 ; j < 40 ; ++j)
            dp[headSt - 2][i][j] = min(dp[headSt - 1][i + 1][j] + dp[headSt][i][j] , dp[headSt - 1][i][j] + dp[headSt][i][j + 1]);
    headSt -= 2;
}

int main(){
    N = read();
    for(int i = 1 ; i < N ; i++){
        ch[i][0] = read();
        ch[i][1] = read();
    }
    for(int i = 1 ; i <= N ; i++){
        num[i][0] = read();
        num[i][1] = read();
        num[i][2] = read();
    }
    dfs(1);
    cout << dp[1][0][0];
    return 0;
}

HNOI2018做題筆記