ICPC 2021 江西省赛

K. Many Littles Make a Mickle

题目大意

楼塔第i层有i*i个房间,每个房间可以容纳m人,问n层的楼可以容纳多少人

解题思路

前缀和预处理每层楼房间个数,询问根据m直接回答即可

代码实现

#include <bits/stdc++.h>

using i64 = long long;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);

    std::vector<int> pre(101);
    for (int i = 1; i <= 100; i++) {
        pre[i] = pre[i - 1] + i * i;
    }

    int tt;
    std::cin >> tt;

    while (tt--) {
        i64 n, m;
        std::cin >> n >> m;
        std::cout << m * pre[n] << "\n";
    }
}

B. Continued Fraction

题目大意

给定一个分数x/y,将其转化为不超过100层高的连分数形式,输出高度和连分数的每一层整数部分

解题思路

当y为1的时候,已经是一个连分数了直接输出即可,否则一直整除取模直到y为1即可

代码实现

#include <bits/stdc++.h>

using i64 = long long;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);

    int tt;
    std::cin >> tt;

    while (tt--) {
        int x, y;
        std::cin >> x >> y;

        if (y == 1) {
            std::cout << 0 << " " << x << "\n";
            continue;
        }

        std::vector<int> a;
        while (y != 1) {
            a.push_back(x / y);
            int t = x;
            x = y;
            y = t % y;
        }
        a.push_back(x);

        std::cout << a.size() - 1 << " ";
        for (int i = 0; i < a.size(); i++) {
            std::cout << a[i] << " \n"[i == a.size() - 1];
        }
    }
}

L. It Rains Again

题目大意

二维坐标系给n条线段,问他们在x轴上的投影总长是多少

解题思路

对线段根据x坐标排序,计算每个联通部分投影长度即可

代码实现

#include <bits/stdc++.h>

using i64 = long long;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);

    int n;
    std::cin >> n;

    std::vector<std::array<int, 2>> x(n);
    for (int i = 0; i < n; i++) {
        int x1, y1, x2, y2;
        std::cin >> x1 >> y1 >> x2 >> y2;
        x[i][0] = x1;
        x[i][1] = x2;
    }
    sort(x.begin(), x.end());

    int ans = 0, l = -1, r = -1;
    for (auto [x1, x2] : x) {
        if (l == -1 || x1 > r) {
            ans += r - l;
            l = x1;
            r = x2;
        } else {
            r = std::max(r, x2);
        }
    }
    ans += r - l;

    std::cout << ans << "\n";
}

H. Hearthstone So Easy

题目大意

f和p轮流出牌,初始都是n点血量,每回合玩家有两种操作:

  • 给自己增加k点血量
  • 给对手扣除k点血量

当玩家手里没有牌的时候每回合就会开始增加疲劳值,每摸一张牌血量就会扣除疲劳值,初始双方都没有牌,疲劳值是0,血量为0则输掉游戏,p先手问谁获胜

解题思路

p能一回合打死f或者让他只剩1滴血则p赢,其余情况f赢,特判n为1的情况

代码实现

#include <bits/stdc++.h>

using i64 = long long;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);

    int tt;
    std::cin >> tt;

    while (tt--) {
        int n, k;
        std::cin >> n >> k;
        
        if (n == 1) {
            std::cout << "freesin\n";
        } else {
            if (k + 1 >= n) {
                std::cout << "pllj\n";
            } else {
                std::cout << "freesin\n";
            }
        }
    }
}

A. Mio visits ACGN Exhibition

题目大意

n*m的01矩阵,只能往右或下走,问从左上到右下的路径中满足0的数量大于p,1的数量大于q的路径数,对答案模998244353

解题思路

首先考虑最暴力的dp, d p [ i ] [ j ] [ k ] [ l ] dp[i][j][k][l] dp[i][j][k][l],走到 a [ i ] [ j ] a[i][j] a[i][j]的时候有k个0和l个1,这样时空复杂度显然都不够优秀,而走过的格子数量和01数量是可以直接计算的,走了i+j-1个格子如果有k个0,那么就会有i+j-1-k个1,可以优化掉一维,得到转移方程

