AGC009 题解
A - Multiple Array
考虑记录 \(\{a_i\}\) 的变化量 \(\{c_i\}\),易得 \(\{c_i\}\) 递减,所以我们从右往左扫,贪心的去求满足条件的最小 \(\{a_i\}\),正确性显然。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline ll Read() {
int sig = 1; ll num = 0; char c = getchar();
while(!isdigit(c)) { if(c == '-') sig = -1; c = getchar(); }
while(isdigit(c)) num = (num << 3) + (num << 1) + (c ^ 48), c = getchar();
return num * sig;
}
void Write(ll x) {
if(x < 0) putchar('-'), x = -x;
if(x > 9) Write(x / 10);
putchar((x % 10) ^ 48);
}
const int N = 100005;
int n;
ll a[N], b[N];
int main() {
int i; ll c = 0; n = Read();
for(i = 1; i <= n; i++) a[i] = Read(), b[i] = Read();
for(i = n; i; i--) c = (a[i] + c + b[i] - 1) / b[i] * b[i] - a[i];
Write(c);
}
B - Tournament
对于一个描述比赛局面的二叉树,所求即为最小的最大深度,可以将 \(x\) 获胜的过程描述为一条链,那么 \(y\) 败给 \(x\),就相当于将 \(y\) 的这条链加一条边后接到了 \(x\) 的这条链的某个结点上,且限制每个结点只能至多接一条链。
那么我们设 \(f_x\) 表示最终状态中以 \(x\) 为根的子树的最大深度,设有 \(t\) 人败给了 \(x\),分别为 \(\{y_1, y_2, \dots, y_t\}\),则:
贪心的,我们肯定用更大的 \(p_i\) 去匹配更小的 \(f_{y_i}\),所以将 \(\{y_i\}\) 按 \(f_{y_i}\) 排序即可转移。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline ll Read() {
int sig = 1; ll num = 0; char c = getchar();
while(!isdigit(c)) { if(c == '-') sig = -1; c = getchar(); }
while(isdigit(c)) num = (num << 3) + (num << 1) + (c ^ 48), c = getchar();
return num * sig;
}
void Write(ll x) {
if(x < 0) putchar('-'), x = -x;
if(x > 9) Write(x / 10);
putchar((x % 10) ^ 48);
}
const int N = 100005;
int n, f[N];
vector<int> e[N];
void Dfs(int u) {
int i, siz = e[u].size(); vector<int> vec;
for(auto v : e[u]) Dfs(v), vec.emplace_back(f[v]);
sort(vec.begin(), vec.end());
for(i = 0; i < siz; i++) f[u] = max(f[u], vec[i] + siz - i);
}
int main() {
int i; n = Read();
for(i = 2; i <= n; i++) e[Read()].emplace_back(i);
Dfs(1), Write(f[1]);
}
C - Division into Two
我们先判掉一个情况:若任意三个数两两求差,全部小于 \(\min(A, B)\),那么一定不合法,转化一下即为将 \(S_i\) 排序后,\(\exists i \in [1, N - 2]\),\(S_{i + 2} - S_i < \min(A, B)\)。
考虑 dp,令 \(f_i\) 表示考虑前 \(i\) 个数,且 \(S_i \in X\) 的方案数,考虑 \(f_j\) 转移到 \(f_i\),\(S_i - S_j \ge A\),以及 \(\forall k \in (j + 1, i)\),\(S_k - S_{k - 1} \ge B\) 两个条件都是好判断的,发现判断的瓶颈在于判断不相邻的两个同属于 \(Y\) 的数(右边那个是 \(j + 1\))之差是否小于 \(B\)。
那么我们考虑若 \(A > B\) 则交换 \(A, B\),此时若不相邻的两个同属于 \(Y\) 的数之差小于 \(B\),必然无解,已经处理过了,所以可以不用管它,综上容易 \(O(N^2)\) 解决问题。
再考虑满足 \(S_i - S_j \ge A\) 的 \(j\) 是一个前缀,且具有单调性,可直接双指针;满足 \(\forall k \in (j + 1, i)\),\(S_k - S_{k - 1} \ge B\) 的 \(j\) 是一个后缀,那么我们遇到 \(S_{i - 1} - S_{i - 2} < B\) 的 \(i\),直接令 \(j\) 的下界为 \(i - 2\) 就可以了,用前缀和优化即可 \(O(N)\)。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline ll Read() {
int sig = 1; ll num = 0; char c = getchar();
while(!isdigit(c)) { if(c == '-') sig = -1; c = getchar(); }
while(isdigit(c)) num = (num << 3) + (num << 1) + (c ^ 48), c = getchar();
return num * sig;
}
void Write(ll x) {
if(x < 0) putchar('-'), x = -x;
if(x > 9) Write(x / 10);
putchar((x % 10) ^ 48);
}
const int N = 100005;
const ll Mod = 1e9 + 7, inf = 4e18;
int n;
ll x, y, a[N], f[N], g[N];
int main() {
int i, l, r; n = Read(), x = Read(), y = Read(), f[0] = g[0] = 1, a[++n] = inf;
if(x > y) swap(x, y);
for(i = 1; i < n; i++) { a[i] = Read(); if(i > 2 && a[i] - a[i - 2] < x) printf("0"), exit(0); }
for(i = 1, l = r = 0; i <= n; i++) {
while(r + 1 < i && a[i] - a[r + 1] >= y) r++;
if(i > 2 && a[i - 1] - a[i - 2] < x) l = i - 2;
f[i] = (r < l ? 0ll : (g[r] - (l ? g[l - 1] : 0ll) + Mod) % Mod), g[i] = (g[i - 1] + f[i]) % Mod;
}
Write(f[n]);
}
D - Uninity
连通块是不那么好弄的,所以我们考虑转化题意。我们在点分树的每个点上记录该子树的深度 \(dep_u\),可以发现:对于任意的 \(u \not = v\) 且 \(dep_u = dep_v\),一定存在一个 \(u, v\) 路径上的点 \(w\) 使得 \(dep_w > dep_u, dep_v\)(因为点分树上 \(u\) 的父亲 \(v\) 一定满足 \(dep_v > dep_u\))。
考虑原树上以 \(u\) 为根的子树,我们要找出这棵子树的答案 \(f_u\),我们有一个想法,就是对子树答案进行合并,每次我们去找到子树内所有点 \(v\),使得到子树根节点路径上的任意点 \(w\) 不会有 \(dep_w > f_u\),我们对子树 \(u\) 的所有儿子进行合并,然后再找到满足条件的最小 \(dep_u\),而 \(\max dep_u\) 即为答案,但这样乍一看并不是很对。
事实上,我们考虑贪心过程:找出子树 \(u\) 内所有满足不存在 \(u, v\) 路径上的点 \(w\) 使得 \(dep_w > dep_v\) 的点 \(v\),令 \(dep_v\) 构成的集合为 \(D(u)\),那么我们找出所有满足 \(\exists v, v' \in S_u\),\(d \in D(v), D(v')\) 的 \(d\),记为 \(T(u)\),显然 \(f_u > d\) 才满足要求,然后我们找出最小的 \(d' > d\),使得 \(d' \not \in T(u)\),且不存在 \(v \in S(u)\),使得 \(d' \in D(v)\),\(d'\) 即为 \(dep_u\) 的取值。
考虑一个不是最优方案的情况,并分析其对父亲 \(u\) 的影响。
-
我们有:\(f_u = \max_{v \in D(u)} v\)。首先 \(f_u \le \max_{v \in D(u)} v\) 是显然的,否则违反了 \(f_u\) 的定义,那么若 \(f_u < \max_{v \in D(u)} v\),则存在两个点 \(v, v'\),使得 \(v, v'\) 的路径上存在 \(w\) 使得 \(dep_w > dep_v, dep_{v'}\),同样不符合 \(f_u\) 的定义。
-
那么我们只需分析 \(D(u)\),我们将 \(D(v)\) 的元素从大到小排序后,发现按照这样的字典序比较 \(D\),贪心得出的 \(D(u)\) 一定是合法的 \(D(u)\) 中最小的那个。
-
我们又发现,使一个 \(D(v)\) 字典序增大一定不优,因为令 \(D(v) < D(v)'\),考虑 \(v \to u\) 的贪心过程,假定其它的结点的 \(D\) 不变,发现也有 \(\bigcup_{v \in S(u)} D(v) \le \bigcup_{v \in S(u)} D(v)'\),令左边为 \(A\),右边为 \(B\),从大到小找到第一个数 \(p\) 使得 \(p \in B\),但 \(p \not \in A\)。我们发现:
- 若 \(\max_{v \in T(u)} \ge p\),由于集合中大于 \(p\) 的部分相同,则 \(D(u) = D(u)'\)。
- 若 \(\max_{v \in T(u)} = p - 1\),对于修改前的情况,有 \(a_u = p\),但对于修改后的情况则会是 \(a_u' > p\),则 \(D(u) < D(u)'\),不优。
- 若 \(\max_{v \in T(u)} < p - 1\),那么修改前 \(a_u \le p\),首先修改后 \(a_u > p\) 肯定不优,所以无论 \(p \not \in D(u)\),\(p \in D(u)'\)(\(a_u < p\)),还是 \(p \not \in D(u), D(u)'\)(\(a_u = p\),此时 \(D(u)\) 不存在小于 \(p\) 的数),都有 \(D(u) < D(u)'\)。
以此类推,我们发现最终根 \(root\) 上 \(D(root)' \ge D(root)\),则答案可能更劣。
-
而对于非最优情况,一定有 \(D(u)' > D(u)\),所以一定不优。
综上,直接贪是对的,太神奇了。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline ll Read() {
int sig = 1; ll num = 0; char c = getchar();
while(!isdigit(c)) { if(c == '-') sig = -1; c = getchar(); }
while(isdigit(c)) num = (num << 3) + (num << 1) + (c ^ 48), c = getchar();
return num * sig;
}
void Write(ll x) {
if(x < 0) putchar('-'), x = -x;
if(x > 9) Write(x / 10);
putchar((x % 10) ^ 48);
}
const int N = 100005;
int n, a[N], ans = 0;
vector<int> e[N];
void Dfs(int u, int fa) {
int t = 0, res, k;
for(auto v : e[u]) if(v != fa) Dfs(v, u), t |= a[u] & a[v], a[u] |= a[v];
k = t ? 32 - __builtin_clz(t) : 0, res = __builtin_ctz((a[u] | ((1 << k) - 1)) + 1), a[u] >>= res, a[u] <<= res, a[u] |= 1 << res, ans = max(ans, res);
}
int main() {
int i; n = Read();
for(i = 1; i < n; i++) { int u = Read(), v = Read(); e[u].emplace_back(v), e[v].emplace_back(u); }
Dfs(1, 0), Write(ans);
}
E - Eternal Average
考虑将操作刻画为树形结构,并将叶子结点标为 \(0/1\),非叶子节点标为操作后的平均数,发现从上到下第 \(i\) 层(从 \(0\) 开始标号)标为 \(1\) 的叶子结点会对答案产生 \(\frac{1}{K^i}\) 的贡献,且设所有叶子结点深度可重集合为 \(S\),则 \(\sum_{d \in S} \frac{1}{K^d} = 1\)。
我们考虑如何不记重,显然同一层的结点可以随意互换,那么我们可以将 \(K\) 个标记相等的叶子结点合成一个深度更小的叶子结点,这样会减少 \(K - 1\) 个叶子结点,所以最终每层同色叶子结点的个数小于 \(K\)。实际上,我们可以把上述操作刻画为 \(K\) 进制数进位的过程。
假设我们已经知道最深层数和标为 \(1\) 的叶子结点的个数和,然后我们补充标为 \(0\) 的叶子结点,贪心的,我们在除最后一层的每一层上补至 \(K - 1\) 个叶子结点,在最后一层上补至 \(K\) 个叶子结点,容易证明这是最优的。
于是我们使用背包就可以了。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
inline ll Read() {
int sig = 1; ll num = 0; char c = getchar();
while(!isdigit(c)) { if(c == '-') sig = -1; c = getchar(); }
while(isdigit(c)) num = (num << 3) + (num << 1) + (c ^ 48), c = getchar();
return num * sig;
}
void Write(ll x) {
if(x < 0) putchar('-'), x = -x;
if(x > 9) Write(x / 10);
putchar((x % 10) ^ 48);
}
const int N = 4005;
const ll Mod = 1e9 + 7;
int n, m, k;
ll f[N][N], ans;
int main() {
int i, j, p, tot; n = Read(), m = Read(), k = Read(), tot = (n + m - 1) / (k - 1), f[0][0] = 1;
for(i = 1; i <= tot; i++) {
for(p = 1; p < k; p++) for(j = p; j <= n; j++) f[i][j] = (f[i][j] + f[i - 1][j - p]) % Mod;
for(j = 0; j <= n; j++) { if(n % (k - 1) == j % (k - 1) && i * (k - 1) - j < m) ans = (ans + f[i][j]) % Mod; f[i][j] = (f[i - 1][j] + f[i][j]) % Mod; }
}
Write(ans);
}

浙公网安备 33010602011771号