Solution Of LDOI
T1
Author: Drifty.
Solution
我们注意到对于任意长度为
证明显然。
因此我们对于每一次
AC-Code
以下是 Drifty 给出的实现。
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 3e5 + 7;
int a[N], n, q, s[N];
int main() {
ios::sync_with_stdio(false);
cin.tie(NULL), cout.tie(NULL);
cin >> n >> q;
for (int i=1; i<=n; i++)
cin >> a[i], s[i] = s[i - 1] + (a[i] < a[i - 1]);
for (int x, y; q--; ) {
cin >> x >> y;
if (x > y) swap(x, y);
cout << s[y] - s[x] << '\n';
}
return 0;
}
T2
Author: Drifty.
Preface
啊?
Solution
对于
为什么?
第一条是为了保证顺序(即
第二条则是真正的关键。由于
如何实现?
我们可以开设两个桶
这样,从
时间复杂度
AC-Code
以下是 Pentatonic_Vi0lin 给出的实现。
#include <bits/stdc++.h>
using namespace std;
#define int long long
inline int read() {
int x=0, f=1; char c = getchar();
while (c<'0' || c>'9') {if (c == '-') f = -1; c = getchar();}
while (c>='0' && c<='9') x = (x << 3) + (x << 1) + (c ^ 48), c=getchar();
return x * f;
}
constexpr int N = 3e6 + 5, add = 1, del = -1;
long long a[N], b[305], c[305], n, ans, Q, x, y;
inline void change(long long num, int op) {
int k = 1;
for (; num; k ++, num >>= 1)
if (!(num & 1)) b[k] += op;
c[k-1] += op;
}
signed main() {
int n = read(), Q = read();
for (int i=1; i<=n; i++)
a[i] = read(), change(a[i], add);
while (Q--) {
x = read(), y = read();
change(a[x], del), change(y, add);
a[x] = y, ans = 0;
for(int i=1; i<=32; i++) ans += b[i] * c[i];
printf("%lld\n", ans);
}
return 0;
}
T3
Author: c_y_y.
Solution
称一棵树中的子图为牵制地点,当且仅当 hgcnxn 和 Drifty只在该子图中走动,且 hgcnxn 无法在有限步抓到 Drifty。
事实上,Drifty 想不要被 hgcnxn 抓住,必须要找到合适的牵制地点。
故第一步,我们需要找到牵制地点的共同特点。
以下称“节点”为房间。
推论 1:一条链不存在牵制地点。
形式化的,设
V 为一个树的点集,则对于\forall i \in V ,均有节点i 的度数\le 2 ,则该图不存在牵制地点。
事实上,hgcnxn 只需往 Drifty 所在的地方走就可以了。由于没有岔路,Drifty 必败。
由推论 1 可以得出,一棵树中存在牵制地点的必要条件是,该图中存在度数
考虑到这种点是特殊点,故定义一棵树中度数
以下表示的“图”默认为一棵树的子图。
推论 2:设图中一个小根的度数为
n 。若以其为该图的根,
n 个儿子中有\ge n-2 个为叶子节点,则该小根及其儿子构成的子图不是牵制地点。
先证明
在某一回合,不妨设 hgcnxn 站在小根,Drifty 在某个叶子节点上。
那么,在下一回合中,hgcnxn 出于最优计划,会走向一个未被搜查过的节点,如果该节点里面有 Drifry ,游戏就结束了。否则对于 Drifty 来说:
- 走到小根。此时轮到 hgcnxn,他立刻回到小根,游戏结束。
- 原地静止,hgcnxn 会依个搜查每一个叶子节点。总会有一次找到 Drifty,游戏结束。
故 Drifty 在这种情况下必败。
称与小根距离为
进一步,若把这些危险节点忽视后,至多只有
总结推论 1 和推论 2,一棵树的子图牵制地点的必要条件是:
- 该子图拥有至少一个小根。
- 该子图的某一个小根中,至少有三个儿子不是叶子节点。
根据上面的结论,得出最小单位的牵制地点如下(其中橙色为小根)。 接下来证明该图是牵制地点即可。
由推论 2,不妨设 Drifty 在黄色点,hgcnxn 在小根。由于 Drifty 知道全部的抓捕计划,故他会待在 hgcnxn 下一回合不会抓捕的黄色点。
出于最优的抓捕计划,hgcnxn 必须要走到黄色地点(否则 Drifty 就可以一直待在黄色点)。
又由于 Drifty 知道全部的抓捕计划,故他知道 hgcnxn 在哪一回合走到黄色点。
不妨那一回合下,hgcnxn 的抓捕方案为
那么,Drifty 就有方案
此操作相当于每次 Drifty 都会躲在 hgcnxn 这次和下次均不会到达的黄点。 证毕。
证明了最小单位的牵制地点,难道就完了吗?不!
Drifty 必须比 hgcnxn 更先到小根,且 Drifty 到小根时,hgcnxn 与他距离至少为
代码还是比较容易实现的,每组数据先进行 dfs 操作,得出两人到每个点的距离。一棵树中可能存在多个牵制地点,对于这些点,我们可以用
AC Code
以下是 Drifty 给出的实现。
#include <bits/stdc++.h>
using namespace std;
constexpr int N = 1e5 + 7;
constexpr int hgcnxn = 1, Drifty = 0;
vector <int> g[N];
bitset <N> tag;
int d[2][N];
auto clear_g = [](size_t n) {
for (int i=0; i<=n; i++) g[i].clear();
tag = 0;
memset (d, 0, sizeof(d));
};
auto get_struct = [](int n) {
for (int i=1; i<=n; i++) {
int siz = g[i].size();
if (siz >= 3) {
int tot = 0;
for (int v : g[i])
if (g[v].size() > 1) tot ++;
if (tot >= 3) tag[i] = 1;
}
}
};
void dfs(int u, int fa, int s, const int k) {
d[k][u] = s;
for (auto v : g[u]) {
if (v == fa) continue;
dfs (v, u, s + 1, k);
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(NULL), cout.tie(NULL);
int T; cin >> T;
for (int n, p, q; T--; ) {
cin >> n >> p >> q;
clear_g (n);
for (int i=1, u, v; i<n; i++)
cin >> u >> v, g[u].push_back(v), g[v].push_back(u);
get_struct (n);
dfs (q, 0, 0, Drifty), dfs (p, 0, 0, hgcnxn);
if (d[Drifty][p] < 2) {cout << "hgcnxn\n"; continue;}
bool f = false;
for (int i=1; i<=n; i++)
if (tag[i]) {
int ans = INT_MAX;
for (int v : g[i]) ans = min(ans, d[hgcnxn][v]);
if (ans > d[Drifty][i]) {f=true; break;}
}
if (f) cout << "Drifty\n";
else cout << "hgcnxn\n";
}
return 0;
}
T4
Author: Pentatonic_Vi0lin.
Solution
首先,很容易观察到,对于一个点执行
其次,一个点受到的影响,仅来自其子树的操作二的个数的奇偶性,与这个点本身的操作。同时对自身子树有影响的操作一是翻转整颗子树,说明这棵子树本身必须全黑或全白,因而存在局部最优解。
从而,我们考虑树形 dp,从叶子节点向上转移状态,记录为
令
对叶子节点状态初始化:
f[u][color[u]][0] = 0;(不用操作)
f[u][!color[u]][0] = 1;(操作一)
f[u][!color[u]][1] = 1;(操作二)
f[u][color[u]][1] = 2;(操作一和二)
接下来考虑状态转移,由于一个点有多个子节点,我们要将这些点合并为一些可以代替f数组意义的量,同样有颜色和奇偶性两个标度,假定已经知道一些当前点子节点的子树全变成同一颜色,且用了某一奇偶性的操作的最小操作次数,设为
t[0][0] = min(g[0][0] + f[v][0][0], g[0][1] + f[v][0][1]);
t[0][1] = min(g[0][1] + f[v][0][0], g[0][0] + f[v][0][1]);
t[1][0] = min(g[1][0] + f[v][1][0], g[1][1] + f[v][1][1]);
t[1][1] = min(g[1][1] + f[v][1][0], g[1][0] + f[v][1][1]);
最后,由子节点的转移为:
f[u][color[u]] [0] = min(g[color[u]][0]//不操作, g[color[u]][1] + 1//只用操作二);
f[u][!color[u]][0] = min(g[color[u]][0] + 1//只用操作一, g[color[u]][1] + 2//用操作一、二);
f[u][!color[u]][1] = min(g[!color[u]][0] + 1//只用操作二, g[!color[u]][1]//不操作);
f[u][color[u]] [1] = min(g[!color[u]][0] + 2//用操作一、二, g[!color[u]][1] + 1//只用操作一);
通过递归实现上述过程即可。
AC Code
以下是 Pentatonic_Vi0lin 给出的实现。
#include <bits/stdc++.h>
using namespace std;
constexpr unsigned inf = INT_MAX >> 1;
constexpr unsigned SIZ = 2e5 + 7;
vector<int> son[SIZ];
bitset<SIZ> color;
int f[SIZ][2][2], n;
void dp_on_the_tree(int u, int fa) {
f[u][0][0] = f[u][0][1] = f[u][1][0] = f[u][1][1] = inf;
if (son[u][0] == fa && son[u].size() == 1) {
f[u][color[u]][0] = 0;
f[u][!color[u]][0] = 1;
f[u][!color[u]][1] = 1;
f[u][color[u]][1] = 2;
return ;
}
int g[2][2]= {{0, inf}, {0, inf}}, t[2][2] = {};
for (auto v : son[u]) {
if (v == fa) continue;
dp_on_the_tree(v, u);
t[0][0] = min(g[0][0] + f[v][0][0], g[0][1] + f[v][0][1]);
t[0][1] = min(g[0][1] + f[v][0][0], g[0][0] + f[v][0][1]);
t[1][0] = min(g[1][0] + f[v][1][0], g[1][1] + f[v][1][1]);
t[1][1] = min(g[1][1] + f[v][1][0], g[1][0] + f[v][1][1]);
memcpy(g, t, sizeof(t));
}
f[u][color[u]] [0] = min(g[color[u]][0], g[color[u]][1] + 1);
f[u][!color[u]][0] = min(g[color[u]][0] + 1, g[color[u]][1] + 2);
f[u][!color[u]][1] = min(g[!color[u]][0] + 1, g[!color[u]][1]);
f[u][color[u]] [1] = min(g[!color[u]][0] + 2, g[!color[u]][1] + 1);
}
int main() {
ios::sync_with_stdio(false);
cin.tie(NULL), cout.tie(NULL);
cin >> n;
for (int i=1, c; i<=n; i++) cin >> c, color[i] = c;
for (int i=1, x, y; i<n; i++)
cin >> x >> y, son[x].push_back(y), son[y].push_back(x);
dp_on_the_tree(1, 1);
cout << min({f[1][1][0], f[1][1][1], f[1][0][1] + 1, f[1][0][0] + 1}) << '\n';
return 0;
}