{ d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k − 1 ] + d p [ i ] [ j − 1 ] [ k − 1 ] , a [ i ] [ j ] = 1 d p [ i ] [ j ] [ k ] = d p [ i − 1 ] [ j ] [ k ] + d p [ i ] [ j − 1 ] [ k ] , a [ i ] [ j ] = 0 \begin{cases} dp[i][j][k] = dp[i - 1][j][k - 1] + dp[i][j -1][k - 1],a[i][j] = 1\\ dp[i][j][k] = dp[i - 1][j][k] + dp[i][j - 1][k],a[i][j] = 0 \end{cases} {dp[i][j][k]=dp[i1][j][k1]+dp[i][j1][k1]a[i][j]=1dp[i][j][k]=dp[i1][j][k]+dp[i][j1][k]a[i][j]=0

最后统计 ∑ k = p n + m − 1 − q d p [ n ] [ m ] [ k ] \sum_{k=p}^{n+m-1-q} dp[n][m][k] k=pn+m1qdp[n][m][k],此时的时空复杂度为 O ( n m ( n + m ) ) O(nm(n+m)) O(nm(n+m)),会爆空间,但是发现每一次的转移只与上一次有关,可以使用滚动数组优化空间复杂度为 O ( 2 m ( n + m ) ) O(2m(n+m)) O(2m(n+m)),得到转移方程

{ d p [ j ] [ k ] = d p [ j ] [ k − 1 ] + d p [ j − 1 ] [ k − 1 ] , d p [ j ] [ 0 ] = 0 , a [ i ] [ j ] = 1 d p [ j ] [ k ] = d p [ j − 1 ] [ k ] + d p [ j ] [ k ] , a [ i ] [ j ] = 0 \begin{cases} dp[j][k] = dp[j][k - 1] + dp[j - 1][k - 1],dp[j][0] = 0,a[i][j] = 1\\ dp[j][k] = dp[j - 1][k] + dp[j][k],a[i][j] = 0 \end{cases} {dp[j][k]=dp[j][k1]+dp[j1][k1]dp[j][0]=0a[i][j]=1dp[j][k]=dp[j1][k]+dp[j][k]a[i][j]=0

d p [ j ] [ k ] dp[j][k] dp[j][k]大小为 d p [ m + 1 ] [ n + m + 1 ] dp[m + 1][n + m + 1] dp[m+1][n+m+1],表示走到 a [ i ] [ j ] a[i][j] a[i][j]有k个0的方案数,i由枚举得到,最后的答案就是 ∑ k = p n + m − 1 − q d p [ m ] [ k ] \sum_{k=p}^{n+m-1-q} dp[m][k] k=pn+m1qdp[m][k]

代码实现

#include <bits/stdc++.h>

using i64 = long long;
const int MOD = 998244353;

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);

    int n, m, p, q;
    std::cin >> n >> m >> p >> q;

    std::vector<std::vector<int>> a(n + 1, std::vector<int>(m + 1)), dp(m + 1, std::vector<int>(n + m + 1));
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            std::cin >> a[i][j];
        }
    }

    dp[1][1 - a[1][1]] = 1;
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= m; j++) {
            if (i == 1 && j == 1) {
                continue;
            }
            if (a[i][j] == 0) {
                for (int k = i + j - 1; k > 0; k--) {
                    dp[j][k] = (dp[j - 1][k - 1] + dp[j][k - 1]) % MOD;
                }
                dp[j][0] = 0;
            } else {
                for (int k = i + j - 1; k >= 0; k--) {
                    dp[j][k] = (dp[j - 1][k] + dp[j][k]) % MOD;
                }
            }
        }
    }

    i64 ans = 0;
    for (int i = p; i <= n + m - 1 - q; i++) {
        ans = (ans + dp[m][i]) % MOD;
    }

    std::cout << ans << "\n";
}

J. LRU

题目大意

有一个缓存队列需要一个一个放入n个数字,如果要放入的数字在缓存队列中则直接将它移动至队尾并认为发生了一次缓存命中,如果队列已满则弹出队首元素,问缓存队列最小容量是多少能发生k次缓存命中,不可能则输出cbddl

解题思路

显然容量和命中次数存在单调性二分答案即可

代码实现

