【解题总结】SWERC 2018(Codeforces Gym 102465)

我解决的:B、H、G(1 WA)。

没看的:无。

旁观的:A、D、E、F、I、J、K。

看了但没做出来的:C。

A City of Lights

简单题,略。

K Dishonest Driver

题意:要求压缩长为 N N 的字符串,一个串 s s 连续出现 t t 次可以被压缩为 ( s ) t (s)^t ,压缩后的长度为 s s 的长度。求可达到的最短长度。 N 700 N \le 700

考虑区间 DP,设 f ( i , j ) f(i, j) 为区间 [ i , j ] [i, j] 的最短可压缩长度。用 KMP 预处理出可以直接压缩的区间然后 DP 即可。

时间复杂度为 O ( N 3 ) O(N^3) ,由于时限有 6s 所以应该并不怕。

E Rounding

应该不是很难,略。

I Mason’s Mark

神秘搜索题,略。字母不会被噪点覆盖,那么可以投机取巧:先找到字母的左上角,然后找出宽和高,然后判断:如果底下有个洞就是 A,右边有个洞就是 C,否则就是 B。

B Blurred pictures

题意 N × N N \times N 的网格内,每一行有一个区间 [ l i , r i ] [l_i, r_i] 是白色方格,其余均为黑色方格。白色方格构成凸联通块。问最大白色子矩形。 N 1 0 5 N \le 10^5

一个非常慢的方法是二分边长 x x ,然后会发现能形成以第 i i 行为底边的正方形的要求是:

  1. x 1 x-1 行不能有左端点大于 r i x + 1 r_i-x+1
  2. x 1 x-1 行不能有右端点小于 l i + x 1 l_i+x-1
  3. 对第 i j ( j < x ) i-j(j < x) 行,其的上 x 1 j x - 1 - j 行参照条件 1 和 2。

这可以通过 multiset 来辅助判定,时间复杂度为 O ( n log 2 n ) O(n \log^2 n)

更好的方法是结合凸联通块的性质,对某一行 i i 和某个边长 k k ,可以 O ( 1 ) O(1) 地检查其是否可以是边长为 k k 的正方形的顶边。由于 k k 作为答案,若可行只会递增,而 i i 也只会递增,因此时间复杂度是 O ( N ) O(N)

#include <bits/stdc++.h>
using namespace std;

int n, l[100005], r[100005];
bool check(int i, int k){
    if (r[i] - l[i] + 1 < k) return false;
    if (i + k - 1 > n) return false;
    int ll = max(l[i], l[i + k - 1]);
    return r[i] - ll + 1 >= k && r[i + k - 1] - ll + 1 >= k;
}
int main(){
    scanf("%d", &n);
    for (int i = 1; i <= n; ++i)
        scanf("%d%d", &l[i], &r[i]);
    int k = 1;
    for (int i = 1; i <= n; ++i){
        while (check(i, k + 1)) ++k;
    }
    printf("%d\n", k);
    return 0;
}

H Travel Guide

题意(转化后):给定三维平面上 N N 个点,一个点 i i 被另一个点 j j 支配当且仅当 x i x j y i y j z i z j ( x i > x j y i > y j z i > z j ) x_i \ge x_j \wedge y_i \ge y_j \wedge z_i \ge z_j \wedge (x_i > x_j \vee y_i > y_j \vee z_i > z_j) 。问有多少个点不被支配。

这看起来像是一个三维偏序,但是比三维偏序简单一点,所以可以用比三维偏序思维难度更低的做法。

一种做法是:按照 z , x , y z, x, y 的关键字顺序升序遍历点,检查每一个点是否被之前的某个点支配。

对于 z z 坐标相同的一段点,我们将其再成多个 x x 坐标相同的小段,每一个小段中 y y 坐标不是最小的那些点必定被支配。对于 y y 坐标最小的点,如果前面有 x x 坐标更小且 y y 坐标不更大的点,那也被支配。因此这里维护一个前缀 y y 坐标最小值即可。

而对于这一段点之前的那些点,如果前面有点 j j 满足 x , y x, y 坐标均不大于这段点中的点 i i ,那么 i i j j 支配。我们考虑对 x x 坐标建立权值线段树,对每一个 x x 坐标维护具有此坐标的点中, y y 坐标的最小值。这样就能快速判断。

看完这一段之后,把这段点的信息插入线段树,然后再看下一段,如此继续下去即可。时间复杂度为 O ( n log n ) O(n \log n)

在这里插入图片描述

#include <bits/stdc++.h>
#define INF 2000000000
using namespace std;

