20250729 杂题
GDCPC 2024 图
首先n-1让然想到树,如果能弄出来k个生成树,每个生成树uv都有路径就好了。
每次加入边的时候二分前缀第一个(u,v)不连通的生成树加入即可,统计答案每棵树搜一遍就行。
AGC016E Poor Turkeys
考虑x和y如果必须吃一个肯定不行。
如果x和i,y和i必须吃一个也不行
x-i i-j j-k k-y 显然也不行。
也就是说两个集合有交就不行。
枚举每一对 𝑖, 𝑗,检查是否满足 𝑆𝑖 ∩ 𝑆𝑗 = ∅。总时间复杂度 𝑂(𝑛𝑚 + 𝑛3)。
我感觉这个很自然,分讨可以看课件:
PA 2024 Desant 3
暴力是一个指数级的,直接枚举初始状态。
因为模数是2
(其实这是一个套路)
考虑搜索,虽然是指数级的,但是我们发现x 的后继有两种可能,而且递归计算这两种后继的贡献的奇偶性一定相等(我们并不关心它们具体贡献了什么,而只关心它们的奇偶性到底相等不相等),那么我们就可以直接忽略掉这两个后继,不计算它们。
就比如x和y都是问号,其实后继状态一模一样(如果x和y不同,从问号看),所以他们对答案没有影响,可以不搜。只需要考虑都为0或者都为1的即可,对于有一个不确定的,如果问号在 y 上且 ax=0 就忽略掉,否则 ax=1。此时考虑 ay 的取值,要么为 0 要么为 1。如果是 0 那么就交换,否则不交换。看起来产生了两个新状态,但其实是一个。我们的后继状态一定 ax′=ay,ay′=1,于是只产生了唯一的后继状态,也就是 x 位置是问号且 ay=1。问号在 x 上是对称的,也只有一个后继状态。
至于这样做,显然有一个漏洞是最后仍然会留问号,其实不然。
最后的问号你可以直接贪心。
不要显示传递,会被卡常,拷贝开销极大。
#include <bits/stdc++.h>
using namespace std;
#define int long long
int n, m;
vector<int> l, r;
int ans[42];
void dfs(int u, vector<int> &now) {
if (u == m) {
int cnt = 0;
for (int i = 1; i <= n; i++) cnt += (now[i] == 1);
for (int i = 1; i <= n; i++) {
if (now[i] == 0) continue;
int v = 0;
for (int j = i; j <= n; j++) {
if (now[j] == 0) break;
if ((v += (now[j] == 1)) == cnt)
ans[j - i + 1] ^= 1;
}
}
return;
}
int x = l[u], y = r[u];
if (now[x] == -1 && now[y] == -1) {
// 尝试 a[x] = a[y] = 0
now[x] = now[y] = 0;
dfs(u + 1, now);
now[x] = now[y] = -1; // 回溯
// 尝试 a[x] = a[y] = 1
now[x] = now[y] = 1;
dfs(u + 1, now);
now[x] = now[y] = -1; // 回溯
} else if (now[x] == -1 || now[y] == -1) {
if (now[x] == 0 || now[y] == 1) {
dfs(u + 1, now);
} else if (now[x] == 1) {
int old_x = now[x], old_y = now[y];
now[x] = -1; now[y] = 1;
dfs(u + 1, now);
now[x] = old_x; now[y] = old_y;
} else if (now[y] == 0) {
int old_x = now[x], old_y = now[y];
now[x] = 0; now[y] = -1;
dfs(u + 1, now);
now[x] = old_x; now[y] = old_y;
}
} else {
if (now[x] == 1 && now[y] == 0) {
swap(now[x], now[y]);
dfs(u + 1, now);
swap(now[x], now[y]); // 回溯
} else {
dfs(u + 1, now);
}
}
}
int32_t main() {
ios::sync_with_stdio(0);
cin >> n >> m;
l.resize(m);
r.resize(m);
for (int i = 0; i < m; i++) {
cin >> l[i] >> r[i];
}
vector<int> init(n + 1, -1);
dfs(0, init);
for (int i = 1; i <= n; i++) {
cout << ans[i] << ' ';
}
cout << '\n';
return 0;
}
所以复杂度就从2n变成了2(n/2)。
京都观光
结论,使用单调栈维护所有横着和竖着斜率增加的点,然后从(0,0),每次贪心选斜率小的,一定是最优的。
感性理解:这样的话,就避免了“反悔”,也就是说为了当前的小代价选择了后面的大代价(即为斜率先减小后增加),我们要杜绝这样的选择,不会后悔,这样复杂度和正确性都能保证。
理性证明: