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
使得
代码
#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 的子串是否能别加入到题目要求我们求的后缀集合中去。显然要满足两种情况能够将这两个字符加入到后缀集合中:
s[i+2],s[i+3],s[i+4] 组成的长度为 3 的子串能加入到后缀集合中去。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+j−1] 加入到 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 了)。最后就可以根据 ii−i−j−jj 这条路径的长度更新最长路径了。代码
#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; }
(其它题目略)