1. 程式人生 > >「NOIP 2017」解題報告

「NOIP 2017」解題報告

整體來說 … 這次考試還是很 NOIP 的 …

D1

T3

wyj 在寫最短路計數的時候還寫掛了。
所以我們來複習一發最短路計數。

Luogu P1144 最短路計數
(無權圖最短路計數,對於重邊和自環我們無需處理
(無法到達的點,那麼在 bfs 的時候就不會訪問所以就一直是 num[] 的初始值。所以當題目要求我們在點無法到達的點輸出什麼我們把起初值賦成什麼

#include <bits/stdc++.h>

using namespace std;
const int N = 1e6 + 5, mod = 1e5 + 3;

struct Edge {
    int
to, next; }e[N << 2]; int cnt = 0; int head[N]; void add(int x, int y) { e[++ cnt].to = y; e[cnt].next = head[x]; head[x] = cnt; } int d[N], num[N]; // d[] 是最短路徑長度,num[] 是最短路徑條數 queue<int> q; int main() { memset(head, 0, sizeof(head)); memset(num, 0, sizeof(num)); memset
(d, 0x7f, sizeof(d)); // 一定要初始設為最大值 int n, m; scanf("%d%d", &n, &m); for(int i = 1; i <= m; i ++) { int x, y; scanf("%d%d", &x, &y); add(x, y), add(y, x); } q.push(1); d[1] = 1, num[1] = 1; while(!q.empty()) { int u = q.front(); q.pop(); for
(int i = head[u]; i; i = e[i].next) { int v = e[i].to; if(d[v] < d[u]) continue; // 不往自己父親走避免死迴圈 if(num[v] == 0) { d[v] = d[u] + 1; num[v] = num[u]; q.push(v); } else if(d[v] == d[u] + 1) { num[v] += num[u]; num[v] %= mod; } } } for(int i = 1; i <= n; i ++) printf("%d\n", num[i]); return 0; }

#include <bits/stdc++.h>

using namespace std;
const int N = 1e5 + 5, inf = 1 << 30;

inline int read() {
    int ret = 0; char gc = getchar();
    while(gc < '0' || gc > '9') gc = getchar();
    while(gc >= '0' && gc <= '9') ret = ret * 10 + gc - '0', gc = getchar();
    return ret;
}

int n, m, K, p;

struct Edge {
    int to, next, w;
}e1[N << 1], e2[N << 1];

struct point {
    int id, val;
    point(int id, int val): id(id), val(val){}
    bool operator < (const point &x) const {
        return val > x.val;
    }
};

int cnt;
int head1[N], dis1[N], vis1[N], head2[N], dis2[N], vis2[N];

void add(int x, int y, int z) {
    e1[++ cnt].to = y; e1[cnt].next = head1[x]; e1[cnt].w = z; head1[x] = cnt;
    e2[cnt].to = x; e2[cnt].next = head2[y]; e2[cnt].w = z; head2[y] = cnt; 
}

priority_queue <point> q1;
void dijkstra1(int s) {
    for(int i = 1; i <= n; i ++) dis1[i] = inf;

    q1.push(point(s, 0));
    dis1[s] = 0;
    while(!q1.empty()) {
        int cur1 = q1.top().id; q1.pop();

        if(vis1[cur1]) continue;
        vis1[cur1] = 1;

        for(int i = head1[cur1]; i != - 1; i = e1[i].next) {
            int temp1 = e1[i].to;
            if(dis1[cur1] + e1[i].w < dis1[temp1]) {
                dis1[temp1] = dis1[cur1] + e1[i].w;
                q1.push(point(temp1, dis1[temp1]));
            }
        }
    }
}

priority_queue <point> q2;
void dijkstra2(int s) {
    for(int i = 1; i <= n; i ++) dis2[i] = inf;

    q2.push(point(s, 0));
    dis2[s] = 0;
    while(!q2.empty()) {
        int cur2 = q2.top().id; q2.pop();

        if(vis2[cur2]) continue;
        vis2[cur2] = 1;

        for(int i = head2[cur2]; i != - 1; i = e2[i].next) {
            int temp2 = e2[i].to;
            if(dis2[cur2] + e2[i].w < dis2[temp2]) {
                dis2[temp2] = dis2[cur2] + e2[i].w;
                if(dis2[temp2] <= K)
                    q2.push(point(temp2, dis2[temp2]));
            }
        }
    }
}

int tot;
int d[N], que[N << 1];

inline int check() {
    for(int i = 1; i <= n; i ++)
        for(int j = head1[i]; j != - 1; j = e1[j].next) {
            int v = e1[j].to;
            e1[j].w = dis1[i] + e1[j].w - dis1[v];
            if(e1[j].w == 0) d[v] ++;
        }
    for(int i = 1; i <= n; i ++)
        if(!d[i]) que[++ tot] = i;
    for(int i = 1; i <= tot; i ++) {
        int u = que[i];
        for(int j = head1[u]; j != -1; j = e1[j].next) {
            int v = e1[j].to;
            if(e1[j].w == 0) {      
                d[v] --;
                if(!d[v]) que[++ tot] = v;
            }
        }
    }
    for(int i = 1; i <= n; i ++)
        if(d[i] && dis1[i] + dis2[i] <= dis1[n] + K)
            {printf("-1\n"); return false;}
    return true;
}

int f[N][55];

void dp() {
    memset(f, 0, sizeof(f));
    f[1][0] = 1;
    for(int k = 0; k <= K; k ++) {
        for(int i = 1; i <= tot; i ++) {
            int u = que[i];
            for(int j = head1[u]; j != -1; j = e1[j].next) {
                int v = e1[j].to;
                if(e1[j].w == 0) f[v][k] = (f[v][k] + f[u][k]) % p;
            }
        }
        for(int i = 1; i <= n; i ++) {
            for(int j = head1[i]; j != -1; j = e1[j].next) {
                int v = e1[j].to;
                if(e1[j].w != 0 && k + e1[j].w <= K)
                    f[v][k + e1[j].w] = (f[v][k + e1[j].w] + f[i][k]) % p;
            }
        }
    }
}

void mem() {
    tot = 0;
    cnt = 0;
    memset(head1, -1, sizeof(head1)); memset(head2, -1, sizeof(head2));
    memset(vis1, 0, sizeof(vis1)); memset(vis2, 0, sizeof(vis2));
    memset(d, 0, sizeof(d));
}

void work() {
    n = read(), m = read(), K = read(), p = read();
    for(int i = 1; i <= m; i ++) {
        int a, b, c;
        a = read(), b = read(), c = read();
        add(a, b, c);
    }
    dijkstra1(1), dijkstra2(n);
    if(check() == 0) return ;
    dp();
    int ans = 0;
    for(int i = 0; i <= K; i ++)
        ans = (ans + f[n][i]) % p;
    printf("%d\n", ans);
}

int main() {
    int T;
    T = read();
    while(T --) mem(), work();
    return 0;
}

D2

T2

預備知識:列舉子集的二進位制寫法

// 列舉 i 的子集
for (int j = i & (i - 1); j; j = i & (j - 1))

這樣做就是每次不斷 - 1 來列舉所有子集,它不是忽略了 i 中的 0,而是在 & i 的過程中將 0 消去了。

分析

我們發現每層的邊對應的權值都不一樣,所以可以按層考慮狀壓 dp。

我們定義:如果狀態 s 的第 k 個位置訪問過,則第 k 個位置為 1。
f[i][s] 表示第 i 層,到狀態 s 的最小花費。
接著列舉 s 子集的補集 s’,作為第 i + 1 層的點,對於 s’ 中每個點,都選擇一條連向 s 中最小的邊。
可以預處理出 u 到任意集合 s 中的最小的邊,所以我們可以按 s 從小帶大 dp。
時間複雜度為 O(3nn)

#include <bits/stdc++.h>
 #define ll long long

 using namespace std;
 const int N = 1005, s = (1 << 12) + 5, inf = 0x7fffffff;

 int a[N][N], f[N][s], w[N][s];
// f[i][s] 表示第 i 層,到狀態 s 的最小花費 
// w[i][s] 表示 s 集合到 i 點的最短邊
// 如果狀態 s 的第 k 個位置訪問過,則第 k 個位置為 1 

 int main() {
    int n, m;
    scanf("%d%d", &n, &m);
    for(int i  = 1; i <= n; i ++)
        for(int j = 1; j <= n; j ++)
            a[i][j] = inf;
    for(int i = 1; i <= m; i ++) {
        int x, y, z;
        scanf("%d%d%d", &x, &y, &z);
        if(z < a[x][y]) a[x][y] = a[y][x] = z;
    }
    int sn = (1 << n) - 1;
    for(int i = 1; i <= n; i ++)
        for(int j = 1; j <= sn; j ++) {
            w[i][j] = inf;
            for(int k = 1; k <= n; k ++) if(1 << (k - 1) & j) w[i][j] = min(w[i][j], a[i][k]);
        }
    for(int i = 1; i <= sn; i ++) {
        if(!(i & (i - 1))) continue;
        for(int j = 0; j <= n; j ++) f[j][i] = inf;
        for(int j = i & (i - 1); j; j = i & (j - 1)) {
            ll z = 0;
            for(int k = 1; k <= n; k ++) if(1 << (k - 1) & j) z += w[k][i - j];
            // i - j 是 i 的子集 j 的補集  
            // z 是子集的補集連向子集中所有點的最短邊的和 
            for(int k = 1; k <= n; k ++) f[k][i] = min((ll)f[k][i], f[k - 1][i - j] + z * k);
            // f[k - 1][i - j] 是狀態為子集的補集的花費,z * k 是狀態為子集的花費,和起來就是狀態 i 的花費 
        }
    }
    int ans = inf;
    for(int i = 1; i <= n; i ++) ans = min(ans, f[i][sn]);
    printf("%d\n", ans);
    return 0;
 }

T3

我們可以用 n + 1 個 Treap 來維護。
用一個 Treap 維護最後一列的位置 treap[0],其他 n 個 Treap 分別維護每行除最後一列剩餘的位置。

然後我們發現維護每一個 Treap 只需要維護他的 root 就好了。
維護 Treap 的時候是在維護他的 root,而對於 Treap 內的每個節點 root 是在維護一個區間。

每次插入就會將根節點的區間分成
如果 k 小於等於左子樹大小,左邊就完全在左子樹裡
如果 k 大於等於 左子樹大小 + 當前節點大小,那自己和左子樹就都在左邊
這兩種情況之外,就是,v 自己,的一半在 k 左邊,一半在 k 右邊

還有注意因為節點維護的是一個區間,所以注意一個節點的大小不是 1 了,而是他維護的區間大小 r - l + 1

#include <bits/stdc++.h>

#define ll long long

using namespace std;
const int N = 3e5 + 5;

int n, m, q;

struct Treap {
    struct Node {
        Node *lc, *rc;
        int size, key;
        ll l, r;

        Node(ll l, ll r) : lc(NULL), rc(NULL), size(r - l + 1), l(l), r(r), key(rand()) {}

        inline void maintain() { size = (lc ? lc->size : 0) + (rc ? rc->size : 0) + r - l + 1; }

        inline int lSize() { return lc ? lc->size : 0; }

        inline long long len() {
            return r - l + 1;
        }
    } *root;

    Node *merge(Node *a, Node *b) {
        if (!a && !b) return NULL;
        if (!a) { b->maintain(); return b; }
        if (!b) { a->maintain(); return a; }

        if (a->key > b->key) {
            a->rc = merge(a->rc, b);
            a->maintain();
            return a;
        } else {
            b->lc = merge(a, b->lc);
            b->maintain();
            return b;
        }
    }

    static inline void split(Node *v, int k, Node *&l, Node *&r) {
        if (!v) { l = r = NULL; return; }

        int s = v->lSize();
        if (k <= s) {
            split(v->lc, k, l, r);
            v->lc = r;
            r = v;  
        } else if (k >= s + v->len()) {
            split(v->rc, k - s - v->len(), l, r);
            v->rc = l;
            l = v;
        } else {
            int t = k - s; // 把這個點代表的前 t 個點分裂出來 

            l = new Node(v->l, v->l + t - 1);
            r = new Node(v->l + t, v->r);

            l->lc = v->lc;
            r->rc = v->rc;

            l->maintain();
            r->maintain();

            delete v;

            return ;
        }

        v->maintain();
    }

    Node *erase(int pos) {
        Node *pred, *tmp;
        split(root, pos - 1, pred, tmp);

        Node *target, *succ;
        split(tmp, 1, target, succ);

        root = merge(pred, succ);

        return target;
    }

    void append(Node *v) {
        root = merge(root, v);
    }

} treap[N + 1];

inline void init() {
    Treap::Node *v;
    for (int i = 1; i <= n; i ++)  {
        v = new Treap::Node((long long)(i - 1) * m + 1, (long long)i * m - 1);
        treap[i].root = v;
    }

    v = new Treap::Node(m, m);
    treap[0].root = v;
    for (int i = 2; i <= n; i ++) {
        v = new Treap::Node((long long)i * m, (long long)i * m);
        treap[0].root = treap[0].merge(treap[0].root, v);
    }
}

inline long long solve(int a, int b) {
    long long ans;
    if (b == m) {
        Treap::Node *v = treap[0].erase(a);
        ans = v->l;
        treap[0].append(v);
    } else {
        Treap::Node *v = treap[a].erase(b);
        ans = v->l;
        treap[0].append(v);

        // 最右邊一列的第 a 行的點要向前移動   
        Treap::Node *u = treap[0].erase(a);
        treap[a].append(u);
    }

    return ans;

    /*之前的錯誤寫法,刪最後一列的時候還要記得合併到這一行前面的 Treap 裡 
    Treap::Node *pos, *pos1;

    if (b != m) pos = treap[a].erase(b);   
    pos1 = treap[0].erase(a);    
    if (b == m) pos = pos1;  
    printf("%lld\n", pos->l); // RE
    treap[0].root = treap[0].merge(treap[0].root, pos);
    */
}

int main() {
    scanf("%d%d%d", &n, &m, &q);
    init();
    for (int i = 1; i <= q; i ++) {
        int a, b;
        scanf("%d%d", &a, &b);
        printf("%lld\n", solve(a, b));
    }
    return 0;   
}