int n, m;
int to[1000005], nxt[1000005], w[1000005], at[100005] = {0}, cnt = 0;
int dis[3][100005];
priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > pq;
int lis[100005], tot;
void dijkstra(int dist[], int S){
    while (!pq.empty()) pq.pop();
    
    dist[S] = 0;
    pq.push(make_pair(0, S));
    tot = 0;
    for (; ; ){
        while (!pq.empty()){
            if (pq.top().first > dist[pq.top().second])
                pq.pop();
            else break;
        }
        if (pq.empty()) break;
        int dd = pq.top().first, h = pq.top().second;
        pq.pop();
        lis[tot++] = h;
        for (int i = at[h]; i; i = nxt[i]){
            if (dist[to[i]] > dd + w[i]){
                dist[to[i]] = dd + w[i];
                pq.push(make_pair(dist[to[i]], to[i]));
            }
        } 
    } 
}

int seg[270005], siz, tmp[100005];
unordered_map<int, int> mp;
pair<int, pair<pair<int, int>, int> > tmp2[100005];
bool vis[100005] = {0};
void update(int k, int x){
    k += siz - 1;
    seg[k] = min(seg[k], x);
    while (k > 1){
        k >>= 1;
        seg[k] = min(seg[k << 1], seg[k << 1 | 1]);
    }
}
int query(int id, int l, int r, int b){
    if (l > b) return INF;
    if (r <= b) return seg[id];
    int mid = (l + r) >> 1;
    return min(query(id << 1, l, mid, b), query(id << 1 | 1, mid + 1, r, b));
}

void solve_2d(int l, int r){
    int pre_mini = INF;
    for (int i = l, j; i < r; i = j){
        j = i;
        while (j < r && tmp2[i].second.first == tmp2[j].second.first)
            ++j;
        // #0 the same, #2 the same, so impossible for the others
        int rb = j;
        while (j < r && tmp2[i].second.first.first == tmp2[j].second.first.first){
            vis[tmp2[j].second.second] = true;
            ++j;        
        }
        int tmp_mini = tmp2[i].second.first.second;
        if (pre_mini <= tmp_mini){
            while (i < rb) {
                vis[tmp2[i].second.second] = true;
                ++i;
            }
        } else pre_mini = tmp_mini;
    }
}

int main(){
    scanf("%d%d", &n, &m);
    for (int i = 1, u, v, ww; i <= m; ++i){
        scanf("%d%d%d", &u, &v, &ww);
        to[++cnt] = v, nxt[cnt] = at[u], w[cnt] = ww, at[u] = cnt;
        to[++cnt] = u, nxt[cnt] = at[v], w[cnt] = ww, at[v] = cnt;
    }
    
    memset(dis, 0x3f, sizeof(dis));
    dijkstra(dis[0], 0);
    dijkstra(dis[1], 1);
    dijkstra(dis[2], 2);
    // connected graph, so tot = n
    
    // discretize the dist for #0
    for (int i = 0; i < n; ++i)
        tmp[i] = dis[0][i];
    sort(tmp, tmp + n);
    int nn = unique(tmp, tmp + n) - tmp;
    for (int i = 0; i < nn; ++i){
        mp[tmp[i]] = i + 1;
    }
    
    // initialize segment tree
    for (siz = 1; siz < nn; siz <<= 1)
        ;
    for (int i = 1; i < siz + siz; ++i)
        seg[i] = INF;
    
    // sort by #2
    for (int i = 0; i < n; ++i){
        tmp2[i].first = dis[2][i];
        tmp2[i].second.second = i;
    }
    for (int i = 0; i < n; ++i){
        tmp2[i].second.first.first = dis[0][i];
    }
    for (int i = 0; i < n; ++i){
        tmp2[i].second.first.second = dis[1][i];
    }
    sort(tmp2, tmp2 + n);
    
    // calc ans
    for (int i = 0, j; i < n; i = j){
        j = i;
        while (j < n && tmp2[i].first == tmp2[j].first)
            ++j;
        solve_2d(i, j);
        
        // use segment tree
        for (int t = i; t < j; ++t){
            int id = tmp2[t].second.second;
            if (vis[id]) continue;
            if (query(1, 1, siz, mp[tmp2[t].second.first.first]) <=
                tmp2[t].second.first.second)
                vis[id] = true;
        }
        
        // insert into segment tree
        for (int t = i; t < j; ++t){
            update(mp[tmp2[t].second.first.first], tmp2[t].second.first.second);
        }
    }
    
    int ans = 0;
    for (int i = 0; i < n; ++i){
        if (!vis[i]) ++ans;
    }
    printf("%d\n", ans);
    return 0;
}

J Mona Lisa

