W
H
X

AtCoder Grand Contest 003 CDEF

AGC003

C - BBuBBBlesort!

翻转相邻三个相当于交换 \(a_i,a_{i+2}\)。离散化把值域弄到 \([1,n]\),用尽量少的操作一使奇数在奇数位,偶数在偶数位,然后使用操作二一定可以让序列归位

每次操作一都可以让两个数奇偶归位,因为一定可以先通过操作二使一奇一偶相邻,然后让操作一产生 \(2\) 的贡献。统计一下 \(/2\) 就是答案

#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 = 1e5 + 5;
int n, a[N], b[N];
signed main() {
    read (n);
    for (int i = 1; i <= n; ++i) read (a[i]), b[i] = a[i];
    sort (b + 1, b + n + 1);
    for (int i = 1; i <= n; ++i)
        a[i] = lower_bound (b + 1, b + n + 1, a[i]) - b;
    int tmp = 0;
    for (int i = 1; i <= n; ++i)
        if ((a[i] + i) & 1) ++tmp;
    cout << tmp / 2 << endl;
    return 0;
}

D - Anticube

分类题。如果按照常规方法 \(\sqrt{A_i}\) 分解质因数大概率会 T,在 \(\sqrt[3]{A_i}\) 的范围内分解质因数成了不错的选择,复杂度不会过高,情况也不会很复杂。大致可以把数分为 \(4\) 类:

1、形如 \(x^3\),这样的数只能选一个

2、分解质因数后剩下的数为 \(tmp\)\(tmp= x\)

3、\(tmp = x^2\)

4、\(tmp = 1\)

第二类只能和第三类配对,第四类内部配对。对于两个矛盾的数,选个数较多的。\(map\) 搞一搞

#include <bits/stdc++.h>
using namespace std;
#define int 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 = 1e5 + 5, M = 1e10;
int n, mx, a[N], m, p[N], k[N];
void getprime () {
    for (int i = 2; i <= mx; ++i) {
        if (!k[i]) p[++m] = i;
        for (int j = 1; j <= m && p[j] * i <= mx; ++j) {
            k[i * p[j]] = 1; if (i % p[j] == 0) break;
        }
    }
}
unordered_map<int, int> is, one, cnt, pp, qq; int res = 0, e = 0;
#define fi first
#define se second
signed main() {
    read (n);
    for (int i = 1; i <= n; ++i) read (a[i]);
    while (mx * mx * mx <= M) ++mx;
    getprime ();
    // for (int i = 1; i <= m; ++i) printf ("%lld\n", p[i]);
    for (int i = 1; i <= mx; ++i) is[i * i * i] = 1;
    for (int i = 1; i <= n; ++i) {
        if (is[a[i]]) { e |= 1; continue; }
        int tmp = 1, qwq = 1, now = a[i], t, tag = 0;
        for (int j = 1; j <= m && p[j] <= now; ++j) {
            t = 0;
            while (now % p[j] == 0) now /= p[j], ++t; t %= 3;
            for (int q = 1; q <= t; ++q) qwq *= p[j];
            t = (3 - t) % 3;
            for (int q = 1; q <= t; ++q) {
                tmp *= p[j]; if (tmp > M) { tag = 1; break; }
            }
            if (tag) break;
        }
        if (tag) ++res;
        else {
            if (now == 1) ++one[tmp], ++cnt[qwq];
            else {
                int sq = sqrt (now);
                if (sq * sq == now) ++pp[tmp * sq];
                else ++qq[qwq * now];
            }
        }
    }
    int s = 0, ss = 0;
    for (auto i : one) {
        if (i.se > cnt[i.fi]) s += i.se;
        if (i.se == cnt[i.fi]) ss += i.se;
    }
    res += s + ss / 2;
    for (auto i : pp) if (i.se >= qq[i.fi]) res += i.se;
    for (auto i : qq) if (i.se > pp[i.fi]) res += i.se;
    printf ("%lld\n", res + e);
    return 0;
}

E - Sequential operations on Sequence

一串下降的数只有最后一次有用,用单调栈维护序列 \(a\) 单调上升。之后的做法就比较神奇了

\(i\) 次形成的数组是 \(i-1\) 次的复制 \(\frac{a_i}{a_{i-1}}\) (取下整)遍加上后面一小段获得的。很可怜的是只有信息缩小到第一段才能快速计算答案。那就重复除法取模的过程,直到效果落到第一段上,这时可以差分处理。对于不在第一段的,记录一下被弄了几次,统计递归。可以看出这个过程要从后往前处理

#include <bits/stdc++.h>
using namespace std;
#define int 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 = 1e5 + 5;
int n, m, q, a[N], f[N], d[N];
void work (int s, int c) {
    if (!s) return;
    int w = upper_bound (a + 1, a + m + 1, s) - a - 1;
    if (!w) d[1] += c, d[s + 1] -= c;
    else f[w] += s / a[w] * c, work (s % a[w], c);
}
signed main() {
    read (n), read (q);
    a[++m] = n;
    for (int i = 1, x; i <= q; ++i) {
        read (x);
        while (x < a[m] && m) --m;
        a[++m] = x;
    }
    f[m] = 1;
    for (int i = m; i > 1; --i)
        f[i - 1] += a[i] / a[i - 1] * f[i], work (a[i] % a[i - 1], f[i]);
    d[1] += f[1], d[a[1] + 1] -= f[1];
    for (int i = 1; i <= n; ++i) printf ("%lld\n", d[i] += d[i - 1]);
    return 0;
}

