1. 程式人生 > >NOIP2018 解題筆記

NOIP2018 解題筆記

最大的 啊啊啊 %d 積木大賽 template lap 旅行 等於 multi

D1T1 鋪設道路

  在場上並沒有想到積木大賽這道原題。

  差分之後可以把在$[l, r]$這段區間$ - 1$變成在$l$處$ - 1$,在$r + 1$處$ + 1$,然後最終目標是使$\forall i \in [1, n] \ \Delta d_i == 0$成立。就想著把正負數配一配對,然後輸出了正數絕對值和負數絕對值的$max$,這導致了我的代碼非常鬼畜。

  出來之後發現正數絕對值一定大於等於負數絕對值,要不然就會出現$d_i < 0$的情況。

  時間復雜度$O(n)$。

技術分享圖片
#include <cstdio>
#include <cstring>
using
namespace std; typedef long long ll; const int N = 1e5 + 5; int n; ll a[N], b[N]; template <typename T> inline void read(T &X) { X = 0; char ch = 0; T op = 1; for(; ch > 9 || ch < 0; ch = getchar()) if(ch == -) op = -1; for(; ch >= 0 && ch <=
9; ch = getchar()) X = (X << 3) + (X << 1) + ch - 48; X *= op; } ll abs(ll x) { return x > 0 ? x : -x; } inline ll max(ll x, ll y) { return x > y ? x : y; } int main() { // freopen("road.in", "r", stdin); // freopen("road.out", "w", stdout); read(n);
for(int i = 1; i <= n; i++) { read(a[i]); b[i] = a[i] - a[i - 1]; } /* for(int i = 1; i <= n; i++) printf("%lld ", b[i]); printf("\n"); */ ll ps = 0LL, ne = 0LL; for(int i = 1; i <= n; i++) { if(b[i] > 0) ps += b[i]; else ne -= b[i]; } printf("%lld\n", max(ne, ps)); return 0; }
road

D1T2 貨幣系統

  一開始覺得是一道數學題,後來回過頭來發現應該可以完全背包,先寫了$80$分的暴力,然後又想了一下可以用分治優化到$O(Tnlogna_i)$,就寫了一下然後拍了拍,最後在少爺機上$AC$了。

  用$solve(l, r)$表示不考慮$[l, r]$這段區間內的貨幣的情況,然後在向下算的時候每一次做一半就好了。

  出來之後發現就是個$sb$排序。

  時間復雜度$O(Tna_i)$,放上分治的代碼。

技術分享圖片
#include <cstdio>
#include <cstring>
using namespace std;

const int N = 105;
const int M = 25005;
const int Lg = 20;

int testCase, n, mx = 0, a[N], ans;
bool f[Lg][M];

inline void read(int &X) {
    X = 0; char ch = 0; int op = 1;
    for(; ch > 9 || ch < 0; ch = getchar())
        if(ch == -) op = -1;
    for(; ch >= 0 && ch <= 9; ch = getchar())
        X = (X << 3) + (X << 1) + ch - 48;
    X *= op;
}

inline void chkMax(int &x, int y) {
    if(y > x) x = y;
}

void solve(int l, int r, int dep) {
    if(l == r) {
        if(f[dep][a[l]]) ans--;
        return;
    }
    
    ++dep;
    for(int i = 0; i <= mx; i++) f[dep][i] = f[dep - 1][i];
    
    int mid = ((l + r) >> 1);
    for(int i = l; i <= mid; i++)
        for(int j = a[i]; j <= mx; j++)
            f[dep][j] |= f[dep][j - a[i]];
    solve(mid + 1, r, dep);
    
    for(int i = 0; i <= mx; i++) f[dep][i] = f[dep - 1][i];
    for(int i = mid + 1; i <= r; i++)
        for(int j = a[i]; j <= mx; j++)
            f[dep][j] |= f[dep][j - a[i]];
    solve(l, mid, dep);
}

int main() {
//    freopen("money.in", "r", stdin);
//    freopen("money.out", "w", stdout);
    
    for(read(testCase); testCase--; ) {
        read(n);
        mx = 0;
        for(int i = 1; i <= n; i++) {
            read(a[i]);
            chkMax(mx, a[i]);
        }
        
        for(int i = 1; i <= mx; i++) f[0][i] = 0;
        f[0][0] = 1;
        
        ans = n;
        solve(1, n, 0);
         
        printf("%d\n", ans);
    }    
    return 0;
}
money