题意:有 4 个随机数生成器,每一个的 seed 不同。给定 N N ,找出四个数 a 1 , a 2 , a 3 , a 4 a_1, a_2, a_3, a_4 ,使得第 i i 个生成器的第 a i a_i 个随机数,这 4 个数最后 N N 个 bit 异或起来是 0。

这种神奇的要求最后结果为 0 的异或题一般要考虑 meet in the middle。

这里不仅用了 meet in the middle,还用了生日悖论。设第 i i 个生成器产生的随机数为 x i x_i 。先产生 N 1 N_1 x 1 , x 2 x_1, x_2 ,如果它们最低 r r 个 bit 相同就将这两个数的异或加入 map。

然后再产生 N 2 N_2 x 3 , x 4 x_3, x_4 ,如果它们最低 r r 个 bit 相同,就到 map 里找有没有能和它们对上的 x 1 , x 2 x_1, x_2

根据生日悖论,如果取 r = n 3 r=\frac{n}{3} ,且 N 1 = N 2 = O ( 2 r ) N_1 = N_2 = O(2^r) ,那么就很有可能会找到一个可行解。

(代码来自队友)

#include <bits/stdc++.h>
using namespace std;

uint64_t state[4][2];
uint64_t next(int i) {
    uint64_t s0 = state[i][0], s1 = state[i][1], result = s0 + s1;
    s1 ^= s0;
    state[i][0] = ((s0 << 55) | (s0 >> 9)) ^ s1 ^ (s1 << 14);
    state[i][1] = (s1 << 36) | (s1 >> 28);
    return result;
}

int n;
unordered_map<uint64_t, vector<pair<uint64_t, int>>> B, D;
unordered_map<uint64_t, pair<int, int>> ab, cd;

void addab(uint64_t v, int i, int j) {
    auto it = cd.find(v);
    if (it != cd.end()) {
        printf("%d %d %d %d\n", i, j, it->second.first, it->second.second);
        exit(0);
    }
    ab[v] = make_pair(i, j);
}

void addcd(uint64_t v, int i, int j) {
    auto it = ab.find(v);
    if (it != ab.end()) {
        printf("%d %d %d %d\n", it->second.first, it->second.second, i, j);
        exit(0);
    }
    cd[v] = make_pair(i, j);
}

int main() {
    scanf("%d", &n);
    int r = n / 3;
    uint64_t nn = (1ll << n) - 1, rr = (1ll << r) - 1;
    for (int i = 0; i < 4; i++) {
        scanf("%llu", &state[i][0]);
        state[i][1] = state[i][0] ^ 0x7263d9bd8409f526ll;
    }
    for (int i = 1; ; i++) {
        uint64_t a = next(0) & nn, b = next(1) & nn;
        uint64_t c = next(2) & nn, d = next(3) & nn;
        B[b & rr].emplace_back(b, i);
        D[d & rr].emplace_back(d, i);
        if (B.count(a & rr))
            for (auto p: B[a & rr])
                addab(a ^ p.first, i, p.second);
        if (D.count(c & rr))
            for (auto p : D[c & rr]) 
                addcd(c ^ p.first, i, p.second);
    }
    return 0;
}

G Strings

题意:有一个串 S ( 0 ) S(0) N N 次操作,第 i i 次操作产生串 S ( i ) S(i) 。操作有两种:指定 x , l o , h i x, lo, hi ,取 S ( x ) [ l o : h i ) S(x)[lo: hi) 作为 S ( i ) S(i) ;指定 x , y x, y ,使得 S ( i ) = S ( x ) + S ( y ) S(i) = S(x) + S(y) 。问最后生成的串中所有字符的 ASCII 码之和。 N 2500 N \le 2500

将串做成二叉树的形式,叶子都是 S ( 0 ) S(0) 的子串。然后模拟各个操作即可。注意复用节点。

#include <bits/stdc++.h>
#define MOD 1000000007
using namespace std;
typedef long long ll;
int n, m, tot;
int sum[10005] = {0};
char s[10005], op[10];

int lch[8000005], rch[8000005];
int sm[8000005];
ll len[8000005];

