【解题总结】Pacific Northwest Regional Contest 2019(Codeforces Gym 102433)

我解决的:E、D、L、M(1 WA)、K。

没看的:G。

旁观的:A、B、C、I、J。

看了但没做出来的:F、H。

E Rainbow Strings

简单题,略。但可能要稍微想两分钟。

D Dividing By Two

题意:给定 A A B B 两个数,每次操作可以将 A A 加 1 或者除以 2(如果 A A 是偶数)。问将 A A 变成 B B 的最少操作次数。

A A 大于 B B 时,尽可能地去除是最优的。当 A A 不大于 B B 时,直接加到 B B 是最优的。

int ans = 0;
while (A > B){
if (A & 1) ++ans, ++A;
    A >>= 1, ++ans;
}
ans += B - A;

A Radio Prize

看上去比较简单的树形 DP,略。

B Perfect Flush

题意:给定长为 n n 的序列和 k k ,每个数 { 1 , 2 , , k } \in \left\lbrace 1, 2, \cdots, k\right\rbrace ,且 { 1 , 2 , , k } \left\lbrace 1, 2, \cdots, k\right\rbrace 均至少在序列中出现一次。求一个 { 1 , 2 , , k } \left\lbrace 1, 2, \cdots, k\right\rbrace 的排列,使之是给定序列的子序列且字典序最小。

这里只讲一个用单调栈的优秀做法:维护一个自栈底向栈顶递增的单调栈,然后遍历序列即可。顶只有在还有多个的情况下才可以被弹出。

#include <bits/stdc++.h>
using namespace std;
vector<int> st;
int n, k, a[200005];
int cnt[200005] = {0};
bool used[200005] = {0};
int main(){
    scanf("%d%d", &n, &k);
    for (int i = 1; i <= n; ++i)
        scanf("%d", &a[i]), ++cnt[a[i]];
    for (int i = 1; i <= n; ++i){
        --cnt[a[i]];
        if (used[a[i]]) continue;
        while (!st.empty() && cnt[st.back()] > 0 && a[i] < st.back())
            used[st.back()] = false, st.pop_back();
        st.push_back(a[i]);
        used[a[i]] = true;
    }

    for (int x: st) printf("%d ", x);
    return 0;
}

C Coloring Contention

题意:给定一个无权无向图,有两种不同颜色可给边着色,路径中相邻边颜色不同称为一个颜色变化,问所有边着色方案中, 1 1 n n 所有的路径上,颜色变化个数的最小值,最大可以是多少。

扫描二维码关注公众号,回复: 11644982 查看本文章

答案就是 1 1 n n 的最短路长度减 1。显然这是答案的上界。实际上也是可以达到的,只要按照边离 1 1 号点的距离交替染色即可。

I Error Correction

题意:给定 N N 个 anagram,词内部不包含相同的字母。如果一个词交换其内部的一对字母可以得到另一个给定的词就在这两个词之间连一条边。求最后图的最大独立集。

一般图最大独立集是很难的。实际上根据第一个样例,大致可以猜出来这是一个二分图。

具体为什么真的就是二分图,如果把每个 anagram 看作是一个排列,那么似乎是因为交换后逆序对的变化存在奇偶性,导致不会有奇环存在。

二分图的最大独立集是容易的,所以用一下匈牙利算法就能求出来了。

L Carry Cam Failure

题意:定义十进制不进位乘法:即原始乘法(卷积)的基础上直接模 10。如 90 和 1234 的运算结果为 98760。给定 N N ,求 a a 使得 a a a a 的运算结果为 N N ,且 a a 最小。

由于是卷积,所以可以根据 N N 的第一位确定 a a 的第一位,再由 N N 的第二位确定 a a 的第二位,以此类推。如果 N N 2 k 1 2k-1 位数,那么 a a k k 位数。

于是对于 a a 从高位到低位直接暴力 DFS 即可。由于对每一位成立的情况都比较少,因此复杂度比较低。

注意 N N 为偶数位数时无解。

M Maze Connect

题意:给定一个旋转了 45 度的迷宫,问最少打破迷宫的几面墙可以使得迷宫与外界联通。

