Codechef January Challenge 2021 Division 3 部分题解

January Challenge 2021 Division 3

Chef and Ants

如果只有一根线,两只蚂蚁相撞可以看成沿着原路前进,这和两只蚂蚁都掉头显然等价。答案就是蚂蚁相撞的次数:负半轴蚂蚁数量 ( imes) 正半轴蚂蚁数量

有了这样的转化,多条轴的问题也容易解决了

如果两只蚂蚁不是在中心相撞,这种情况和一根线的一样处理

如果在中心相撞,要注意两个点:

原本在答案中算多此的现在算一次,减去多余

对于每根轴上的蚂蚁,原本是和另一半还没过界的蚂蚁相撞,现在改为和身后的蚂蚁相撞

也就是在原来的基础上稍作修改即可

#pragma GCC optimize(2)
#include <bits/stdc++.h>
using namespace std;
// #define int long long
void read (int &x) {
    char ch = getchar(); x = 0; int f = 0;
    while (!isdigit(ch)) { if (ch == '-') f = 1; ch = getchar(); }
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
    if (f) x = -x;
} const int N = 2e5 + 5, M = 5e5 + 5;
int n, m, c[N], b[M]; vector<int> a[N]; unordered_map<int, int> mp;
signed main() {
    int T; read (T);
    while (T--) {
        read (n); long long res = 0; mp.clear();
        for (int i = 1; i <= n; ++i) a[i].clear();
        for (int i = 1; i <= n; ++i) {
            read (c[i]); a[i].resize (c[i] + 1); int cnt = 0;
            for (int j = 1; j <= c[i]; ++j)
                read (a[i][j]), cnt += (a[i][j] < 0), ++mp[abs (a[i][j])];
            res += 1ll * cnt * (c[i] - cnt);
        }
        // cout << res << endl;
        for (int i = 1; i <= n; ++i) {
            int cnt = 0;
            for (int j = 1; j <= c[i]; ++j)
                b[j] = a[i][j], cnt += (b[j] < 0);
            for (int j = 1, k = c[i]; j <= cnt; ++j) {
                while (k > cnt && b[k] + b[j] >= 0) --k;
                if (mp[abs (b[j])] > 1) {
                    res += j - 1 - (c[i] - k);
                    // cout << j << ' ' << j - 1 - (c[i] - k) << endl;
                }
            }
            for (int j = c[i], k = 1; j > cnt; --j) {
                while (k <= cnt && b[k] + b[j] < 0) ++k;
                if (mp[b[j]] > 1) {
                    res += c[i] - j - (k - 1);
                    // cout << j << ' ' << c[i] - j - (k - 1) << endl;
                }
            }
        } //res /= 2;
        for (auto i : mp) if (i.second > 1) ++res;
        printf ("%lld
", res);
    }
    return 0;
}

Guess the Tiling

如果想要知道答案,显然要让一个 (2 imes 2) 的方格内的线处于特殊位置(形成正方形),这样 (ask) 后答案会改变,就可以判断了

我们采取“边缘包围中心”的策略,就是先把左上角四个才出来,然后猜最上面一层和最左边一层,中间的都很好判断了。具体怎么判断还是很好想的

主要的难点:平均每个格子只能询问 (4) 次,这就要求严格的乱搞能力,也就是说在枚举周围格子状态(用二进制表示)的时候,每次只能改变一个二进制位。对于左上角的四个,以嘴上角的那个为“关键点”(根据操作它后答案是否改变来判断是否形成正方形),要枚举其他三个的状态,(2^3=8),给出一种顺序使相邻两个只有一个二进制位不同:(0, 1, 3, 2, 6, 4, 5, 7),而对于最左侧和最右侧(两层),每次推进一层,以最左侧为例:以左上的为关键点,右上的已经猜出来,直接转到关键状态,剩下两个的状态要枚举:(0,1,3,2)。中间的每次处理一个,其他三个都已知,直接转到目标状态,一次询问即可

代码的四个注释简短的体现了四种情况

#include <bits/stdc++.h>
using namespace std;
const int N = 205;
int n, q, cnt, la, a[N][N]; 
void ask (int x, int y) {
    printf ("1 %d %d
", x, y);
    fflush (stdout); a[x][y] ^= 1;
    cin >> cnt;
}
int k[8] = {0, 1, 3, 2, 6, 4, 5, 7}, p[3] = {1, 2, 4};
signed main() {
    ios_base::sync_with_stdio (0);
    cin.tie (0); //cout.tie (0);
    int T; cin >> T;
    while (T--) {
        cin >> n >> q >> cnt;
        memset (a, 0, sizeof (a));
/*
左上角
x 0
1 2
*/
        for (int i = 0; i < 8; ++i) {
            for (int j = 0, x, y; j < 3; ++j) {
                if (j == 0) x = 1, y = 2;
                if (j == 1) x = 2, y = 1;
                if (j == 2) x = y = 2;
                if ((k[i] & p[j]) && !a[x][y]) ask (x, y);
                else if (!(k[i] & p[j]) && a[x][y]) ask (x, y);
            }
            la = cnt, ask (1, 1);
                if (cnt == la + 1) {
                a[1][1] = a[2][2] = 0;
                a[1][2] = a[2][1] = 1;
                break;
            }
            if (cnt == la - 1) {
                a[1][1] = a[2][1] = a[1][2] = 1;
                a[2][2] = 0; break;
            }
        }
/*
左边缘
x 定
0 1
*/
        for (int t = 3; t <= n; ++t) {
            if (!a[t - 1][2]) ask (t - 1, 2);
            for (int i = 0; i < 4; ++i) {
                for (int j = 0; j < 2; ++j) {
                    if ((k[i] & p[j]) && !a[t][j + 1]) ask (t, j + 1);
                    else if (!(k[i] & p[j]) && a[t][j + 1]) ask (t, j + 1);
                }
                la = cnt, ask (t - 1, 1);
                if (cnt == la + 1) {
                    a[t - 1][1] = a[t][2] = 0;
                    a[t - 1][2] = a[t][1] = 1; break;
                }
                if (cnt == la - 1) {
                    a[t - 1][1] = a[t - 1][2] = a[t][1] = 1;
                    a[t][2] = 0; break;
                }
            }
        }
/*
右边缘
x 0
定 1
*/
        for (int t = 3; t <= n; ++t) {
            if (!a[2][t - 1]) ask (2, t - 1);
            for (int i = 0; i < 4; ++i) {
                for (int j = 0; j < 2; ++j) {
                    if ((k[i] & p[j]) && !a[j + 1][t]) ask (j + 1, t);
                    else if (!(k[i] & p[j]) && a[j + 1][t]) ask (j + 1, t);
                }
                la = cnt, ask (1, t - 1);
                if (cnt == la + 1) {
                    a[1][t - 1] = a[2][t] = 0;
                    a[2][t - 1] = a[1][t] = 1; break;
                }
                if (cnt == la - 1) {
                    a[1][t - 1] = a[2][t - 1] = a[1][t] = 1;
                    a[2][t] = 0; break;
                }
            }
        }
/*
定 定
定 x
*/
        for (int i = 3; i <= n; ++i)
            for (int j = 3; j <= n; ++j) {
                if (a[i - 1][j - 1]) ask (i - 1, j - 1);
                if (!a[i][j - 1]) ask (i, j - 1);
                if (!a[i - 1][j]) ask (i - 1, j);
                la = cnt, ask (i, j);
                a[i][j] = (cnt < la);
            }
        puts ("2");
        for (int i = 1; i <= n; ++i) {
            for (int j = 1; j <= n; ++j)
                putchar ('0' + a[i][j]), putchar (' '); puts ("");
        } fflush (stdout); cin >> cnt;
    }
}

And-Or Game

如果只有一种 (|) 运算,显然如果每次操作都发生更改,最多更改 (20) 次,做 (20)(fwt)

同样,可以把一堆 (|) 和一堆 (&) 看成一次运算,每次也只能变多,这样也最多更改 (20) 次,一共 (40)(fwt)卡卡常就好了

#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
using namespace std;
#define ll long long
void read (int &x) {
    char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 1024 * 1024 * 2 + 5, mod = 1e9 + 7;
int n, m, mx, lim; ll f[N], g[N], res[N], la[N];
#define up(x, y) ((x += y) >= mod && (x -= mod))
void fwt_or (ll *a, int opt) {
    for (int i = 1; i < lim; i <<= 1)
        for (int j = 0; j < lim; j += (i << 1))
            for (int k = 0; k < i; ++k)
                if (opt == 1) up (a[i + j + k], a[j + k]);
                else up (a[i + j + k], mod - a[j + k]);
}
void fwt_and (ll *a, int opt) {
    for (int i = 1; i < lim; i <<= 1)
        for (int j = 0; j < lim; j += (i << 1))
            for (int k = 0; k < i; ++k)
                if (opt == 1) up (a[j + k], a[i + j + k]);
                else up (a[j + k], mod - a[i + j + k]);
}
ll qpow (ll x, int y) {
    ll t = 1; while (y) { if (y & 1) t = t * x % mod; x = x * x % mod, y >>= 1; } return t;
}
signed main() {
    int T; read (T);
    while (T--) {
        read (n), read (m); lim = 1, mx = 0;
        for (int i = 1, x; i <= n; ++i)
            read (x), mx = max (mx, x), f[x] = 1;
        for (int i = 1, x; i <= m; ++i)
            read (x), mx = max (mx, x), g[x] = 1;
        while (lim <= mx) lim <<= 1;
        f[0] = res[0] = 1, g[lim - 1]= 1;
        fwt_or (f, 1), fwt_and (g, 1);
        for (int i = 0; i < lim; ++i) f[i] = qpow (f[i], 21);
        for (int i = 0; i < lim; ++i) g[i] = qpow (g[i], 21);
        while (1) {
            memcpy (la, res, sizeof (res));
            int tag = 0;
            fwt_or (res, 1);
            for (int i = 0; i < lim; ++i) res[i] = res[i] * f[i] % mod;
            fwt_or (res, -1);
            fwt_and (res, 1);
            for (int i = 0; i < lim; ++i) res[i] = res[i] * g[i] % mod;
            fwt_and (res, -1);
            for (int i = 0; i < lim; ++i)
                if (!la[i] && res[i]) { tag = 1; break; }
            if (!tag) break;
        }
        int cnt = 0;
        for (int i = 0; i < lim; ++i) if (res[i]) ++cnt;
        printf ("%d
", cnt);
        for (int i = 0; i < lim; ++i) f[i] = g[i] = res[i] = 0;
    }
    return 0;
}

Blackjack

这题最后还是没想出正解。。。写了个 (O(frac{n^3log_n}{w})) 卡过去了,正解是 (O(frac{n^3}{w}))

题意就是枚举一个边界 (x),然后在右侧选 (y) 个,左侧选 (x-y) 个,和在 ([l,r]) 内,使 (y) 最小

前后缀背包是肯定要做的,(L_{i,j,k}) 表示前 (i) 个物品,选 (j) 个,和为 (k) 是否可行,(R) 同理。

只有 (0/1) 状态显然是可以用 (bitset) 优化的,推完一遍是 (O(frac{n^3}{w}))

brute solution:

枚举边界 (x),枚举 (L) 的个数 (j),然后每一个可行的 (k)(R) 中对应一段区间,这个也是可以 (bitset) 优化的(初始一个一段是 (1) 的,(&) 一下看一下有没有 (1)),应该是(O(frac{n^4}{w}))

my solution:

先把 (L,R) 的二 、三两维翻转,变成 (L_{i,j,k}) 表示前 (i) 个物品,和为 (j),选 (k) 个是否可行

同样枚举 (x),对于 (L) 中每一种和,在 (R) 中对应一段下标,可以用数据结构维护(st表,线段树...)。然后用 (\_Find\_first()) 找第一个 (1),即最小的答案

有一个需要注意的地方是由于 (L)(R) 选的物品要满足数量和是个定值,在推 (L) 的时候会发生一些改变,(L)(R) 的方程式不太一样

#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
// using namespace std;
void read (int &x) {
    char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
    while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 1005;
int n, res, mx, L, R, len, a[N], lg[N];
std::bitset<505> f[N][N], g[N][N], tmp, c[12][N];
void build (int k) {
    for (int i = 0; i<= R; ++i) c[0][i] = g[k][i];
    // for (int i = R + 1; i <= R + (1 << len); ++i) c[0][i].reset();
    for (int i = 1; i <= len; ++i) {
        int lim = R - (1 << i);
        for (int j = 0; j <= lim; ++j)
            c[i][j] = c[i - 1][j] | c[i - 1][j + (1 << (i - 1))];
    }
}
void query (int l, int r) {
    l = std::max (l, 0), r = std::min (r, R); int t = lg[r - l + 1];
    tmp |= c[t][l] | c[t][r - (1 << t) + 1];
}
int get (int x) {
    int ans = 10000; build (x + 1);
    for (int i = 0; i <= R; ++i) {
        // int l = L - i, r = R - i;
        tmp.reset(); query (L - i, R - i); tmp &= f[x][i];
        ans = std::min (ans, (int)tmp._Find_first());
    }
    return ans;
}
signed main() {
    int T; read (T); lg[0] = -1;
    for (int i = 1; i <= 1000; ++i) lg[i] = lg[i >> 1] + 1;
    while (T--) {
        read (n), read (L), read (R);
        int zero = 0, ss = 0; mx = R;
        for (int i = 1; i <= n; ++i) read (a[i]);
        for (int i = 1; i <= n; ++i) {
            ss += a[i]; if (ss >= L && ss <= R) { zero = 1; break; }
        }
        if (zero) { puts ("0"); continue; }
        for (int i = 0; i <= mx; ++i)
            f[0][i].reset(), g[n + 1][i].reset();
        // for (int i = 0; i <= 1000) qwq[i].reset();
        f[0][0][0] = g[n + 1][0][0] = 1;
        for (int i = 1; i <= n; ++i) {
            for (int j = 0; j <= mx; ++j) f[i][j] = f[i - 1][j] << 1;
            for (int j = mx; j >= a[i]; --j)
                f[i][j] |= f[i - 1][j - a[i]];
        }
        for (int i = n; i >= 1; --i) {
            for (int j = 0; j <= mx; ++j) g[i][j] = g[i + 1][j];
            for (int j = mx; j >= a[i]; --j)
                g[i][j] |= g[i + 1][j - a[i]] << 1;
        }
        len = lg[R - L + 1]; res = 100000;
        for (int i = 1; i < n; ++i)
            res = std::min (res, get (i));
        if (res > n / 2) res = -1;
        printf ("%d
", res);
    }
    return 0;
}

very good solution:

其实和普通 (dp) 优化方法是一样的,记录 (0/1) 状态往往产生很多浪费,即使有 (bitset) 加持。

这种时候可以想一想转向记录最佳状态。

(L,R) 还是按照上面那样翻转二、三维

(L'_{i,j}) 表示选了和为 (j),选了 (i) 个数,满足 (L_{x,i,j}=1) 的最小的 (x)

(R'_{i,j}) 表示和为 (j),选了 (i) 个数,满足 (R_{x,i,j}=1) 的最大的 (x)

在原来递推求 (L,R) 的基础上,每次计算 (L_x xor L_{x-1}),多出来的 (1) 就是新产生的可行方案,在 (L') 中修改。找所有的 (1) 依然可以用 (bitset) 优化遍历,求出 (L',R') 的复杂度依然是 (O(frac{n^3}{w}))

有了 (L',R'),枚举 (R') 的每一个 (i,j),此时 (L') 中的 (i’) 在一段区间内,(j') 随意(所以可以把所有 (j') 缩到一起变成一维数组),如果存在 (L'_{i',j'}<R'_{i,j}),则答案可以为 (j)。这个过程同样可以数据结构优化,复杂度 (O(n^2log_n))

所以复杂度为 (O(n^2log_n+frac{n^3}{w})),此问题得到完美解决,然而代码还没有写。。。

原文地址:https://www.cnblogs.com/whx666/p/14295514.html