#include <bits/stdc++.h>

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);

    int n, k;
    std::cin >> n >> k;
    std::vector<int> a(n);
    for (int i = 0; i < n; i++) {
        std::cin >> a[i];
    }

    auto check = [&](int x) -> bool {
        int cnt = 0;
        std::list<int> cache;
        std::unordered_map<int, std::list<int>::iterator> pos;

        for (int i = 0; i < n; i++) {
            auto it = pos.find(a[i]);
            if (it != pos.end()) {
                cache.splice(cache.end(), cache, it->second);
                cnt++;
            } else {
                if (cache.size() == x) {
                    int head = cache.front();
                    cache.pop_front();
                    pos.erase(head);
                }
                cache.push_back(a[i]);
                pos[a[i]] = --cache.end();
            }
        }
        return cnt >= k;
    };

    int l = 1, r = 1e5, ans = 0;
    while (l <= r) {
        int mid = (l + r) / 2;
        if (check(mid)) {
            ans = mid;
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    }

    if (!ans) {
        std::cout << "cbddl\n";
        return 0;
    }
    std::cout << ans << "\n";
}

F. Four Column Hanoi Tower

题目大意

四柱汉诺塔,求n个圆盘从A柱移动到D柱的最小操作次数

解题思路

三柱汉诺塔的操作方法,设 d p [ i ] dp[i] dp[i]为i个圆盘的最小操作次数:

  • 将i-1个圆盘从A借助C移动到B,需要 d p [ i − 1 ] dp[i-1] dp[i1]
  • 将第i个圆盘直接移动到C,需要1步
  • 将i-1个圆盘从B借助A移动到C,需要 d p [ i − 1 ] dp[i-1] dp[i1]

因此 d p [ i ] = d p [ i − 1 ] ∗ 2 + 1 dp[i] = dp[i - 1] * 2 + 1 dp[i]=dp[i1]2+1 d p [ 0 ] = 0 dp[0] = 0 dp[0]=0,可得 d p [ i ] = 2 n − 1 dp[i] = 2^n - 1 dp[i]=2n1

对于四柱汉诺塔,也可以进行类似的操作,设 f [ n ] f[n] f[n]为n个圆盘的最小操作次数:

  • 将n-x个圆盘从A借助CD移动到B柱,需要 f [ n − x ] f[n-x] f[nx]
  • 用三柱算法的操作将A上剩余的x个圆盘通过C移动到D,需要 2 x − 1 2^x-1 2x1
  • 将n-x个圆盘从B借助AC移动到D柱,需要 f [ n − x ] f[n-x] f[nx]

得到如下转移方程