对整个矩阵的格点建图,如图所示。然后就是简单的 BFS 找闭联通块个数了。

注意格点可以向八个方向移动。

#include <bits/stdc++.h>
using namespace std;
int R, C;
char s[1005][1005];
const int dx[] = {1, -1, -1, 1}, dy[] = {1, -1, 1, -1};
const int ddx[] = {1, -1, 0, 0}, ddy[] = {0, 0, 1, -1};
bool iswall[1005][1005][4] = {0};
bool occupied[1005][1005] = {0};
bool vis[1005][1005] = {0};
queue<pair<int, int> > q;
int bfs(int x, int y){
    // can reach outside: 0
    vis[x][y] = true;
    q.emplace(x, y);
    int able = 1;
    while (!q.empty()){
        x = q.front().first, y = q.front().second;
        q.pop();
        // do not go straight
        for (int i = 0; i < 4; ++i){
            if (iswall[x][y][i]) continue;
            int cx = x + dx[i], cy = y + dy[i];
            if (cx <= 0 || cy <= 0 || cx >= R + 2 || cy >= C + 2){
                able = 0;
                continue;
            }
            if (occupied[cx][cy] || vis[cx][cy]) continue;
            vis[cx][cy] = true;
            q.emplace(cx, cy);
        }
        // go straight
        for (int i = 0; i < 4; ++i){
            int cx = x + ddx[i], cy = y + ddy[i];
            if (cx <= 0 || cy <= 0 || cx >= R + 2 || cy >= C + 2){
                able = 0;
                continue;
            }
            if (occupied[cx][cy] || vis[cx][cy]) continue;
            vis[cx][cy] = true;
            q.emplace(cx, cy);
        }
    }
    return able;
}
int main(){
    scanf("%d%d", &R, &C);
    // build the graph
    for (int i = 0; i < R; ++i){
        scanf("%s", s[i]);
        for (int j = 0; j < C; ++j){
            if (s[i][j] == '/'){
                iswall[i + 1][j + 1][0] = true;
                iswall[i + 2][j + 2][1] = true;
                occupied[i + 1][j + 2] = true;
                occupied[i + 2][j + 1] = true;
            }else if (s[i][j] == '\\'){
                iswall[i + 1][j + 2][3] = true;
                iswall[i + 2][j + 1][2] = true;
                occupied[i + 1][j + 1] = true;
                occupied[i + 2][j + 2] = true;
            }
        }
    }
    
    // bfs
    int ans = 0;
    for (int i = 1; i <= R + 1; ++i){
        for (int j = 1; j <= C + 1; ++j){
            if (occupied[i][j]) continue;
            if (!vis[i][j]){
                ans += bfs(i, j);
            }
        }
    }
    printf("%d\n", ans);
    return 0;
}

K Computer Cache

题意复杂,略。

本题可以用可持久化线段树做。但我不会可持久化的区间操作,于是写了一个离线。

离线做法要遍历两次所有操作。第一次不做 3 操作,只对每条 data 记录 3 操作发生了几次。

我们用一个支持区间赋值的线段树维护那个 cache,一个叶子节点对应 cache 的一个 byte。叶子节点保存 ( i d x , t m , p o s ) (idx, tm, pos) 三元组,表示该节点对应的 byte 上保存了 i d x idx 号 data 的值,这段数据在被 load 到 cache 之前被更新了 t m tm 次,且 load 的左端点为 p o s pos

对于 1 操作,直接区间赋值即可。对于 2 操作,我们到这个线段树上询问 p p 节点的这个三元组,就可以知道这次回答时,要回答的是哪条数据,在第几次更新后,哪个位置上的值。

把这些要回答的问题记录下来,然后在第二次遍历中,只处理 3 操作,按照一定顺序完成这些回答即可。由于对 data 的更新只涉及简单的区间加,因此可以用树状数组。

