【解题报告】Codeforces Round #383 (Div. 2)

题目链接


A. Arpa’s hard exam and Mehrdad’s naive cheat(Codeforces 742A)

思路

1378n 看似很可怕,但实际上如果只求其个位数的话,问题就可以转化成求 8n 的个位数。在纸上用小规模的 n 演算 8n 后发现,其个位数是有规律的。具体是 8,4,2,6 四个数字呈周期性出现。
于是将 nmod4 的方式分类就好了。另外必须小心 13780

代码

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

int n;

int main() {
//  freopen("data.txt", "r", stdin);
    scanf("%d", &n);
    if(n == 0) {
        puts("1");
    }
    else if(n % 4 == 1) {
        puts("8");
    }
    else if(n % 4 == 2) {
        puts("4");
    }
    else if(n % 4 == 3) {
        puts("2");
    }
    else {
        puts("6");
    }
    return 0;
}

B. Arpa’s obvious problem and Mehrdad’s terrible solution(Codeforces 742B)

思路

看到题目很自然地想枚举二元组 (i,j) 去验证是否有 a[i]a[j]=x 。但显然 105 这种数据规模是不允许 O(n2) 的算法出现的。
在数据中寻找突破点。我们发现 a[i] 的值的上限是 105 ,这就告诉我们,可以用数组开 105 的空间(这里用 cnt 数组)来记录每个数值出现次数。这样我们只用枚举 j ,访问过的 a[j] 都做统计: cnt[a[j]]++ 。对当前 j ,统计 a[i]a[j]=x 的次数等价于统计 a[j]x=a[i] 的个数。这意味着如果想知道 a[i]a[j]=x 的次数,我们只需查询 cnt[a[j]x] 就可以了。

代码

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

typedef long long ll;
const int maxn = 1e5 + 10;
int n, x, a[maxn], cnt[maxn<<2];
ll ans;

int main() {
//  freopen("data.txt", "r", stdin);
    scanf("%d%d", &n, &x);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a[i]);
    }
    for(int i = 1; i <= n; i++) {
        ans += cnt[a[i]^x];
        cnt[a[i]]++;
    }
    printf("%I64d\n", ans);
    return 0;
}

C. Arpa’s loud Owf and Mehrdad’s evil plan(Codeforces 742C)

思路

这题看上去貌似不太容易发现端倪,但是如果注意到 icrushi 的关系的话,就会发现题中的所有的i构成了有向图(可能是不连通的)。
根据题目给出的信息,有向图中的每个节点的出度为 1 (这个很重要,避免了很多奇怪的环图的出现)。
然后就是处理奇怪的规则:对于任意的 x ,若从 x 出发到达 y 的话,则从 y 出发也要到达 xx 可以等于 y )。直觉告诉我们图中一定得有环,而且每个点都要在环上。

  • 先考虑只有一个环的情况( n 表示该环上的点的个数, t 表示该环满足规则需要的最小 t )。当环上有奇数个点时, x=y 必须成立才能满足规则,也就是说对这个环,只有 t=n 才能满足规则。当环上有偶数个点时当然可以模仿奇数情况,但是 t=n2 显然可以得到更小的解(表示 x,y 互为“在对面的点”)。
  • 其次考虑有多个环的情况(只可能在不同的来连通分量上)。当 t 为所有环的 t 的倍数时,所有环上的规则仍能运行良好。于是问题就转化成求所有环的 t 的最小公倍数。
  • 最后考虑有点不出现在环上的情况。对在环上的点计数,若计数结果不等于 n 的话这种情况就发生了(点的出度为 1 保证了计数不会重复)。

根据以上分析,只要找到图中所有的环的长度,问题就迎刃而解了。那么我们可以对图用类似拓扑排序的搜索(这里是DFS)的方法找环(将未访问,访问过,正在访问的点分别标记为 1,1,0 ,当访问标记为 0 的点时就说明遇到了环)。对每个节点记录下该点在搜索树中的深度,就能在碰到环的时候计算出环的长度了。

代码

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

const int maxn = 110;
bool fail;
// vis保存了{-1, 0, 1}三种值
int n, a, ans, sum, vis[maxn], dep[maxn];
vector <int> vec, G[maxn];

// 找到所有的环并计算其长度
void dfs(int u, int d) {
    vis[u] = 0;
    dep[u] = d;
    for(int v : G[u]) {
        // 发现环的存在
        if(vis[v] == 0) {
            // 计算长度并保存下来
            vec.push_back(d - dep[v] + 1);
            // 用于判断是否有点不在环上
            sum += d - dep[v] + 1;
        }
        // 继续访问下个节点
        else if(vis[v] < 0) {
            dfs(v, d + 1);
        }
        // 用于判断是否有点不在环上(保险措施)
        else {
            fail = true;
        }
    }
    vis[u] = 1;
}

