Loading

:D 获取中...

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 @ 2023-11-16 16:24  liuzimingc  阅读(20)  评论(3编辑  收藏  举报