D1T3 賽道修建

  挺簡單的第三題。首先外層二分,然後把一個點的所有兒子存下來,貪心從小到大匹配,在保證對答案的貢獻最大的情況下使延伸到父親處理的鏈盡量長。我在考場上寫了一個常數巨大的$multiset$,也能跑過。

  其實也可以再二分一個能向上的最長鏈然後看看答案會不會變差就行了,這樣子常數比較優秀。

  仍然不會更高級的解法。

  時間復雜度$O(nlog^2n)$。

技術分享圖片
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <set>
using namespace std;

const int N = 5e4 + 5;
const int inf = 1 << 30;

int n, m, cnt, lim, tot = 0, head[N], f[N];
multiset <int> s[N];

struct Edge {
    int to, nxt, val;
} e[N << 1];

inline void add(int from, int to, int val) {
    e[++tot].to = to;
    e[tot].val = val;
    e[tot].nxt = head[from];
    head[from] = tot;
}

inline void read(int &X) {
    X = 0; char ch = 0; int op = 1;
    for(; ch > 9 || ch < 0; ch = getchar())
        if(ch == -) op = -1;
    for(; ch >= 0 && ch <= 9; ch = getchar())
        X = (X << 3) + (X << 1) + ch - 48;
    X *= op;
}

inline void chkMax(int &x, int y) {
    if(y > x) x = y;
}

/*  inline int getPos(int which, int siz, int val) {
    val = lim - val;
    int ln = 0, rn = siz, mid, res = -1;
    for(; ln <= rn; ) {
        mid = ((ln + rn) >> 1);
        if(vec[which][mid] >= val) res = mid, rn = mid - 1;
        else ln = mid + 1;
    }    
    return res;
}   */

/*   void dfs(int x, int fat) {
    vec[x].clear();
    for(int i = head[x]; i; i = e[i].nxt) {
        int y = e[i].to;
        if(y == fat) continue;
        dfs(y, x);
        vec[x].push_back(f[y] + e[i].val);
    }
    
    sort(vec[x].begin(), vec[x].end());
    int vecSiz = vec[x].size() - 1;
    for(; vecSiz; --vecSiz) {
        if(vecSiz == -1) break;
        if(vec[x][vecSiz] < lim) break;
        ++cnt;
    }
    for(int i = 0; i <= vecSiz; i++) tag[i] = 0;
    for(int i = vecSiz; i > 0; i--) {
        int pos = getPos(x, i - 1, vec[x][i]);
        if(pos != -1) tag[pos] = 1, tag[i] = 1, ++cnt;
    }
    
    int res = 0;
    for(int i = 0; i <= vecSiz; i++)
        if(tag[i]) tag[i] = 0;
        else chkMax(res, vec[x][i]);
    
    f[x] = res;
}    */

void dfs(int x, int fat) {
//    s[x].clear();
    for(int i = head[x]; i; i = e[i].nxt) {
        int y = e[i].to;
        if(y == fat) continue; 
        dfs(y, x);
        s[x].insert(f[y] + e[i].val);
    } 
    
    for(; !s[x].empty(); ) {
        multiset <int> :: iterator it = (--s[x].end());
        if((*it) >= lim) {
            ++cnt;
            s[x].erase(it);
        } else break;
    }
    
    int res = 0;
    for(; !s[x].empty(); ) {
        multiset <int> :: iterator p1 = s[x].begin();
        int tmp = (*p1);
        s[x].erase(p1);        
        multiset <int> :: iterator p2 = s[x].lower_bound(lim - tmp);

        if(p2 == s[x].end()) {
            chkMax(res, tmp);
        } else {
            cnt++;
            s[x].erase(p2); 
        }
    }
    
    f[x] = res;
}

inline bool chk(int mid) {
    lim = mid, cnt = 0;
    for(int i = 1; i <= n; i++) f[i] = 0;
    dfs(1, 0);
    return cnt >= m;
}