int rt[2505];
inline int modadd(int x, int y){
    return (x + y >= MOD ? x + y - MOD : x + y);
}
inline ll getlen(int id){
    if (id >= m * m) return len[id];
    return (id % m) - (id / m) + 1;
}
inline int getsum(int id){
    if (id >= m * m) return sm[id];
    return sum[id % m + 1] - sum[id / m];
}
int build_tree(int rt_id, ll l, ll r){
    if (rt_id < m * m){
        // 如果这个根是 S(0) 的子串,那就取其一部分
        int lb = rt_id / m, rb = rt_id % m;
        rb = lb + (int)r;
        lb += (int)l;
        return lb * m + rb;
    }
    if (l == 0 && r == getlen(rt_id) - 1) {
        // 整个根可以复用,不用修改
        return rt_id;
    }
    // 只要左子树或者右子树
    if (l >= getlen(lch[rt_id]))
        return build_tree(rch[rt_id], l - getlen(lch[rt_id]), r - getlen(lch[rt_id]));
    if (r < getlen(lch[rt_id]))
        return build_tree(lch[rt_id], l, r);
    
    // 否则新建一个根,向左右两边递归
    int new_rt = tot++;
    sm[new_rt] = 0;
    len[new_rt] = 0ll;
    lch[new_rt] = build_tree(lch[rt_id], l, getlen(lch[rt_id]) - 1);
    sm[new_rt] = getsum(lch[new_rt]);
    
    rch[new_rt] = build_tree(rch[rt_id], 0, r - getlen(lch[rt_id]));
    sm[new_rt] = modadd(sm[new_rt], getsum(rch[new_rt]));
    
    len[new_rt] = getlen(lch[new_rt]) + getlen(rch[new_rt]);
    return new_rt;
}
int main(){
    scanf("%d%s", &n, s);
    m = strlen(s);
    --n;
    tot = m * m;
    rt[0] = m - 1;
    
    // 将子串 [l, r] 编号为 l * len(s) + r
    for (int i = 1; i <= m; ++i)
        sum[i] = modadd(sum[i - 1], s[i - 1]);

    for (int R = 1; R <= n; ++R){
        scanf("%s", op);
        if (op[0] == 'S'){
            int x;
            ll lb, rb;
            scanf("%d%lld%lld", &x, &lb, &rb);
            --rb;
            rt[R] = build_tree(rt[x], lb, rb);
        } else {
            int x, y;
            scanf("%d%d", &x, &y);
            rt[R] = tot++;
            lch[rt[R]] = rt[x];
            rch[rt[R]] = rt[y];
            sm[rt[R]] = modadd(getsum(rt[x]), getsum(rt[y]));
            len[rt[R]] = getlen(rt[x]) + getlen(rt[y]);
        }
    }
    printf("%d\n", getsum(rt[n]));
    return 0;
}

C Crosswords

题意:有一个 N × M N \times M 的矩阵,给定 A A 个长度为 N N 的单词、 B B 个长度为 M M 的单词,现在要用字母填充矩阵,使得每一行形成的单词都在给定的 B B 个之中,每一列形成的单词都在给定的 A A 个之中。求方案数目。

std 说是暴力,于是写了一个暴力,真的可以。绝了。

简单来说就是用两个 Trie 存横竖的单词表,然后维护一下每一列在 Trie 上的指针以及当前行在 Trie 上的指针,按格子 DFS 就行了。

或者更保险一点,写一个 7 维 DP,然后记忆化搜索,也是一样的。

#include <bits/stdc++.h>
using namespace std;
int n, m, A, B;
int trieh[1000005][26] = {0}, toth = 1;
int triev[1000005][26] = {0}, totv = 1;
int ans, col_pointer[10];
// col_pointer:每一列填到现在为止,在 trie 上的指针
char tmp[10], mat[10][10];
void insert(int trie[][26], int& tot){
    int p = 1, l = strlen(tmp);
    for (int i = 0; i < l; ++i){
        if (!trie[p][tmp[i] - 'a'])
            trie[p][tmp[i] - 'a'] = ++tot;
        p = trie[p][tmp[i] - 'a'];
    }
}
void dfs(int r, int c, int row_pointer){
    // row_pointer: r 这一行填到现在为止,在 trie 上的指针
    if (r == n){
        ++ans;
        return ;
    }
    for (int i = 0; i < 26; ++i){
        if (!trieh[row_pointer][i]) continue;
        int lst_p = col_pointer[c];
        if (!triev[lst_p][i]) continue;
        col_pointer[c] = triev[lst_p][i];
        // dfs
        if (c == m - 1) dfs(r + 1, 0, 1);
        else dfs(r, c + 1, trieh[row_pointer][i]);
        // back
        col_pointer[c] = lst_p;
    }
}
int main(){
    scanf("%d%d%d%d", &n, &A, &m, &B);
    for (int i = 1; i <= A; ++i){
        scanf("%s", tmp);
        insert(triev, totv);
    }
    for (int i = 1; i <= B; ++i){
        scanf("%s", tmp);
        insert(trieh, toth);
    }
    ans = 0;
    for (int i = 0; i < m; ++i)
        col_pointer[i] = 1;
    dfs(0, 0, 1);
    printf("%d\n", ans);
    return 0;
}

D Monument Tour

待补。。。

F Paris by Night

待补。。。

猜你喜欢

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