ATCoder Regular Contest 172 A~E

A.Chocolate(贪心)

题意:

问题陈述

A t C o d e r AtCoder AtCoder女士决定在情人节向 N N N位朋友分发巧克力。她想送给第 i i i个朋友 ( 1 ≤ i ≤ N ) (1 \leq i \leq N) (1iN)一块 2 A i × 2 A i 2^{A_i}\times 2^{A_i} 2Ai×2Ai大小的正方形巧克力。

她购买了一块大小为 H × W H\times W H×W的长方形巧克力。它被分割成 H H H行和 W W W列的网格,每个单元格都是一个 1 × 1 1\times 1 1×1的正方形。

请判断是否可以将巧克力沿线分割成若干块,以满足她的分配方法。

分析:

对于从大小为 H × W H\times W H×W的长方形巧克力上切出小块的巧克力,显然从角落切会比放在中间或者其他地方带来更多可以分配的块。从最大的开始切,然后将剩下的分成两个长方形放回去用,两种分块情况的影响是相同的,因为能切出的最大正方形由切出两块较小的长方形决定。(如果从最小的开始切可能还要判断一下大小再选择情况)

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL N = 1010;

int h, w, n, tmp = 1;

map<int, int> two;
int cnt[N];
vector<pair<int, int>> v;

void solve() {
    for (int i = 0; i <= 25; i++) {
        two[i] = tmp;
        tmp <<= 1;
    }
    cin >> h >> w >> n;
    for (int i = 1; i <= n; i++) {
        cin >> tmp;
        cnt[tmp]++;
    }

    v.push_back({h, w});
    for (int i = 25; i >= 0; i--) {
        while (cnt[i]) {
            bool flag = false;
            for (int j = 0; j <= v.size() - 1; j++) {
                if (v[j].first >= two[i] and v[j].second >= two[i]) {
                    flag = true;
                    cnt[i]--;
                    int h = v[j].first, w = v[j].second;
                    v.push_back({h - two[i], two[i]});
                    v.push_back({h, w - two[i]});
                    v.erase(v.begin() + j);
                    break;
                }
            }
            if (!flag) {
                cout << "No" << endl;
                return;
            }
        }
    }
    cout << "Yes" << endl;
}

int main() {
    solve();
    return 0;
}

B.AtCoder Language(数学)

题意:

问题陈述

A t C o d e r AtCoder AtCoder语言有 L L L个不同的字符。有多少个由 A t C o d e r AtCoder AtCoder语言中的字符组成的长度为 N N N的字符串 s s s满足以下条件?答案对 998244353 998244353 998244353取模。

  • 字符串 s s s的所有 K K K个字符其子序列都是不同的。更确切地说,从字符串 s s s中提取 K K K个字符,然后在不改变顺序的情况下将它们连接起来,得到 K K K个字符的字符串的方法有 N C K _N\mathrm{C}_K NCK种,而所有这些方法一定会生成不同的字符串。

N C K _N\mathrm{C}_K NCK指的是从 N N N项中选择 K K K的方法的总数。更确切地说, N C K _N\mathrm{C}_K NCK N ! N! N!除以 K ! × ( N − K ) ! K! \times (N-K)! K!×(NK)!的值。

分析:

首先容易发现对于同一个字母,两次相邻的位置中间至少有 n − k n-k nk个字符。
现在有两种思路:对于每一个位置的字符,考虑它下一个的位置或者考虑转移到它有多少种方案。

容易发现,对于第一个字符,它有 L L L种选法;而对于第二个,它有 L − 1 L-1 L1种选法(因为不能选第一个)以此类推,发现对于第 i i i个字符,它不能选的只是在它前面 n − k n-k nk个中出现的,而这显然是不会重复的,所以第 i i i个字符的选法是 L − m i n ( i − 1 , n − k ) L-min(i-1,n-k) Lmin(i1,nk)

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 998244353;

int main() {
    int n, k, l, ans = 1;
    cin >> n >> k >> l;
    if (l < n - k) {
        cout << "0" << endl;
    } else {
        for (int i = 1; i <= n; i++) {
            if (i <= n - k)
                ans = 1ll * ans * (l - i + 1) % mod;
            else
                ans = 1ll * ans * (l - n + k) % mod;
        }
        cout << ans % mod << endl;
    }
    return 0;
}

C.Election(枚举)

题意:

问题陈述

今年的 A t C o d e r AtCoder AtCoder市长选举有两位候选人 A A A B B B N N N位选民已经投了票。选民的编号从 1 1 1 N N N,选民 i i i ( 1 ≤ i ≤ N ) (1\leq i\leq N) (1iN)投票给候选人 c i c_i ci ( 1 ≤ i ≤ N ) (1\leq i\leq N) (1iN)

