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

题目链接


A. Pouring Rain(Codeforces 667A)

思路

首先把水位的上升和下降速度的单位统一,然后判断水位的上升和下降的速度哪个更大。最后就可以计算,输出了。

代码
#include <cstdio>
#include <cmath>
using namespace std;

const double eps = 1e-10, pi = 4 * atan(1.0);
double d, h, v, up, down;

int main() {
    scanf("%lf%lf%lf%lf", &d, &h, &v, &up);
    down = 4 * v / pi / d / d;
    if(up - down >= -eps) {
        puts("NO");
    }
    else {
        printf("YES\n%.5f\n", h / (down - up));
    }
    return 0;
}

B. Coat of Anticubism(Codeforces 667B)

思路

先拿最简单的凸多边形——三角形来思考。假如现在有两个线段 a,b ,加入一个最小的线段 c 使得 a,b,c 能够构成三角形的约束是 a,b 中更短的线段的长度加上 c 的长度要大于 a,b 中更长的线段的长度(相当于三角形任意两边之和大于第三边)。想清楚三角形的情况后再来思考一般的凸多边形的情况。假设我们有一个线段集合 s ,通过类比不难想出,加入一个最小的线段 c 使得 s+c 能够构成凸多边形的约束是 max(s)<smax(s)+c (假设 s 中最大的线段为 max(s) )。

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

int n, a, m;
long long sum;

int main() {
    scanf("%d", &n);
    sum = m = 0;
    for(int i = 0; i < n; i++) {
        scanf("%d", &a);
        sum += a;
        m = max(m, a);
    }
    printf("%I64d\n", 2 * m - sum + 1);
    return 0;
}

C.Reberland Linguistics(Codeforces 667C)

思路

