题解: 5/5 试图理解Div.1的D题, fail...算上这一题应该是: 5/6 # A. Bear and Big Brother 小熊, 大熊的初始体重分别为a, b. 每年小熊的体重变为原来的3倍, 每年大熊的体重变为原来的2倍, 问多少年后小熊的体重严格大于大熊. (1≤a≤b≤10)
模拟即可, 经过实验, 循环次数很少.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int main()
{
ll a, b;
cin >> a >> b;
ll n = 0;
while (a <= b) {
a *= 3;
b *= 2;
++n;
}
cout << n << endl;
return 0;
}
B. Bear and Friendship Condition
a是b的朋友当且仅当b是a的朋友. 每个人不能是自己的朋友. 给出n个人间m对朋友关系, 问是否满足: a是b的朋友, b是c的朋友 => a是c的朋友. 每对朋友关系在输入中不会出现两次. (3≤n≤150000, 0≤m≤min(150000, n(n-1)/2), 保证输入合法)
画图, 发现按照题面所描述的性质, 一个连通块变成了完全图. 证明: 设存在路径
#include <bits/stdc++.h>
#define Rep(i, a, b) for (int i = (a), i##_ = (b); i < i##_; ++i)
#define For(i, a, b) for (int i = (a), i##_ = (b); i <= i##_; ++i)
using namespace std;
const int N = 150000;
typedef long long ll;
vector<int> adj[N + 1];
bool vis[N + 1];
ll cnt, num;
void dfs(int u)
{
vis[u] = true;
cnt += adj[u].size();
++num;
Rep (i, 0, adj[u].size()) {
int v = adj[u][i];
if (!vis[v])
dfs(v);
}
}
int main()
{
int n, m;
cin >> n >> m;
Rep (i, 0, m) {
int a, b;
cin >> a >> b;
adj[a].push_back(b);
adj[b].push_back(a);
}
For (i, 1, n)
if (!vis[i]) {
num = cnt = 0;
dfs(i);
if (cnt != (ll)num*(num-1)) {
puts("NO");
return 0;
}
}
puts("YES");
return 0;
}
C. Bear and Different Names
下标从1开始. 一个长度为n的字符串序列s, 一个大于1的整数k. 已知s[i..i+k)内是否存在两个相等的字符串 (i=1,...,n-k+1) (记为y[i]), 构造一个满足题意的序列. 可以证明总是存在解. (2≤k≤n≤50) 方便起见等价改动了一下描述.
出题人良心啊......误导大众可以改成 "无解输出'IMPOSSIBLE' (不含引号)".
手动模拟, 发现可以从左往右依次构造. 以下用数代替字符串. 循环不变式: s[i, i+k-1)两两不等. 如果y[i]=true, 那么令s[i+k-1] = s[i], 否则, 令s[i+k-1] = s[i+k-2] + 1; 这样不但满足输入的限制, 还保证s[i+1, i+k)两两不等, 下一次循环开始的时候假设为真. 初始化, 令s[i]=i (i=1,...,k-1).
比赛的时候我的初始化稍微麻烦了些. 找第一个y[i]=false, 前面的都赋为1.
#include <bits/stdc++.h>
#define Rep(i, a, b) for (int i = (a), i##_ = (b); i < i##_; ++i)
#define pb push_back
using namespace std;
const int N = 50;
vector<string> name;
int id[N], cnt;
string s[N];
int main()
{
Rep (i, 0, 26) {
char t1[2] = {char('A' + i), 0}, t2[3] = {char('A' + i), 'x', 0};
name.pb(t1);
name.pb(t2);
}
int n, k;
cin >> n >> k;
Rep (i, 0, n-k+1)
cin >> s[i];
int fst = n-k+1;
Rep (i, 0, n-k+1) {
id[i] = cnt;
if (s[i] == "YES") {
fst = i;
++cnt;
Rep (j, 1, k)
id[fst + j] = cnt++;
break;
}
}
Rep (i, fst+1, n-k+1)
id[i+k-1] = s[i] == "YES" ? cnt++ : id[i];
Rep (i, 0, n)
cout << name[id[i]] << " ";
cout << endl;
return 0;
}
D. Bear and Tree Jumps
一棵无根树, 所有边权为1, 定义f(s,t)=ceil(s,t之间简单路径上边权之和/k), 求所有f(s,t) (s<t) 之和. (2≤n≤200000, 1≤k≤5)
做完前3题排名500+, 发现此时D题只有5个人过, E题没人过. 一方面, 说明这题有一定难度; 另一方面, 如果我把它切掉不就翻盘了吗? 然而1小时过去, 并没有搞出来.
比赛时yy的树上启发式合并是错的......虽然改做点分治就对了.
据说是树上DP, 事后yy了一个这样的做法: |
首先, 取上整运算是分段的. 有这样一条链: 0 1 ... x, 则 |
同样的关系可以推广到树上. 转有根树. 递推计算 |
考虑完子树, 再考虑外面的部分. 令 |
一直觉得树上DP子树外面的部分不好处理. 这个例子告诉我们可以利用减法. |
时间复杂度 |
Editorial给了一个时间复杂度
令k=1, 则本问题变成一个经典 (唉, 见识浅薄的人不知道啊) 问题: 求树上所有路径长度之和. 有一个很简单的做法: 考虑每一条边对答案的贡献. (比赛的时候想过这个问题, 但只想到DP, 还不知道子树外面怎么处理)
本题每条路径的长度要除以k, 再取上整. 也就是说, 要加上一个最小的非负数, 把长度凑成k的倍数, 再除以k. 不妨最后再做除法, 并把被除数拆成两部分: 原长之和, 按经典问题的做法来求; 用于凑倍数的非负数之和, DP.
设
代码中的变量可能和上面叙述中的不同. |
做法一
|
做法二
|
# E. Bear and Company 一个长度为n的字符串, 一次操作可以交换相邻两个字母. 问最少多少次操作后, 不存在子串"VK". (1≤n≤75) |
首先, 只存在三类字母: 'V', 'K', 其他 (记为'X'). |
不妨从左到右构造最终的字符串, 设使用前v个'V', 前k个'K', 前x个'X'构造前缀(v+k+x)的最小操作次数为dp[v][k][x][0/1] , 最后一维标记末尾是否为'V'. 如果是, 则不能添加'K'. 转移的代价=前面未使用的字母的数量. 记录pos[ch][i] 表示第i个ch在原串中的下标, 统计cnt[i][ch] 表示原串前i个字符中有多少个ch, 则可做到 |
我觉得解法中最奥妙重重的是不妨从左到右构造最终的字符串. 想不到这点没法入手......暂时不知道怎么证明这样不妨的合理性, 虽然直观上是正确的. |
|
收获: 想问题可以先从退化/特殊情形入手.