#include <bits/stdc++.h>
using namespace std;
int n, m, q;
int *c[500005], siz[500005];
int qq[500005][4];
int tmp[500005];
int main_siz, main_seg[1100000][3] = {0}, main_tag[1100000][3] = {0};
vector<pair<int, pair<int, int> > > G[500005]; 
int have_answered[500005] = {0};
int ans[500005], ans_tot = 0;
int cnt[500005] = {0};
// BIT
inline int lowbit(int x){
    return x & (-x);
}
void add(int id, int k, int x){
    while (k <= siz[id]){
        c[id][k] += x, k += lowbit(k);
    }
}
int query(int id, int k){
    int res = 0;
    while (k > 0){
        res += c[id][k], k -= lowbit(k);
    }
    return res;
}

// segment tree
int _a, _b, _idx, _tm, _pos;
void maintain(int id, int _idxx, int _tmm, int _poss){
    main_seg[id][0] = main_tag[id][0] = _idxx;
    main_seg[id][1] = main_tag[id][1] = _tmm;
    main_seg[id][2] = main_tag[id][2] = _poss;
}
void pushdown(int id){
    if (main_tag[id][0] > 0){
        maintain(id << 1, main_tag[id][0], main_tag[id][1], main_tag[id][2]);
        maintain(id << 1 | 1, main_tag[id][0], main_tag[id][1], main_tag[id][2]);
        main_tag[id][0] = main_tag[id][1] = main_tag[id][2] = 0;
    }
}
void update(int id, int l, int r){
    if (l > _b || r < _a) return ;
    if (l >= _a && r <= _b){
        maintain(id, _idx, _tm, _pos);
        return ;
    }
    pushdown(id);
    int mid = (l + r) >> 1;
    update(id << 1, l, mid);
    update(id << 1 | 1, mid + 1, r);
}
int qans_idx, qans_tm, qans_pos;
void query_seg(int p){
    int id = 1, l = 1, r = main_siz;
    while (r > l){
        pushdown(id);
        int mid = (l + r) >> 1;
        if (p <= mid) id = id << 1, r = mid;
        else id = id << 1 | 1, l = mid + 1;
    }
    qans_idx = main_seg[id][0];
    qans_tm = main_seg[id][1];
    qans_pos = main_seg[id][2];
}

int main(){
    scanf("%d%d%d", &n, &m, &q);
    for (int i = 1; i <= m; ++i){
        scanf("%d", &siz[i]);
        c[i] = new int[siz[i] + 1]{0};
        
        tmp[0] = 0;
        for (int j = 1; j <= siz[i]; ++j){
            scanf("%d", &tmp[j]);
            add(i, j, tmp[j] - tmp[j - 1]);
        }
    }
    for (int i = 1; i <= q; ++i){
        scanf("%d", &qq[i][0]);
        if (qq[i][0] == 1){
            scanf("%d%d", &qq[i][1], &qq[i][2]);
        } else if (qq[i][0] == 2){
            scanf("%d", &qq[i][1]);
        } else {
            scanf("%d%d%d", &qq[i][1], &qq[i][2], &qq[i][3]);
        }
    }

    for (main_siz = 1; main_siz < n; main_siz <<= 1)
        ;
    
    // 第一轮遍历:获取要回答的问题
    for (int i = 1; i <= q; ++i){
        if (qq[i][0] == 1){
            _idx = qq[i][1], _tm = cnt[_idx], _pos = qq[i][2];
            _a = _pos, _b = _a + siz[_idx] - 1;
            update(1, 1, main_siz);
        } else if (qq[i][0] == 2){
            query_seg(qq[i][1]);
            ++ans_tot;
            G[qans_idx].push_back(make_pair(qans_tm, make_pair(qq[i][1] - qans_pos + 1, ans_tot)));
        } else {
            // do nothing
            ++cnt[qq[i][1]];
        }
    }
    
    // 第二轮遍历:进行回答
    for (int i = 1; i <= m; ++i){
        cnt[i] = 0;
        sort(G[i].begin(), G[i].end());
        int Gsiz = G[i].size();
        int& cur = have_answered[i];
        while (cur < Gsiz && G[i][cur].first == 0){
            ans[G[i][cur].second.second] = query(i, G[i][cur].second.first);
            ++cur;
        }
    }
    
    for (int i = 1; i <= q; ++i){
        if (qq[i][0] != 3) continue ;
        int id = qq[i][1];
        int lb = qq[i][2], rb = qq[i][3];
        add(id, lb, 1);
        if (rb + 1 <= siz[id]) add(id, rb + 1, -1);
        ++cnt[id];
        
        int Gsiz = G[id].size();
        int& cur = have_answered[id];
        while (cur < Gsiz && G[id][cur].first == cnt[id]){
            ans[G[id][cur].second.second] = query(id, G[id][cur].second.first);
            ++cur;
        } 
    }
    
    // output
    for (int i = 1; i <= ans_tot; ++i){
        printf("%d\n", (ans[i] & 255));
    }
    return 0;
}

