A. Bear and Big Brother(Codeforces 791A)
思路
由于 220 和 320 都会超过 106 ,所以只要模拟 20 年两人的体重变化就可以了。当然也可以列方程解之,不过太麻烦。
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a, b, x, y;
int main() {
// freopen("Input3.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0);
cin >> a >> b;
x = 1;
y = 1;
for(int i = 1; i <= 20; i++) {
x *= 3;
y *= 2;
if(a * x > b * y) {
cout << i << endl;
break;
}
}
return 0;
}
B. Bear and Friendship Condition(Codeforces 791B)
思路
这种有“关系”的题目,用图来建模最合适不过。所以问题转化为每个连通分量的子图是否构成“完全图”,也就是两点之间是否都有边相连。完全图有一个性质:其点数
n
与边数
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5;
vector <int> G[maxn];
bool vis[maxn];
int u, v, n, m;
ll V, E;
void dfs(int u) {
vis[u] = true;
E += G[u].size();
V++;
for(int v : G[u]) {
if(vis[v] == true) {
continue;
}
dfs(v);
}
}
int main() {
// freopen("Input1.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> m;
while(m--) {
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
memset(vis, 0, sizeof(vis));
for(int i = 1; i <= n; i++) {
if(vis[i] == true) {
continue;
}
V = E = 0;
dfs(i);
if(V * (V - 1) != E) {
puts("NO");
return 0;
}
}
puts("YES");
return 0;
}
C. Bear and Different Names(Codeforces 791C)
思路
本题只需要构造一组解。搜索的话复杂度太高,可以先考虑贪心的思想。首先应当明确,输入中的一个字符串
YES
或
NO
描述的是一个窗口的情况,也就是窗口中的名字是否有重复。
例如
k=4
的某个窗口
[1,1,2,3,4]
是有重复的,
k=5
的某个窗口
[3,4,1,5,2]
是没有重复的。注意,这里名字的描述方式是数字,答案要求输出真实人名,我们只需要将数字映射到人名上就行了。
如果窗口的描述是
YES
的话那么为了简化问题,构造一个升序序列就行了,例如
[1,2,3,4]
,如果窗口的描述是
NO
的话那么为了简化问题,让窗口首尾的元素相同其它元素保持升序就行了,例如
[1,2,3,1]
。为什么要这么做呢?原因是要为以下的贪心算法做铺垫,下面提出贪心算法:
(注意,下面的构造方法以第一组样例为例)
- 先构造前 k−1 个元素。将其构造成升序即可。那么我们构造出序列 [1,2] 。
- 然后构造第
k
个元素。检查第
1 个字符串,如果其为 NO 则在序列末尾添加与其窗口起始元素相同的元素,如果其为 YES ,那么添加新的元素即可。在第一组样例中此时的序列变为 [1,2,1] 。 - 重复第
2
个步骤,即不断往序列末尾添加新元素。序列依次被构造成 ->
1,[2,1,2] -> 1,2,[1,2,3] -> 1,2,1,[2,3,4] -> 1,2,1,2,[3,4,5] -> 1,2,1,2,3,[4,5,4] - 最后序列被构造出来了,第一组样例构造出的序列是 1,2,1,2,3,4,5,4 ,将数字映射到姓名即可。比如可以将数字看成 26 进制数然后用英文字幕 A,B,C …表示 1,2,3 …(如下)。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn = 100;
string s[maxn];
int name[maxn];
int n, k, cnt;
int main() {
// freopen("Input1.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> k;
for(int i = k; i <= n; i++) {
cin >> s[i];
}
for(int i = 1; i <= k - 1; i++) {
name[i] = ++cnt;
}
for(int i = k; i <= n; i++) {
if(s[i] == "YES") {
name[i] = ++cnt;
}
else {
name[i] = name[i - k + 1];
}
}
for(int i = 1; i <= n; i++) {
cout << (char)(name[i] % 10 + 'A');
cout << (char)(name[i] / 10 + 'a') << ' ';
}
return 0;
}
D. Bear and Tree Jumps(Codeforces 791D)
思路
先将问题简化为 k=1 的情况。定义 dis(u,v) 为 u,v 两点之间的距离(假设任意树边的长度为 1 )。那么答案就是
令
w[u]
为以
u
为根的子树中含有的节点数。那么对于每条树边
res(1)=∑(w[u]×(n−w[u])) (for all u in the tree)
接着要将问题推广到 k=2,3,4,5 的情况。可不可以直接算 ⌊res(1)k⌋ 呢?如果这么算的话会发现答案比真正的答案变小了。原因是在按照最多跳k步的规则计算答案时,算的是
ans(k)=∑⌈dis(u,v)k⌉(for all u,v that u < v)
也就是说
ans(k)=⌊res(1)k⌋+δd=res(1)+Δdk
如果能求出 Δd 的话就能求出结果。
那么我们需要对每条路径算模
k
的余数。当然还是不能一条一条路径算,还是得用贡献度的方法算。设以
Δd=∑del(subtree(u)) (for all u in the tree)
代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 2e5 + 5, maxk = 10;
vector <int> G[maxn];
int n, k, u, v;
ll ans, w[maxn], cnt[maxn][maxk];
int sub(int a, int b) {
return ((a - b) % k + k) % k;
}
// 边搜索边统计需要的数据
void dfs(int u, int p, int d) {
w[u] = cnt[u][d % k] = 1;
for(int v : G[u]) {
if(v == p) {
continue;
}
dfs(v, u, d + 1);
for(int i = 0; i < k; i++) {
for(int j = 0; j < k; j++) {
// r为余数,k为还要多少能模k余0
int r = sub(i + j, 2 * d);
int need = sub(k, r);
// 计算贡献度
ans += need * cnt[u][i] * cnt[v][j];
}
}
// 从子树总累加cnt[u][i]
for(int i = 0; i < k; i++) {
cnt[u][i] += cnt[v][i];
}
w[u] += w[v];
}
// 计算贡献度
ans += w[u] * (n - w[u]);
}
int main() {
// freopen("Input2.txt", "r", stdin);
ios::sync_with_stdio(false);
cin.tie(0);
cin >> n >> k;
for(int i = 1; i <= n - 1; i++) {
cin >> u >> v;
G[u].push_back(v);
G[v].push_back(u);
}
dfs(1, -1, 0);
cout << ans / k << endl;
return 0;
}
(其它题目略)