int main() {
//    freopen("track.in", "r", stdin);
//    freopen("track.out", "w", stdout);
    
    read(n), read(m);
    int ln = 0, rn = 0, mid, res = inf;
    for(int x, y, v, i = 1; i < n; i++) {
        read(x), read(y), read(v);
        add(x, y, v), add(y, x, v);
        rn += v;
    }
    
    for(; ln <= rn; ) {
        mid = (ln + rn) / 2;
        if(chk(mid)) ln = mid + 1, res = mid;
        else rn = mid - 1;
    }   
    
    printf("%d\n", res);
    return 0;
}
track

D2T1 旅行

  原來以為圖論跑到$T1$來了,沒想到最後還是個樹。

  先考慮樹的部分分,發現只有走完一個點的所有子樹之後才能向上走,所以每次貪心地走最小的子樹就好了;然後考慮基環樹的部分分,看到$n$不超過$5000$,直接把環找出來然後斷一斷走一走取個最小就好了。

  場上寫出了一個$bug$但只被卡了$4$分。

  樹的話時間復雜度是$O(nlogn)$,基環樹是$O(n^2)$。

技術分享圖片
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;

const int N = 5005;
const int inf = 1 << 30;

int n, m, cnt = 0, ans[N];
vector <int> e[N];

inline void read(int &X) {
    X = 0; char ch = 0; int op = 1;
    for(; ch > 9 || ch < 0; ch = getchar())
        if(ch == -) op = -1;
    for(; ch >= 0 && ch <= 9; ch = getchar())
        X = (X << 3) + (X << 1) + ch - 48;
    X *= op;
}

namespace Solve1 {
    
    void dfs(int x, int fat) {
        int vecSiz = e[x].size();
        for(int i = 0; i < vecSiz; i++) {
            int y = e[x][i];
            if(y == fat) continue;
            ans[++cnt] = y;
            dfs(y, x);
        }
    }
    
    void work() {        
        ans[++cnt] = 1;
        dfs(1, 0);
        
        for(int i = 1; i <= n; i++) {
            printf("%d", ans[i]);
            if(i == n) putchar(\n);
            else putchar( );
        }
    }
    
}

namespace Solve2 {
    int top, stk[N], sum, cir[N], dx, dy, res[N];
    bool inc[N], vis[N], flag = 0;
    
    void getCir(int x, int fat) {
//        int pre = top;
        if(flag) return;
        stk[++top] = x, vis[x] = 1;
        int vecSiz = e[x].size();
        for(int i = 0; i < vecSiz; i++) {
            int y = e[x][i];
            if(y == fat) continue;
            if(vis[y]) {
                flag = 1;
                sum = 0;
                for(; stk[top] != y; --top) {
                    inc[stk[top]] = 1;
                    vis[stk[top]] = 0;
                    cir[++sum] = stk[top];
                }
                inc[y] = 1, cir[++sum] = y, vis[y] = 0, top--;
                return;
            } else getCir(y, x);
        }
//        top = pre;
        if(vis[x]) --top, vis[x] = 0;
    }
    
    inline bool bet() {
        for(int i = 1; i <= n; i++)
            if(res[i] != ans[i]) return res[i] < ans[i];
        return 0;
    }
    
    void dfs(int x, int fat) {
        int vecSiz = e[x].size();
        for(int i = 0; i < vecSiz; i++) {
            int y = e[x][i];
            if(y == fat) continue;
            if((x == dx && y == dy) || (x == dy && y == dx)) continue;
            res[++cnt] = y;
            dfs(y, x);
        }
    }
    
    void work() {
        top = 0;
        getCir(1, 0);
        
/*        for(int i = 1; i <= sum; i++)
            printf("%d ", cir[i]);
        printf("\n");   */
        
        for(int i = 1; i <= n; i++) ans[i] = inf;
        
        for(int i = 1; i < sum; i++) {
            dx = cir[i], dy = cir[i + 1];
            res[cnt = 1] = 1;
            dfs(1, 0);
            if(bet()) {
                for(int j = 1; j <= n; j++)
                    ans[j] = res[j];
            }
        }
        dx = cir[1], dy = cir[sum];
        res[cnt = 1] = 1;
        dfs(1, 0);
        if(bet()) {
            for(int j = 1; j <= n; j++)
                ans[j] = res[j];
        }
        
        for(int i = 1; i <= n; i++) {
            printf("%d", ans[i]);
            if(i == n) putchar(\n);
            else putchar( );
        }
    }
    
}