现在开始计票。在计算每张选票时,将宣布以下三种结果之一的临时结果:

  • 结果 A A A:候选人 A A A得票较多。
  • 结果 B B B:候选人 B B B得票较多。
  • 结果 C C C:候选人 A A A B B B的得票数相同。

计票顺序有一条规则:除 1 1 1号选民外,其他选民的投票必须按照选民编号由大到小的顺序计算( 1 1 1号选民的投票必须按照选民编号由小到大的顺序计算)。(投票人 1 1 1的选票可以随时计算)。

可以宣布多少种不同顺序的临时结果?

临时结果序列:假设 s i s_i si是第 i i i个选票 ( 1 ≤ i ≤ N ) (1\leq i\leq N) (1iN)计票时报告的结果(“A”、“B"或"C”)。临时结果序列指的是字符串 s 1 、 s 2 … s N s_1、s_2\dots s_N s1s2sN

分析:

假设现在 1 1 1号放在位置 i i i,且 i i i及之前的结果都计算完毕。如果 1 1 1号挪到位置 i + 1 i+1 i+1,那么 i i i前面(不包括 i i i)的结果不受影响, i + 1 i+1 i+1及以后的结果也不受影响。所以可以倒着模拟 1 1 1号放在每处的情况。

定义两个变量 s u m 1 sum1 sum1 s u m 2 sum2 sum2,分别代表目前为止 A A A B B B的票数。记录一个字符数组 c i c_i ci,代表前 i i i人投票的临时结果(要从第二个人开始算,可以整体向前移一位,即 i i i 1 1 1 n − 1 n−1 n1)。输入时处理出前 1 1 1 n − 1 n−1 n1人的 s u m 1 sum1 sum1 s u m 2 sum2 sum2(即前 2 2 2 n n n人)。倒推的时候到每一位调整当前的 s u m 1 sum1 sum1 s u m 2 sum2 sum2,代表第 i i i位时,选民 1 1 1插在了第 i − 1 i−1 i1 i i i人之间(第 i i i i + 1 i+1 i+1人之间)。然后再计算当前到第 i i i位临时选票结果,如果和之前的 c i c_i ci不一样,说明有新的序列,对答案加一。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 998244353;
const LL N = 1e6 + 10;
int n, sum1, sum2, ans;
char now, a[N], c[N], t;

int main() {
    cin >> n >> t;
    if (n == 1) {
        cout << 1 << endl;
        return 0;
    }
    for (int i = 1; i < n; i++) {
        cin >> a[i];
        if (a[i] == 'A')
            sum1++;
        else
            sum2++;
        if (sum1 == sum2)
            c[i] = 'C';
        else if (sum1 > sum2)
            c[i] = 'A';
        else
            c[i] = 'B';
    }
    for (int i = n; i; i--) {
        if (a[i] == 'A')
            sum1--;
        else if (a[i] == 'B')
            sum2--;
        if (t == 'A')
            sum1++;
        else
            sum2++;
        if (sum1 == sum2)
            now = 'C';
        else if (sum1 > sum2)
            now = 'A';
        else
            now = 'B';
        if (now != c[i])
            ans++;
        if (t == 'A')
            sum1--;
        else
            sum2--;
    }
    cout << ans << endl;
    return 0;
}

D.Distance Ranking(构造)

题意:

问题陈述

在一个 N N N维空间中放置 N N N个点 p 1 , p 2 , … , p N p_1,p_2,\dots,p_N p1,p2,,pN以满足以下条件:

条件1 点的坐标由 0 0 0 1 0 8 10^8 108之间的整数组成,包括 0 0 0 1 0 8 10^8 108

条件2 对于指定为输入的 ( A 1 , B 1 ) , ( A 2 , B 2 ) , … , ( A N ( N − 1 ) / 2 , B N ( N − 1 ) / 2 ) (A_1,B_1),(A_2,B_2),\dots,(A_{N(N-1)/2},B_{N(N-1)/2}) (A1,B1),(A2,B2),,(AN(N1)/2,BN(N1)/2) d ( p A 1 , p B 1 ) < d ( p A 2 , p B 2 ) < ⋯ < d ( p A N ( N − 1 ) / 2 , p B N ( N − 1 ) / 2 ) d(p_{A_1},p_{B_1})\lt d(p_{A_2},p_{B_2})\lt\dots \lt d(p_{A_{N(N-1)/2}},p_{B_{N(N-1)/2}}) d(pA1,pB1)<d(pA2,pB2)<<d(pAN(N1)/2,pBN(N1)/2)必须成立。 d ( x , y ) d(x,y) d(x,y)表示点 x x x y y y之间的欧几里得距离。

题目保证在问题的约束条件下至少存在一个解。如果存在多个解,只需输出其中一个。

分析:

考虑先构造任意两点之间距离相等, 再细微调整使其满足条件。