F Carny Magician

题意:一个排列中若 p i = i p_i = i i i 为一个不动点。求长为 n n 、有 m m 个不动点的排列中,字典序第 k k 小的那个。

本题是一个非常精巧的 DP。设 f ( i , j , k ) f(i, j, k) 表示对于有 i i 个数的排列,有 j j 个不动点要产生,且有 k k 个数可以成为不动点(即位置尚未被占据)的方案数。

对于 j j 这一维度,有
f ( i , j , k ) = ( k j ) f ( i j , 0 , k j ) f(i, j, k) = \binom{k}{j}f(i - j, 0, k - j)
因此后面只要考虑 j = 0 j=0 的情况。

假设 k > 0 k > 0 ,那么考虑分配这 k k 个数的去向。选出其中的一个数,由于不再需要不动点,因此有两种方法:

  1. 占据另一个可以成为不动点的数的位置,这种位置有 k 1 k-1 个。
  2. 占据另一个无法成为不动点的数的位置,这种位置有 i k i-k 个。

因此有
f ( i , 0 , k ) = ( k 1 ) f ( i 1 , 0 , k 2 ) + ( i k ) f ( i 1 , 0 , k 1 ) f(i, 0, k) = (k-1)f(i - 1, 0, k - 2) + (i-k)f(i-1, 0, k-1)
如果 k = 1 k=1 ,那么等号右边第一项是 0。

边界条件为 f ( i , 0 , 0 ) = i ! f(i, 0, 0) = i!

最后还有两个麻烦的地方:一个是构造答案,另一个是可能会爆 long long,因此这里用 Python 写了。

n, m, K = [int(x) for x in input().split()]

f = [[[0] * (n + 1) for _ in range(m + 1)] for _ in range(n + 1)]
C = [[0] * (n + 1) for _ in range(n + 1)]

# 组合数
C[0][0] = 1
for i in range(1, n + 1):
    C[i][0] = 1
    for j in range(1, i + 1):
        C[i][j] = C[i - 1][j - 1] + C[i - 1][j]

f[0][0][0] = 1
for i in range(1, n + 1):
    f[i][0][0] = i * f[i - 1][0][0]
    f[i][0][1] = (i - 1) * f[i - 1][0][0]
    for k in range(2, i + 1):
        f[i][0][k] = (k - 1) * f[i - 1][0][k - 2] + (i - k) * f[i - 1][0][k - 1]

def ff(i, j, k):
    return f[i - j][0][k - j] * C[k][j]

ans = [0] * n
used = [0] * (n + 1)

def dfs(cur, j, k):
    global f, K, n
    if cur == n + 1:
        return 
    for t in range(1, n + 1):
        if used[t]: continue
        if j == 0 and t == cur: continue
        
        jj, kk = j, k
        # 下一层递归的 j,k
        if t == cur:
            jj -= 1
            kk -= 1
        elif t < cur:
            if used[cur] == 0:
                kk -= 1
        elif t > cur:
            kk -= 1
            if used[cur] == 0:
                kk -= 1
        
        if K > ff(n - cur, jj, kk):
            K -= ff(n - cur, jj, kk)
            # 不同的后继状态 j,k 不同
            # 所以可以直接减
        else:
            ans[cur - 1] = t
            used[t] = True
            dfs(cur + 1, jj, kk)
            break

if ff(n, m, n) < K:
    print("-1")
else:
    dfs(1, m, n)
    print(" ".join([str(x) for x in ans]))

J Interstellar Travel

待补。。。

G Glow, Little Pixel, Glow

待补。。。

H Pivoting Points

待补。。。

猜你喜欢

转载自blog.csdn.net/zqy1018/article/details/108204939