// 计算最小公倍数
int lcm(int x, int y) {
    return x / __gcd(x, y) * y;
}

int main() {
//  freopen("data.txt", "r", stdin);
    scanf("%d", &n);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &a);
        G[i].push_back(a);
    }
    // 访问每个环
    memset(vis, -1, sizeof(vis));
    for(int i = 1; i <= n; i++) {
        if(vis[i] < 0) {
            dfs(i, 1);
        }
    }
    if(fail == true || sum != n) {
        puts("-1");
        return 0;
    }
    ans = 1;
    // 计算最终结果
    for(int i = 0; i < vec.size(); i++) {
        if(vec[i] % 2 == 0) {
            ans = lcm(ans, vec[i] >> 1);
        }
        else {
            ans = lcm(ans, vec[i]);
        }
    }
    printf("%d\n", ans);
    return 0;
}

D. Arpa’s weak amphitheater and Mehrdad’s valuable Hoses(Codeforces 742D)

思路

如果熟悉揹包问题的人可能比较容易反应过来这是个揹包问题的变形。但是即使熟悉揹包问题,也会觉得“小团体”要么全拿要么只能拿一个的规则十分棘手。
考虑将有 x 个人的小团体看做一个“联合实体”,也就是一个人。那么我们可以将小团体看成一个集合,其中有 x+1 个元素,其中包括 x 个人和我们创造的“联合实体”。显然对这个集合,我们要么不取这个集合中的元素,要么只取集合中的一个元素。我们可以处理出所有的集合(通过用并查集合并出“联合实体”)。
处理出这些集合又该怎么办呢?熟悉分组揹包的人会发现这是一个分组揹包问题。每个集合是分组揹包中的一个物品组,用一组的物品反复利用上一组的信息更新 DP 二维表中的一行然后每组新开一行即可。

代码

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

const int maxn = 3000;
bool vis[maxn];
int n, m, k, f, u, v, cnt, ans;
int b[maxn], w[maxn], B[maxn], W[maxn], p[maxn], d1[maxn], d2[maxn];
vector <int> G[maxn];

// 并查集的初始化
void init() {
    for(int i = 1; i <= n; i++) {
        W[i] = w[i];
        B[i] = b[i];
        p[i] = i;
    }
}

// 并查集的查找
int find(int x) {
    return x == p[x] ? x : p[x] = find(p[x]);
}

// 并查集的合并
void Union(int x, int y) {
    x = find(x);
    y = find(y);
    if(x == y) {
        return;
    }
    // 数据的合并
    W[x] += W[y];
    B[x] += B[y];
    //  点编号的合并
    p[y] = x;
}

int main() {
//  freopen("data.txt", "r", stdin);
    scanf("%d%d%d", &n, &m, &k);
    for(int i = 1; i <= n; i++) {
        scanf("%d", &w[i]);
    }
    for(int i = 1; i <= n; i++) {
        scanf("%d", &b[i]);
    }
    // 处理出“联合实体”
    init();
    while(m--) {
        scanf("%d%d", &u, &v);
        Union(u, v);
    }
    for(int i = 1; i <= n; i++) {
        f = find(i);
        G[f].push_back(i);
    }
    // 将“联合实体”放入集合中
    cnt = n;
    for(int i = 1; i <= n; i++) {
        f = find(i);
        if(vis[f] == false) {
            G[f].push_back(++cnt);
            w[cnt] = W[f];
            b[cnt] = B[f];
            vis[f] = true;
        }
    }
    // 分组揹包(滚动数组实现)
    memset(d1, 0, sizeof(d1));
    for(int i = 1; i <= n; i++) {
        memset(d2, 0, sizeof(d2));
        for(int u : G[i]) {
            for(int j = k; j >= w[u]; j--) {
                d2[j] = max(d2[j], d1[j-w[u]] + b[u]);
            }
        }
        for(int j = 0; j <= k; j++) {
            d1[j] = max(d1[j], d2[j]);
        }
    }
    // 在DP表中寻找答案
    for(int j = 1; j <= k; j++) {
        ans = max(ans, d1[j]);
    }
    printf("%d\n", ans);
    return 0;
}

(其它题目略)

发布了70 篇原创文章 · 获赞 30 · 访问量 7万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章