int main() {
//    freopen("travel.in", "r", stdin);
//    freopen("travel.out", "w", stdout);

//    freopen("testdata.in", "r", stdin);
    
    read(n), read(m);
    for(int x, y, i = 1; i <= m; i++) {
        read(x), read(y);
        e[x].push_back(y), e[y].push_back(x);
    }
    
    for(int i = 1; i <= n; i++) 
        sort(e[i].begin(), e[i].end());
    
    if(m == n - 1) Solve1 :: work();
    else Solve2 :: work();
    
    return 0;
}
travel

D2T2 填數遊戲

  我到現在都還認為這是一道打表題。

  放上大神的狀壓題解 戳這裏

  首先寫個暴力寫打出$(n, n)$的表$(n \leq 8)$,然後就靠這三條性質求答案:

  1、$(n, m) = (m, n)$。

  2、$(n, m) = (n, m - 1) * 3$ $(m > n)$。

  3、$$ (n, n + 1) = \left\{\begin{matrix}
    (n, n) * 3 & (n \leq 3) \\
    (n, n) * 3 - 2^n * 3& (n \geq 4)
    \end{matrix}\right. $$

  時間復雜度$O(logn)$。

技術分享圖片
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;

const int N = 10;
const ll P = 1e9 + 7;
const ll base[N] = {0, 0, 12, 112, 912, 7136, 56768, 453504, 3626752, 0};

int n, m;

template <typename T>
inline void swap(T &x, T &y) {
    T t = x; x = y; y = t;
}

inline ll fpow(ll x, ll y) {
    ll res = 1LL;
    for(; y > 0; y >>= 1) {
        if(y & 1) res = res * x % P;
        x = x * x % P;
    }
    return res;
}

int main() {
    scanf("%d%d", &n, &m);
    if(n > m) swap(n, m);
    if(n == 1) return printf("%lld\n", fpow(2LL, m)), 0;
    if(n == m) return printf("%lld\n", base[n]), 0;
    ll ans = 1LL;
    if(n > 3) ans = (3LL * base[n] % P - 3LL * fpow(2LL, n) % P + P) % P;
    else ans = base[n] * 3LL % P;
    ans = ans * fpow(3LL, m - n - 1) % P;
    printf("%lld\n", ans);
    return 0;
}
game

D2T3 保衛王國

  為什麽會有動態$dp$這種東西出現啊啊啊啊啊啊。

  我還是只會倍增的做法。用$h_{x, 0/1}$表示$x$的子樹中選/不選$x$的最小代價,用$g_{x, 0/1}$表示$x$到根的鏈上不算$x$的子樹$x$選/不選的最小代價,用$f_{x, i, 0/1, 0/1}$表示從$x$向上跳$2^i$的這條鏈上不算$x$的子樹其他子樹的最小代價。

  然後剩下看代碼吧,感覺代碼肯定比我講得清楚。

  時間復雜度$O(nlogn)$,常數巨大。

技術分享圖片
#include <cstdio>
#include <cstring>
#include <map>
#include <algorithm>
using namespace std;
typedef long long ll;
typedef pair <int, int> pin;

const int N = 1e5 + 5;
const int Lg = 20;
const ll inf = 1LL << 60;

int n, qn, tot = 0, head[N], fa[N][Lg], dep[N];
ll a[N], h[N][2], f[N][Lg][2][2], g[N][2];
map <pin, int> ex;

struct Edge {
    int to, nxt;
} e[N << 1];

inline void add(int from, int to) {
    e[++tot].to = to;
    e[tot].nxt = head[from];
    head[from] = tot;
}

template <typename T>
inline void read(T &X) {
    X = 0; char ch = 0; T op = 1;
    for(; ch > 9 || ch < 0; ch = getchar())
        if(ch == -) op = -1;
    for(; ch >= 0 && ch <= 9; ch = getchar())
        X = (X << 3) + (X << 1) + ch - 48;
    X *= op;
}

template <typename T>
inline void chkMin(T &x, T y) {
    if(y < x) x = y;
}

inline ll min(ll x, ll y) {
    return x > y ? y : x;
}

void dp1(int x, int fat) {
    h[x][0] = 0, h[x][1] = a[x];
    for(int i = head[x]; i; i = e[i].nxt) {
        int y = e[i].to;
        if(y == fat) continue;
        dp1(y, x);
        h[x][0] += h[y][1];
        h[x][1] += min(h[y][0], h[y][1]);
    }
}

void dp2(int x, int fat) {
    for(int i = head[x]; i; i = e[i].nxt) {
        int y = e[i].to;
        if(y == fat) continue;
        g[y][0] = g[x][1] + h[x][1] - min(h[y][0], h[y][1]);
        g[y][1] = min(g[y][0], h[x][0] - h[y][1] + g[x][0]);
        dp2(y, x);
    }
}

void dfs(int x, int fat, int depth) {
    fa[x][0] = fat, dep[x] = depth;

    f[x][0][0][0] = inf, f[x][0][0][1] = h[fat][1] - min(h[x][0], h[x][1]);
    f[x][0][1][0] = h[fat][0] - h[x][1], f[x][0][1][1] = h[fat][1] - min(h[x][0], h[x][1]);
    for(int i = 1; i <= 18; i++) {
        fa[x][i] = fa[fa[x][i - 1]][i - 1];
        for(int u = 0; u < 2; u++)
            for(int v = 0; v < 2; v++) {
                f[x][i][u][v] = inf;
                for(int k = 0; k < 2; k++)
                    chkMin(f[x][i][u][v], f[x][i - 1][u][k] + f[fa[x][i - 1]][i - 1][k][v]);
            }
    }

    for(int i = head[x]; i; i = e[i].nxt) {
        int y = e[i].to;
        if(y == fat) continue;
        dfs(y, x, depth + 1);
    }
}

inline void solve(int x, int tx, int y, int ty) {
    if(dep[x] < dep[y])
        swap(x, y), swap(tx, ty);

    ll resx[2] = {inf, inf}, resy[2] = {inf, inf}, tox[2], toy[2];
    resx[tx] = h[x][tx], resy[ty] = h[y][ty];

    for(int i = 18; i >= 0; i--)
        if(dep[fa[x][i]] >= dep[y]) {
            tox[0] = tox[1] = inf;
            for(int u = 0; u < 2; u++)
                for(int v = 0; v < 2; v++)
                    chkMin(tox[u], resx[v] + f[x][i][v][u]);
            resx[0] = tox[0], resx[1] = tox[1];
            x = fa[x][i];
        }

    if(x == y) {
        printf("%lld\n", resx[ty] + g[y][ty]);
        return;
    }

    for(int i = 18; i >= 0; i--)
        if(fa[x][i] != fa[y][i]) {
            tox[0] = tox[1] = inf;
            for(int u = 0; u < 2; u++)
                for(int v = 0; v < 2; v++)
                    chkMin(tox[u], resx[v] + f[x][i][v][u]);
            resx[0] = tox[0], resx[1] = tox[1];
            
            toy[0] = toy[1] = inf;
            for(int u = 0; u < 2; u++)
                for(int v = 0; v < 2; v++)
                    chkMin(toy[u], resy[v] + f[y][i][v][u]);
            resy[0] = toy[0], resy[1] = toy[1];

            x = fa[x][i], y = fa[y][i];
        }

    int z = fa[x][0];
    ll res = h[z][0] - h[x][1] - h[y][1] + g[z][0] + resx[1] + resy[1];
    chkMin(res, h[z][1] - min(h[x][0], h[x][1]) - min(h[y][0], h[y][1]) + 
        g[z][1] + min(resx[0], resx[1]) + min(resy[0], resy[1]));
    printf("%lld\n", res);
}

int main() {
    read(n), read(qn);
    char typ[5]; scanf("%s", typ);
    for(int i = 1; i <= n; i++) read(a[i]);
    for(int x, y, i = 1; i < n; i++) {
        read(x), read(y);
        add(x, y), add(y, x);
        ex[pin(x, y)] = ex[pin(y, x)] = 1;
    }

    dp1(1, 0), dp2(1, 0), dfs(1, 0, 1);

    for(int x, tx, y, ty; qn--; ) {
        read(x), read(tx), read(y), read(ty);
        if(ex.find(pin(x, y)) != ex.end() && !tx && !ty) puts("-1");
        else solve(x, tx, y, ty);
    }

    return 0;
}
defense

NOIP2018 解題筆記