f [ n ] = { 1 , n = 1 min ⁡ 1 ≤ x ≤ n − 1 { 2 f [ n − x ] + 2 x − 1 } , n > 1 f[n] = \begin{cases} 1, & n = 1 \\ \min\limits_{1 \leq x \leq n - 1} \{2f[n - x] + 2^x - 1\}, & n > 1 \end{cases} f[n]={1,1xn1min{2f[nx]+2x1},n=1n>1

可以写出一个 n 2 n^2 n2的暴力打表程序,发现两项之间存在规律(证明),按照规律直接预处理即可,注意数据范围需要高精度

f = [float("inf")] * 20
f[0] = 0
f[1] = 1
for n in range(1, 20):
    for x in range(1, n):
        f[n] = min(f[n], 2 * f[n - x] + pow(2, x) - 1)
    print(n, f[n], f[n] - f[n - 1])

代码实现

f = [0] * int(1e4 + 10)
x = cnt1 = cnt2 = 1
for i in range(1, int(1e4 + 1)):
    f[i] = f[i - 1] + x
    cnt1 -= 1
    if cnt1 == 0:
        x *= 2
        cnt2 += 1
        cnt1 = cnt2
for _ in range(int(input())):
    print(f[int(input())])

G. Magic Number Group

题目大意

给定一个数组,多次询问lr区间内出现最多的因子的个数

解题思路

寻找因子其实可以简化为寻找质因子,ai只有1e6,质因子最多只有7个,因此先预处理出所有数字的质因子,然后将问题转化为求区间众数,可以用莫队解决,本题有点但是非常有点卡常,实现过程需要注意常数

代码实现

#include <bits/stdc++.h>

using i64 = long long;
const int N = 1e6 + 10;

std::bitset<N + 1> f;
std::vector<int> minpf(N + 1), cnt(N);
std::vector<std::vector<int>> fac(N, std::vector<int>());

void Eratosthenes(int n) {
    for (int i = 2; i <= n; i++) {
        f[i] = true;
        minpf[i] = i;
    }
    for (int i = 2; i * i <= n; i++) {
        if (f[i]) {
            for (int j = i * i; j <= n; j += i) {
                f[j] = false;
                minpf[j] = i;
            }
        }
    }
}

inline int read() {
    int x = 0, f = 1;
    char ch = getchar();
    while (ch < '0' || ch > '9') {
        if (ch == '-') {
            f = -1;
        }
        ch = getchar();
    }
    while (ch >= '0' && ch <= '9') {
        x = x * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

inline void write(int x) {
    if (x < 0) {
        putchar('-'), x = -x;
    }
    if (x > 9) {
        write(x / 10);
    }
    putchar(x % 10 + '0');
    return;
}

int main() {
    std::ios::sync_with_stdio(false);
    std::cin.tie(0);
    std::cout.tie(0);

    Eratosthenes(N);

    int tt = read();

    while (tt--) {
        int n = read(), q = read();
        int size = std::sqrt(n);
        int bnum = ceil((double)n / size);

        std::vector<int> a(n + 1), block(n + 1), b;
        for (int i = 1; i <= bnum; i++) {
            for (int j = (i - 1) * size + 1; j <= i * size && j <= n; j++) {
                block[j] = i;
            }
        }

        for (int i = 1; i <= n; i++) {
            a[i] = read();
        }
        b = a;

        for (int i = 1; i <= n; i++) {
            if (!fac[a[i]].empty()) {
                continue;
            }
            while (b[i] > 1) {
                int x = minpf[b[i]];
                fac[a[i]].push_back(x);
                while (b[i] % x == 0) {
                    b[i] /= x;
                }
            }
        }

        std::vector<std::array<int, 3>> ask(q);
        for (int i = 0; i < q; i++) {
            ask[i][0] = read();
            ask[i][1] = read();
            ask[i][2] = i;
        }

        std::sort(ask.begin(), ask.end(), [&](std::array<int, 3> a, std::array<int, 3> b) {
            if (block[a[0]] != block[b[0]]) {
                return block[a[0]] < block[b[0]];
            }
            if (block[a[0]] & 1) {
                return a[1] < b[1];
            } else {
                return a[1] > b[1];
            }
        });

        int l = 1, r = 0, now = 0;
        std::vector<int> ans(q), freq(n + 1);
        for (auto &[L, R, idx] : ask) {
            while (l > L) {
                l--;
                for (auto x : fac[a[l]]) {
                    freq[cnt[x]]--;
                    cnt[x]++;
                    freq[cnt[x]]++;
                    if (cnt[x] > now) {
                        now = cnt[x];
                    }
                }
            }
            while (r < R) {
                r++;
                for (auto x : fac[a[r]]) {
                    freq[cnt[x]]--;
                    cnt[x]++;
                    freq[cnt[x]]++;
                    if (cnt[x] > now) {
                        now = cnt[x];
                    }
                }
            }
            while (l < L) {
                for (auto x : fac[a[l]]) {
                    freq[cnt[x]]--;
                    if (cnt[x] == now && freq[cnt[x]] == 0) {
                        now--;
                    }
                    cnt[x]--;
                    freq[cnt[x]]++;
                }
                l++;
            }
            while (r > R) {
                for (auto x : fac[a[r]]) {
                    freq[cnt[x]]--;
                    if (cnt[x] == now && freq[cnt[x]] == 0) {
                        now--;
                    }
                    cnt[x]--;
                    freq[cnt[x]]++;
                }
                r--;
            }
            ans[idx] = now;
        }

        for (int i = l; i <= r; i++) {
            for (auto x : fac[a[i]]) {
                cnt[x] = 0;
            }
        }

        for (int i = 0; i < q; i++) {
            write(ans[i]);
            putchar('\n');
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值