F - Fraction of Fractal

这个题看题解可能还是自己想来的快。

如果两个一级分形上下叠在一起,边界上能连通,左右叠在一起也可以,记为类型 \(1\)

如果只有左后或上下一种可以,记为类型 \(2\)

如果都不行,记为类型 \(3\)

对于类型 \(1\),无论怎样展开都是全部连通的,直接输出 \(1\)

对于类型 \(3\) ,答案就是 \(k-1\) 级分形中黑格的数量,即 \(cnt^{k-1}\)。因为最后一次展开后有 \(cnt^{k-1}\) 个一级分形,这些一级分形相互之间没有通道连通,单独成为一个连通块

\(2\) 是重点,以左右的情况为例(样例就是一个左右的类型 \(2\)),有了上面两种分析,可以发现答案是:\(k-1\) 级分形中黑格的数量减去左右相邻的黑格对数量。

只要求出左右相邻的黑格对数就 \(ok\) 了。

\(k\) 的范围很大,莫非这就是一个线性递推式的矩阵乘法优化?

猜对了!

先定义变量(和代码一致):\(ans\),当前左右相邻的黑格对数量;\(cnt\),一级分形中黑格数量;\(num’\),当前分形左右摆放后边界处相连的行数;\(tot\),一级分形中左右相邻黑格对数量;\(num\),一级分形左右摆放后边界处相连的行数。样例中 \(cnt=6,tot=2,num=2\)

有递推式 \(ans = ans \times cnt + num' \times tot, num' = num'\times num\)

可以这样理解:把 \(p\) 级分形按照一级分形划分成 \(h\times w\) 块,一级分形中白色的区域全都是白色,黑色的区域是一个 \(p-1\) 级分形。\(ans\times cnt\) 表示 \(cnt\)\(p-1\) 级分形内部的答案,\(num'\times tot\) 表示相邻的 \(p-1\) 级分形左右边界上相邻的数量

#include <bits/stdc++.h>
using namespace std;
#define int 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 = 1005, mod = 1e9 + 7;
int n, m, k, cnt, tot, num; char a[N][N];
int qpow (int x, int y) {
    int t = 1; while (y) { if (y & 1) t = t * x % mod; x = x * x % mod, y /= 2; } return t;
}
struct mat {
    int a[2][2];
    void clear () { memset (a, 0, sizeof (a)); }
    mat operator * (const mat &b) const {
        mat t; t.clear();
        for (int i = 0; i < 2; ++i)
            for (int j = 0; j < 2; ++j)
                for (int k = 0; k < 2; ++k) (t.a[i][j] += a[i][k] * b.a[k][j]) %= mod;
        return t;
    }
} res, u;
signed main() {
    read (n), read (m), read (k); --k;
    for (int i = 1; i <= n; ++i) {
        scanf ("%s", a[i] + 1);
        for (int j = 1; j <= m; ++j) a[i][j] = (a[i][j] == '#'), cnt += a[i][j];
    }
    int ka = 0, kb = 0;
    for (int i = 1; i <= n; ++i) ka |= (a[i][1] & a[i][m]);
    for (int i = 1; i <= m; ++i) kb |= (a[1][i] & a[n][i]);
    if (!ka && !kb) return printf ("%lld\n", qpow (cnt, k)), 0;
    if (ka && kb) return puts ("1"), 0;
    // ans = ans * cnt + num' * tot, num' = num' * num
    // cnt,一级分形中黑格数量;num’,当前分形左右摆放后边界处相连的行数;tot,一级分形中左右相邻黑格对数量;num,一级分形左右摆放后边界处相连的行数
    if (ka) {
        for (int i = 1; i <= n; ++i)
            for (int j = 1; j < m; ++j) tot += (a[i][j] && a[i][j + 1]);
        for (int i = 1; i <= n; ++i) num += (a[i][1] && a[i][m]);

    } else {
        for (int j = 1; j <= m; ++j)
            for (int i = 1; i < n; ++i) tot += (a[i][j] && a[i + 1][j]);
        for (int i = 1; i <= m; ++i) num += (a[1][i] && a[n][i]);
    }
    int ans = qpow (cnt, k);
    res.a[0][1] = 1, u.a[0][0] = cnt, u.a[1][0] = tot, u.a[1][1] = num;
    while (k) { if (k & 1) res = res * u; u = u * u, k >>= 1; }
    return printf ("%lld\n", (ans += mod - res.a[0][0]) % mod), 0;
}

posted @ 2020-12-26 21:14  -敲键盘的猫-  阅读(131)  评论(0编辑  收藏  举报