本题是关于字符串后缀的题, KMP 算法和后缀数组看上去都不会有什么用处,反倒是从尾到头搜索貌似能够得到结果,只是复杂度太高了。在搜索能够有效解决问题而复杂度太高的情况下,记忆化搜索/动态规划可能会有效。于是假设我在对字符串做从后往前的扫描,现在扫描到了字符串的第i位s,那么怎么样能够利用之前做过的工作呢?先考虑 s[i] s[i+1] 组成的长度为 2 的子串是否能别加入到题目要求我们求的后缀集合中去。显然要满足两种情况能够将这两个字符加入到后缀集合中:

  1. s[i+2],s[i+3],s[i+4] 组成的长度为 3 的子串能加入到后缀集合中去。
  2. s[i+2],s[i+3] 组成的长度为 2 的子串能加入到后缀集合中去且 s[i......i+1]!=s[i+1......i+2] (题目的要求)。

    假设我们用 d[i][j] 表示以 i 为首的长度为j的子串是否在后缀集合中(用 0,1 表示),那么我们可以在从右到左的扫描中按照上面的方法计算 d[i][j] 的值。当 d[i][j] 1 的时候就把 s[i......i+j1] 加入到 set 中,以便以字典序的方式输出。

    代码
    #include <bits/stdc++.h>
    using namespace std;
    
    const int maxn = 5e4 + 10;
    int n, d[maxn][4];
    string s;
    set <string> :: iterator it;
    set <string> ss;
    
    int main() {
        cin >> s;
        n = s.size();
        d[n][2] = d[n][3] = 1;
        for(int i = n - 1; i >= 5; i--) {
            for(int j = 2; j <= 3; j++) {
                if(d[i][j] = d[i+j][j] && s.substr(i, j) != s.substr(i + j, j) || d[i+j][j^1]) {
                    ss.insert(s.substr(i, j));
                }
            }
        }
        cout << ss.size() << endl;
        for(it = ss.begin(); it != ss.end(); it++) {
            cout << *it << endl;
        }
        return 0;
    }

    D. World Tour(Codeforces 667D)

    思路

    因为时间给了 5 秒,所以枚举其中的两个点肯定没问题,但是再多枚举一个点都是不可能的了。那么我们就枚举两个点。枚举哪两个点呢?先考虑枚举起点和终点。不过即使枚举出起点和终点,我们也很难求出中间的点,因为变化实在太多(如果只走三个点的话这样枚举或许是可行的)。然后再考虑枚举中间两个点。假设枚举到的中间两个点为 i,j ,那么如果能求出到i点距离最远的点和j点能到达的最远的点(根据题意两点之间“距离”表示的是两点之间的最短路径长度),这样枚举就是可行的。我们可以通过 BFS (不需要用 Dijkstra SPFA ,因为每条边的长度都是相同的)预处理求出任意两点之间的最短路径长度。然后对于每个点得到两个序列 d1,d2 d1[i] 表示所有能够到达 i 的不是i的点按照到i的距离从大到小排序得到的序列, d2[i] 表示所有从 i 出发能够到达的不是i的点按照i到它的距离从大到小排序得到的序列。那么,为什么要求出这么个序列而不是直接求距离最大的点呢?因为题目要求的 4 个点不能重复。也就是说,如果我们求出到中间两点距离最大的两个点 s,t ,那么 s,t 可能会和 i,j 发生重复。因此我们应该得到 d1,d2 序列。对于我们枚举出的点 i,j ,再枚举 d1[i] d2[j] 中的点 ii jj 3 个。为什么只枚举 3 个点呢?因为 i,j 是两个点。只有枚举 3 个点才能保证至少有一个点不与 i,j 重复(其实我觉得为了防止 s,t 重复至少应该枚举 4 个点,但是 3 个点也 AC 了)。最后就可以根据 iiijjj 这条路径的长度更新最长路径了。

    代码

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef pair <int, int> p;
    const int maxn = 3010;
    bool vis[maxn];
    int mi, mj, pd, pv, nd, nv, res;
    int n, m, u, v, v1, v2, v3, v4, ans;
    int d[maxn][maxn];
    vector <int> G[maxn];
    vector <p> d1[maxn], d2[maxn];
    
    // 搜索并求最短路径长度
    void bfs(int s) {
        queue <p> q;
        q.push(p(0, s));
        // 初始化访问标记
        memset(vis, 0, sizeof(vis));
        vis[s] = true;
        while(!q.empty()) {
            p node = q.front();
            q.pop();
            int u = node.second;
            int w = node.first;
            d[s][u] = w;
            for(int i = 0; i < G[u].size(); i++) {
                int v = G[u][i];
                if(!vis[v]) {
                    q.push(p(w + 1, v));
                    // 不能取出结点的时再修改
                    vis[v] = true;
                }
            }
        }
    }
    
    int main() {
        scanf("%d%d", &n, &m);
        while(m--) {
            scanf("%d%d", &u, &v);
            G[u].push_back(v);
        }
        // 预处理出每个点到其它点的最短路d[i]
        for(int i = 1; i <= n; i++) {
            bfs(i);
        }
        // 预处理出d1和d2
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= n; j++) {
                if(d[j][i] > 0) {
                    d1[i].push_back(p(d[j][i], j));
                }
                if(d[i][j] > 0) {
                    d2[i].push_back(p(d[i][j], j));
                }
            }
            sort(d1[i].rbegin(), d1[i].rend());
            sort(d2[i].rbegin(), d2[i].rend());
        }
        // 枚举两个中间点
        for(int i = 1; i <= n; i++) {
            for(int j = 1; j <= n; j++) {
                if(i == j || !d[i][j]) {
                    continue;
                }
                mi = min(3, (int)d1[i].size());
                mj = min(3, (int)d2[j].size());
                // 枚举起点和终点
                for(int ii = 0; ii < mi; ii++) {
                    for(int jj = 0; jj < mj; jj++) {
                        p pre = d1[i][ii], nxt = d2[j][jj];
                        pd = pre.first, pv = pre.second;
                        nd = nxt.first, nv = nxt.second;
                        if(pv == j || nv == i || pv == nv) {
                            continue;
                        }
                        res = pd + d[i][j] + nd;
                        if(res <= ans) {
                            continue;
                        }
                        // 更新最优解
                        v1 = pv, v4 = nv;
                        v2 = i, v3 = j;
                        ans = res;
                    }
                }
            }
        }
        printf("%d %d %d %d\n", v1, v2, v3, v4);
        return 0;
    }

    (其它题目略)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值