Solution Set - CF787
Link。
Vive le R & M!
还被种草了 Hurt,真的颇有感触,但这是 Solution Set,就不写了。
还是写了点鲜花
Season 2 的结尾差点哭了。Rick 的科学终究不敌那份亲情,那份 Morty & Summer & Beth 的信任(是的 Jerry 你被 banned 了)。Rick 这么平静地 *Hurt* 了自己,一种极致的孤独?A. The Monster
exgcd,但是发现 \(1 \leq a, b, c, d \leq 100\) 直接暴力枚举即可。我认为这是 \(O(1)\) 的,但题解认为是 \(O(n)\),感觉不如原神。
B. Not Afraid
每一组里面只要有来自同一个宇宙的 Rick and Morty 就不可能都是坏的。等价于只要判断是否每一组都有 \(x\) 和 \(-x\) 同时存在即可。
C. Berzerk
难度骤升。显然是一个 bot 论。
这里不能写 dfs,因为 Loop 的存在会导致这个写法 \(O(n ^ 3)\),得换用 bfs 的有向图游戏弄 \(O(n ^ 2)\),然后就很 naive 了。只要有一个状态能到达对方的输状态就是赢状态,只能到达对方的赢状态就是输状态,否则不能确定,就是 Loop,因为这个情况只能到对方的赢和不确定,为了更优肯定选不确定。
我的写法还被卡空间(微笑),要写 short 才能过 /fn
namespace liuzimingc {
const int N = 7e3 + 5;
#define int short
#define endl '\n'
int n, out[N << 1], ans[N << 1];
set<int> s[2];
vector<int> e[N << 1];
queue<int> q;
bool vis[N << 1];
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n;
for (int i = 0; i <= 1; i++) {
int m, x;
cin >> m;
while (m--) {
cin >> x;
s[i].insert(x);
}
}
for (int i = 0; i < n; i++)
for (int j = 0; j <= 1; j++) {
for (const int &k : s[j]) {
int pos = (i + k) % n;
if (pos * 2 + !j == i * 2 + j) continue;
e[pos * 2 + !j].push_back(i * 2 + j);
out[i * 2 + j]++;
}
}
for (int i = 0; i <= 2 * n + 1; i++) ans[i] = 2;
ans[2] = ans[3] = 0;
q.push(2);
q.push(3);
vis[2] = vis[3] = true;
while (q.size()) {
int u = q.front(); q.pop();
for (const int &v : e[u]) {
if (ans[u] == 1) out[v]--;
if (!out[v] && !vis[v]) ans[v] = 0, vis[v] = true, q.push(v);
if (!ans[u] && !vis[v]) ans[v] = 1, vis[v] = true, q.push(v);
}
}
for (int i = 0; i <= 1; i++)
for (int j = 2; j <= n; j++) {
if (ans[(j % n) * 2 + i] == 1) cout << "Win";
else if (!ans[(j % n) * 2 + i]) cout << "Lose";
else cout << "Loop";
cout << " \n"[j == n];
}
return 0;
}
#undef int
} // namespace liuzimingc
D. Legacy
越做越感觉这是 edu。C 博弈论,D 线段树优化建图,E,见后文,根号分治。
而且可喜可贺的是我们是学过线段树优化建图的,但是那天我请假了。
把线段树的区间分别标号成点,然后这里有两种操作,一种是 \([l, r]\) 连向 \(v\),一种是 \(v\) 连向 \([l, r]\),要建两棵线段树分别处理。\([l, r]\) 连向 \(v\) 的线段树就是父节点连向子节点, \(v\) 连向 \([l, r]\) 就是子节点连向父节点。建完跑 Dijkstra 即可。线段树纯属工具树,就是整合了一下区间的连边。
namespace liuzimingc {
#define int long long
const int N = 1e5 + 5, INF = 1e18;
int n, Q, s, tot;
int dis[N << 3];
bool vis[N << 3]; // 2 棵线段树 * 4 倍空间
struct segment_tree {
int l, r, v;
} t[2][N << 2];
vector<pair<int, int>> e[N << 3];
priority_queue<pair<int, int>> q;
void build(int p, int l, int r, int id) {
t[id][p].l = l, t[id][p].r = r;
t[id][p].v = ++tot;
if (l == r) {
if (!id) e[t[id][p].v].push_back(make_pair(l, 0));
else e[l].push_back(make_pair(t[id][p].v, 0));
return;
}
int mid = l + r >> 1;
build(p << 1, l, mid, id);
build(p << 1 | 1, mid + 1, r, id);
if (!id) {
e[t[id][p].v].push_back(make_pair(t[id][p << 1].v, 0));
e[t[id][p].v].push_back(make_pair(t[id][p << 1 | 1].v, 0));
}
else {
e[t[id][p << 1].v].push_back(make_pair(t[id][p].v, 0));
e[t[id][p << 1 | 1].v].push_back(make_pair(t[id][p].v, 0));
}
}
void update(int p, int l, int r, int id, int u, int w) {
if (l <= t[id][p].l && t[id][p].r <= r) {
if (!id) e[u].push_back(make_pair(t[id][p].v, w));
else e[t[id][p].v].push_back(make_pair(u, w));
return;
}
int mid = t[id][p].l + t[id][p].r >> 1;
if (l <= mid) update(p << 1, l, r, id, u, w);
if (r > mid) update(p << 1 | 1, l, r, id, u, w);
}
void dijkstra(int s) {
for (int i = 1; i <= tot; i++) dis[i] = INF;
dis[s] = 0;
q.push(make_pair(0, s));
while (q.size()) {
int u = q.top().second; q.pop();
if (vis[u]) continue;
vis[u] = true;
for (const auto &i : e[u]) {
int v = i.first, w = i.second;
if (dis[u] + w < dis[v]) {
dis[v] = dis[u] + w;
q.push(make_pair(-dis[v], v));
}
}
}
}
int main() {
ios::sync_with_stdio(0);
cin.tie(nullptr);
cout.tie(nullptr);
cin >> n >> Q >> s;
tot = n;
build(1, 1, n, 0);
build(1, 1, n, 1);
while (Q--) {
int t;
cin >> t;
if (t == 1) {
int u, v, w;
cin >> u >> v >> w;
e[u].push_back(make_pair(v, w));
}
else {
int v, l, r, w;
cin >> v >> l >> r >> w;
update(1, l, r, t - 2, v, w);
}
}
dijkstra(s);
for (int i = 1; i <= n; i++)
cout << (dis[i] == INF ? -1 : dis[i]) << " \n"[i == n];
return 0;
}
#undef int
}
E. Till I Collapse
这道题是放在 vjudge 里的,标题是根号算法。但是这和根号算法有什么关系?
哦看标签可以知道是根号分治。
那么对于 \(k \leq \sqrt{n}\) 的情况直接暴力就是 \(O(n \sqrt{n})\) 的。
int calc(int i) {
int tot = 0, lst = 1, ans = 1;
for (int j = 1; j <= n; j++) {
if (!vis[a[j]]) tot++, vis[a[j]] = true;
if (tot > i) {
ans++;
tot = 1;
for (int k = lst; k <= j; k++) vis[a[k]] = false;
vis[a[j]] = true;
lst = j;
}
}
for (int k = lst; k <= n; k++) vis[a[k]] = false;
return ans;
} // 很丑的暴力
然后 \(k > \sqrt{n}\) 怎么做?答案相同的一段一定连续,\(k > \sqrt{n}\) 时最多也只有 \(\sqrt{n}\) 段,二分相同的区间即可。时间复杂度应该是 \(O(n \sqrt{n} \log n)\) 的?然后 @Lovely_Cat 好像写错了一点,不能直接对所有的 \(k\) 二分,这样的话会退化到 \(O(n ^ 2 \log n)\)。
更新了。
首先根据上文的描述,是可以二分的,但如果对所有的 \(k\) 都二分就会 T,又考虑到这道题又被放在整体二分的链接里了,我们可以使用一个类似于整体二分的玩意。
void solve(int l, int r, int ql, int qr) { // 计算区间 [ql, qr],答案的区间为 [l, r]
if (l > r || ql > qr) return;
int mid = ql + qr >> 1;
ans[mid] = l == r ? l : calc(mid);
solve(ans[mid], r, ql, mid - 1);
solve(l, ans[mid], mid + 1, qr);
}
Posted by liuzimingc