易发现有一种很明显的构造方式: p 1 = { 1 , 0 , 0 , 0 , … 0 } p_1=\{1,0,0,0,\dots 0\} p1={1,0,0,0,0} p 2 = { 0 , 1 , 0 , 0 , … 0 } p_2=\{0,1,0,0,\dots 0\} p2={0,1,0,0,0} p 3 = { 0 , 0 , 1 , 0 , … 0 } p_3=\{0,0,1,0,\dots 0\} p3={0,0,1,0,0} … \dots p n = { 0 , 0 , 0 , 0 , … 1 } p_n=\{0,0,0,0, \dots 1\} pn={0,0,0,0,1}

尝试把它转化成距离不等的方式,只需要在原基础上面加上很小很小的数 z z z就可以实现,由于这个 z z z非常小,所以之后的运算中,它的平方项都可以被忽略不计。(类似于求极限)

现在,我们得到的点是: p 1 = { 1 + z 1 , 1 , z 1 , 2 , … , z 1 , n } p_1=\{1+z_{1,1},z_{1,2},\dots,z_{1,n}\} p1={1+z1,1,z1,2,,z1,n} p 2 = { z 2 , 1 , 1 + z 2 , 2 , … , z 2 , n } p_2=\{z_{2,1},1+z_{2,2},\dots,z_{2,n}\} p2={z2,1,1+z2,2,,z2,n}

那么我们再对 d ( x , y ) d(x,y) d(x,y)表示,直接化简并去掉平方项就可以得到:

d ( x , y ) = 2 − 2 ( z x , y + z y , x ) d(x,y)=\sqrt{2-2(z_{x,y}+z_{y,x})} d(x,y)=22(zx,y+zy,x)
于是我们发现影响这个的只是后面的 z x , y z_{x,y} zx,y

那么一种构造方式就非常明显了,我们直接让一个为 0 0 0,另一个按越在后面输入的越小即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 998244353;
const LL N = 25;

int n, a[N][N], m;

int main() {
    cin >> n;
    m = n * (n - 1) / 2;
    for (int i = 1; i <= n; i++)
        a[i][i] = 1e8;
    for (int i = 1, x, y; i <= m; i++) {
        cin >> x >> y;
        a[x][y] = m - i + 1;
    }
    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= n; j++) {
            cout << a[i][j] << " ";
        }
        cout << " ";
    }
    return 0;
}

E.Last 9 Digits(数学)

题意:

判断是否存在一个不是 2 2 2或者 5 5 5倍数的正整数 n n n,使得 n n n^n nn除以 1 0 9 10^9 109的余数是 X X X。如果存在,求最小的 n n n

分析:

暴力枚举后发现似乎当 n < 1 0 9 n<10^9 n<109时,每个余数是唯一的。猜想 n n n对于 ∀ k ∈ N + ∀k∈N^+ kN+ 1 0 k 10^k 10k取模是否也存在当 n < 1 0 k n<10^k n<10k n n n既不是 2 2 2 5 5 5的倍数时每个余数是唯一的。这个猜想是正确的,证明过程可参考官方题解。

由于当 n < 1 0 k n<10^k n<10k n n n既不是 2 2 2 5 5 5的倍数时 n n n ∀ k ∈ N + ∀k∈N^+ kN+ 1 0 k 10^k 10k取模每个余数唯一,我们可以预处理出小于等于一定位数 k k k的所有 n n m o d    1 0 k n^n\mod 10^k nnmod10k,再把它们扔进一个映射中。对于一个数 x x x,先通过 x m o d    1 0 k x\mod10^k xmod10k确定 n n n的末 k k k位,再枚举 1 , 2 , . . . , 1 0 9 − k − 1 1,2,...,10^{9−k}−1 1,2,...,109k1,和确定的 n n n的末 k k k位拼在一起验证 m o d    1 0 9 \mod10^9 mod109是否为 x x x即可。

代码:

#include<bits/stdc++.h>

typedef long long LL;
using namespace std;
const LL mod = 1e9;
LL q, x;
map<LL, LL> vis;

LL qpow(LL a, LL b) {
    LL res = 1;
    while (b) {
        if (b & 1)
            res = res * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return res;
}

int main() {
    for (LL i = 1; i <= 1000000; ++i) {
        if (i % 2 == 0 || i % 5 == 0)
            continue;
        LL now = qpow(i, i);
        vis[now % 1000000] = i;
    }
    cin >> q;
    while (q--) {
        cin >> x;
        LL what = vis[x % 1000000];
        for (LL i = 0; i <= 1000; ++i) {
            LL cnt = what + i * 1000000;
            if (qpow(cnt, cnt) == x) {
                cout << cnt << endl;
                break;
            }
        }
    }
    return 0;
}

赛后交流

在比赛结束后,会在交流群中给出比赛题解,同学们可以在赛后查看题解进行补题。

群号: 704572101,赛后大家可以一起交流做题思路,分享做题技巧,欢迎大家的加入。

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值