刷CF #2000

CF242E XOR on Segment

题目描述

你得到了一个序列 \(a\),其中包含 \(n\) 个元素 \(a_1,a_2,\cdots,a_n\)
你需要执行 \(m\) 个操作,分为两种:

  • 1 l r:求 \(a_l\sim a_r\) 的和。
  • 2 l r x:将 \(a_l\sim a_r\) 异或上 \(x\)

题解

区间异或不可以直接累加,所以无法直接维护。
但是我们假设数字仅有 \(0\)\(1\),那和 \(0\) 异或,\(1\) 的个数不变;和 \(1\) 异或相当于将区间取反。所以考虑二进制拆位,维护区间内二进制第 \(i\) 位 1 的个数即可。时间复杂度 \(\mathcal{O}(n \log n \log V)\)

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 1e5 + 5;
struct node{
	int sum[22], tg[22];
}Tree[M << 2];
int a[M];
void pushup(int p) {
	for(int i = 0; i <= 20; i++) Tree[p].sum[i] = Tree[p << 1].sum[i] + Tree[p << 1 | 1].sum[i];
}
void pushdown(int p, int l, int r){
	int mid = (l + r) >> 1;
	for(int i = 0; i <= 20; i++){
		if(Tree[p].tg[i]){
			Tree[p << 1].sum[i] = (mid - l + 1) - Tree[p << 1].sum[i];
			Tree[p << 1].tg[i] ^= 1;
			Tree[p << 1 | 1].sum[i] = (r - mid) - Tree[p << 1 | 1].sum[i];
			Tree[p << 1 | 1].tg[i] ^= 1;
			Tree[p].tg[i] = 0;
		}
	}
}
void build(int p, int l, int r){
	if(l == r) {
		for(int i = 0; i <= 20; i++) Tree[p].sum[i] = (a[l] >> i) & 1;
		return;
	}
	int mid = (l + r) >> 1;
	build(p << 1, l, mid), build(p << 1 | 1, mid + 1, r);
	pushup(p);
}
int qry(int p, int l, int r, int ql, int qr){
	int res = 0;
	if(ql <= l && r <= qr) {
		for(int i = 0; i <= 20; i++){
			res += (1 << i) * Tree[p].sum[i];
		}
		return res;
	}
	int mid = (l + r) >> 1;
	pushdown(p, l, r);
	if(ql <= mid) res += qry(p << 1, l, mid, ql, qr);
	if(qr > mid) res += qry(p << 1 | 1, mid + 1, r, ql, qr);
	return res;
}
void mdf(int p, int l, int r, int ql, int qr, int x){
	if(ql <= l && r <= qr){
		for(int i = 0; i <= 20; i++){
			if((x >> i) & 1){
				Tree[p].sum[i] = (r - l + 1) - Tree[p].sum[i];
				Tree[p].tg[i] ^= 1;
			}
		}
		return;
	}
	int mid = (l + r) >> 1;
	pushdown(p, l, r);
	if(ql <= mid) mdf(p << 1, l, mid, ql, qr, x);
	if(qr > mid) mdf(p << 1 | 1, mid + 1, r, ql, qr, x);
	pushup(p);
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int n, m;
	cin >> n;
	for(int i = 1; i <= n; i++) cin >> a[i];
	build(1, 1, n);
	cin >> m;
	while(m--){
		int op, l, r, x;
		cin >> op >> l >> r;
		if(op == 1) cout << qry(1, 1, n, l, r) << '\n';
		else {
			cin >> x;
			mdf(1, 1, n, l, r, x);
		}
	}
	return 0;
}

CF461B Appleman and Tree

题目描述

给你一棵有 \(n\) 个节点的树,下标从 \(0\) 开始。

\(i\) 个节点可以为白色或黑色。

现在你可以从中删去若干条边,使得剩下的每个部分恰有一个黑色节点。

问有多少种符合条件的删边方法,答案对 \(10^9+7\) 取模。

题解

注意到删除若干条边相当于在树上找连通块。

在以 \(u\) 为根的子树中,删边后 \(u\) 所在的那个连通块有两种可能:

  • 还没有黑点,此时需要从祖先那里获得黑点。
  • 已经恰有一个黑点,可以直接封闭成合法块,也可以选择继续向上合并,前提是父节点那边也没黑点。

受此启发,我们设 \(dp_{u, 0/1}\) 表示 \(u\) 为根的子树是否已经有黑点的方案数量。初始每个节点 \(dp_{u, col_u} = 1, dp_{u, col_u \oplus 1} = 0\)

然后我们考虑当我们处理完一个子节点 \(v\),要考虑边 \((u,v)\) 删还是不删,并把 \(v\) 的贡献合并进 \(u\)

对子节点 \(v\),它向上提供的状态有两类:

  • \(dp_{v, 0}\)\(v\) 所在块无黑点,必须保留边才能与 \(u\) 连通。

  • \(dp_{v, 1}\)\(v\) 所在块恰有一黑点,可以切断边独立成块,也可以保留边(但保留时要求 \(u\) 那边目前黑点数为 0)。

所以我们易得转移方程:

\[dp_{u,1} \leftarrow dp_{u, 1} \times (dp_{v, 0} + dp_{v, 1}) + dp_{u, 0} \]

\[dp_{u, 0} \leftarrow dp_{u, 0} \times (dp_{v, 0} + dp_{v, 1}) \]

做完了。

Code

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
const int M = 1e5 + 5;
vector<int> vec[M];
int col[M], dp[M][2];
void dfs(int u, int fa) {
	if (col[u] == 1) {
		dp[u][0] = 0;
		dp[u][1] = 1;
	} else {
		dp[u][0] = 1;
		dp[u][1] = 0;
	}
	for (int v : vec[u]) {
		if (v == fa) continue;
		dfs(v, u);
		dp[u][1] = (dp[u][1] * (dp[v][0] + dp[v][1]) + dp[u][0] * dp[v][1]) % mod;
		dp[u][0] = dp[u][0] * (dp[v][0] + dp[v][1]) % mod;
	}
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int n;
	cin >> n;
	for (int i = 2, x; i <= n; i++) {
		cin >> x;              
		vec[i].push_back(x + 1);
		vec[x + 1].push_back(i);
	}
	for (int i = 1; i <= n; i++) {
		cin >> col[i];
	}
	dfs(1, 0);
	cout << dp[1][1];
	return 0;
}

CF514C Watto and Mechanism

题目描述

Watto 是一家零件商店的老板,他最近接到一个订单,需要一种可以以特定方式处理字符串的机制。最初,该机制的存储器中存储有 \(n\) 个字符串,每个字符串仅由字母 'a'、'b'、'c' 组成。之后,该机制需要能够处理如下类型的查询:“给定字符串 \(s\),判断机制的存储器中是否存在某个字符串 \(t\),它与 \(s\) 长度相同,并且与 \(s\) 恰好只在一个位置上不同。”

Watto 已经组装好了这个机制,现在只需为其编写程序,并用包含 \(n\) 条初始字符串和 \(m\) 条查询的数据进行测试。他决定将这项任务交给你。

题解

正解是 trie,但谁说要正解?

我们直接对每个字符串 hash,丢进一个 set 里面,然后对于每次查询,暴力的把每个位置换剩余的字母计算哈希值,在 set 中查找是否出现过即可。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 350234136991;
const int M = 600005;
int pw[M];
int hsh(string s){
	int res = 0;
	for(int i = 0; i < s.size(); i++){
		res = (res * 4 % mod + s[i] - 'a') % mod;
	}
	return res;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int n, m;
	cin >> n >> m;
	pw[0] = 1;
	for(int i = 1; i < M; i++){
		pw[i] = (pw[i - 1] * 4) % mod;
	}
	set<int>s;
	for(int i = 1; i <= n; i++){
		string x;
		cin >> x;
		s.insert(hsh(x));
	}
	for(int i = 1; i <= m; i++){
		string t;
		cin >> t;
		int len = t.size();
		int h = hsh(t);
		bool ok = false;
		for(int j = 0; j < len; j++){
			int c = t[j] - 'a';
			for(int nw = 0; nw < 3; nw++){
				if(nw == c) continue;
				int hash = (h + (nw - c) * pw[len - 1 - j]) % mod;
				if(hash < 0) hash += mod;
				if(s.count(hash)){
					ok = true;
					break;
				}
			}
			if(ok) break;
		}
		if(ok) cout << "YES\n";
		else cout << "NO\n";
	}
	return 0;
}

CF681D Gifts by the List

题目描述

Sasha 生活在一个幸福美满的大家庭。在男子日这天,家族中所有男性成员都会聚在一起,用他们自己的传统庆祝。Sasha 的家族共有 \(n\) 名男性,我们可以用 \(1\)\(n\) 的整数对他们进行编号。

每个人至多有一位父亲,但可以有任意数量的儿子。

如果满足以下至少一个条件,则编号为 \(A\) 的男性被视为编号为 \(B\) 的男性的祖先:

  • \(A = B\)
  • 编号为 \(A\) 的男性是编号为 \(B\) 的男性的父亲;
  • 存在编号为 \(C\) 的男性,使得编号为 \(A\) 的男性是编号为 \(C\) 的祖先,且编号为 \(C\) 的男性是编号为 \(B\) 的父亲。

当然,如果编号为 \(A\) 的男性是编号为 \(B\) 的男性的祖先且 \(A \neq B\),那么编号为 \(B\) 的男性就不是编号为 \(A\) 的男性的祖先。

Sasha 家族的传统是在男子日互赠礼物。因为以普通的方式赠送礼物太过无聊,所以每年都会按以下流程赠送礼物:

  1. 准备一个候选人名单,名单中包含一部分(也可能是全部)\(n\) 名男性,并有一定的顺序。
  2. 每位男性都决定要赠送礼物。
  3. 选择赠送礼物的人时,编号为 \(A\) 的男性会遍历名单,选择列表中第一个是自己祖先的男性 \(B\),并将礼物送给他。注意,按照定义,一个人可能会把礼物送给自己。
  4. 如果某人没有在名单中找到自己的祖先,他会伤心地离开庆典,不向任何人送礼。

今年你打算帮助大家组织庆典,并已经询问每一位男性——他想要把礼物送给哪位祖先(只能从祖先中选择)。你能否制作一个候选人名单,使所有人的这一愿望都能按照上述方式实现?

题解

大概意思如下:

  1. 构造一个一个排列。对于每个人,他会按顺序扫描这个名单,找到第一个是他的祖先的人(包括自己),并把礼物送给那个人。
  2. 你已经知道每个人想给谁送礼。能否构造一个候选人名单,使得每个人找到的祖先就是他想送的?

首先注意到如果 \(a_i = i\),那么 \(i\) 必须出现在名单中,并且必须排在它所有祖先的前面。证明略。

我们设 \(a_i = i\) 的节点是好节点。对于一个节点 \(u\),考虑从根到 \(u\) 的路径,设这条路径上最深的好节点为 \(v\)

由于所有好节点都在名单中,并且每个好节点必须出现在它的所有祖先之前,因此好节点的排列顺序一定是按深度降序。

那么对任意节点 \(u\),他扫描名单时,遇到的第一个自己的祖先必定是路径上深度最大的好节点 \(v\)(因为深度大的好节点排在最前), 所以必须有 \(a_u = v\), 这是有解的必要条件。做完了。

Code

#include <bits/stdc++.h>
using namespace std;
const int N = 100005;
vector<int> vec[N];
int a[N], dep[N], deg[N];
bool ck[N];
void dfs(int u, int cur) {
	if (ck[u]) cur = u;
	if (a[u] != cur) {
		cout << "-1";
		exit(0);
	}
	for (int v : vec[u]) {
		dep[v] = dep[u] + 1;
		dfs(v, cur);
	}
}
int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		int u, v;
		cin >> u >> v;
		vec[u].push_back(v);
		++deg[v];
	}
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		ck[i] = (a[i] == i);
	}
	for (int i = 1; i <= n; i++) {
		if (deg[i] == 0) {
			dep[i] = 0;
			dfs(i, 0);
		}
	}
	vector<int> ans;
	for (int i = 1; i <= n; i++){
		if (ck[i]) ans.push_back(i);
	}
	sort(ans.begin(), ans.end(), [](int x, int y) { return dep[x] > dep[y]; });
	cout << ans.size() << '\n';
	for (int x : ans) cout << x << '\n';
	return 0;
} 

CF749D Leaving Auction

题目描述

今天有 \(n\) 个人参加拍卖。拍卖规则是经典的。总共进行了 \(n\) 次出价,注意出价的人不一定都不同,可能有的人一次都没有出价。

每一次出价由两个整数 \((a_{i}, b_{i})\) 表示,其中 \(a_{i}\) 表示第 \(i\) 次出价的人编号,\(b_{i}\) 表示这次出价的金额。所有出价按时间顺序给出,即对于所有 \(i < n\),都有 \(b_{i} < b_{i+1}\)。此外,每个参与者不会连续两次出价(即不会连续两次更新自己的出价),也就是对于所有 \(i < n\),都有 \(a_{i} \ne a_{i+1}\)

现在你对以下问题很感兴趣:如果某些人缺席,谁(以及他出的哪一次价)将赢得拍卖?认为如果某些人缺席,那么所有与他们相关的出价都被移除,没有任何新的出价被添加。

注意,如果在这种“想象删除”后,剩下的人有人连续两次或以上出价,那么只计算第一次出价,其余的被忽略。为了更好地理解,请参见样例。你有若干个类似的问题,请你计算每个问题的答案。

题解

删除缺席者后,有效序列的赢家仍然是剩下人中最后出价最高的那个人(设为 \(x\))。设 \(y\) 是剩下人中最后出价第二高的人。那么在所有出价中,金额大于 \(y\) 的最后出价的部分,只可能有 \(x\)

因为大于 \(y\) 的最后出价只有 \(x\) 的出价,按照规则连续出价只保留第一次,所以最终出价即为大于 \(y\) 出价中金额最小的那一次。

Code

#include<bits/stdc++.h>
using namespace std;
const int M = 2e5 + 5;
vector<int> vec[M];
int main() {
	ios::sync_with_stdio(0);
	int n;
	cin >> n;
	for (int i = 1, x, y; i <= n; i++) {
		cin >> x >> y;
		vec[x].push_back(y);
	}
	set<pair<int, int>>s;
	for (int i = 1; i <= n; i++) {
		if (!vec[i].empty()) {
			s.emplace(vec[i].back(), i);
		}
	}
	int T;
	cin >> T;
	while (T--) {
		int k;
		cin >> k;
		vector<int> t(k + 2);
		for (int i = 1; i <= k; i++) {
			cin >> t[i];
			if (vec[t[i]].size()) {
				s.erase(make_pair(vec[t[i]].back(), t[i]));
			}
		}
		if (s.empty()) cout << "0 0\n";
		else if (s.size() == 1) {
			int id = s.rbegin()->second;
			cout << id << ' ' << vec[id][0] << '\n';
		} else {
			auto it = s.rbegin();
			int x = it->second;
			++it;
			int y = it->second;
			int id = *upper_bound(vec[x].begin(), vec[x].end(), vec[y].back());
			cout << x << ' ' << id << '\n';
		}
		for (int v : t) {
			if (!vec[v].empty()) {
				s.emplace(vec[v].back(), v);
			}
		}
	}
	return 0;
}

CF207B3 Military Trainings

题目描述

\(n\) 个坦克,从 \(1\)\(n\) 编号,它们要进行消息传输。

每一次传输如下,列表中第一个坦克将信息传输到列表中的某个坦克。接收到该消息的坦克将其进一步发送到列表后的某个坦克。该过程将继续进行,直到最后一个坦克收到消息。可能不是列表中的所有坦克都会收到消息,但列表中的最后一个坦克必须收到消息。

当最后一个坦克收到消息时,它将挪到第一个位置,并发送一条消息。当信息到达最后一个坦克时,该坦克移动到列的开头,并将下一条信息发送到列表的末尾,依此类推。因此,当列中的坦克返回到其原始顺序时,练习就完成了。

在两个坦克之间传输信息需要一秒钟,然而,并非总是一个坦克可以将信息传输给另一个坦克。让我们考虑列中的两个坦克,使它们中的第一个是从开始计数的列中的第 \(i\) 个,第二个是列中的 \(j\) 个,并假设第二个坦克的编号为 \(x\)。然后,如果\(i<j\)\(i\ge j-a_{x}\) 则可以传输。

你会得到坦克的数量,以及所有坦克的信息接收半径。您必须帮助 Smart Beaver 并组织消息传输,使所有消息的总传输时间尽可能短。

题解

首先我们容易得到 dp 转移方程: \(dp_i = \min\limits_{j = \max(1, i - a_i)}^{i - 1}dp_j + 1\),直接暴力转移是 \(\mathcal{O}(n^3)\) 的。

然后我们发现,对于每个 \(x\),如果起点不能直接发送消息给它,从 \(y = x - a_x \sim x - 1\)\(y - a_y\) 最小的 \(y\) 接收消息肯定是最优的。我们可以用 ST 表预处理 + 查询,时间复杂度优化到 \(\mathcal{O}(n^2)\)

接着对于每个 \(x\)\(y\),暴力的方法是一个一个找。但是这样太慢了,可以通过倍增来优化,预处理出 \(x\)\(2^k\) 次方跳到的位置,时间复杂度优化到 \(\mathcal{O}(n \log n)\),可以通过。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 5e5 + 5;
int a[M], st[M][22], nxt[M][22], lg[M], n;
int query(int l, int r) {
	int k = lg[r - l + 1];
	int lf = st[l][k], rt = st[r - (1 << k) + 1][k];
	return a[lf] <= a[rt] ? lf : rt;
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i], a[i + n] = a[i];
	n <<= 1;
	for (int i = 1; i <= n; i++) {
		a[i] = max(i - a[i], 1ll);
		st[i][0] = i;
	}
	for (int i = 2; i <= n; i++) lg[i] = lg[i / 2] + 1;
	for (int j = 1; (1 << j) <= n; j++) {
		for (int i = 1; i + (1 << j) - 1 <= n; i++) {
			int lf = st[i][j - 1];
			int rt = st[i + (1 << (j - 1))][j - 1];
			st[i][j] = a[lf] <= a[rt] ? lf : rt;
		}
	}
	for (int i = 1; i <= n; i++) nxt[i][0] = query(a[i], i);
	for (int j = 1; j <= 20; j++)
		for (int i = 1; i <= n; i++)
			nxt[i][j] = nxt[nxt[i][j - 1]][j - 1];
	int ans = 0;
	for (int i = 1; i <= n / 2; i++) {
		int j = i + n / 2 - 1;
		if (a[j] <= i) {
			ans++;
			continue;
		}
		for (int k = 20; k >= 0; k--)
			if (a[nxt[j][k]] > i) {
				ans += (1ll << k);
				j = nxt[j][k];
			}
		ans += 2;
	}
	cout << ans;
	return 0;
}

CF2055D Scarecrow

题目描述

在一个数轴上,原点处有一只乌鸦。同时在这个数轴上还有 \(n\) 个稻草人,分别位于 \(a_1,a_2,\cdots,a_n\) 的位置。这些稻草人可以进行移动,每 \(1\) 秒可以向左或者向右一个单位长度。稻草人开始移动、静止不动或者改变移动方向的时间可以是任意时刻,可以不是整数

由于乌鸦很害怕稻草人,所以乌鸦想要距离它前边且离它最近的稻草人至少有 \(k\) 的长度。为了能够保证这 \(k\) 的长度,乌鸦会施展它的传送能力:

  • \(x\) 为乌鸦当前的位置,\(y\) 为最大的的稻草人位置且满足 \(y\le x\),如果 \(x-y<k\),说明乌鸦和稻草人离得太近,那么乌鸦就会瞬移到位置 \(y+k\)

乌鸦持续不断地进行传送,即它会在任意时刻检查它与它前边离它最近的稻草人的距离,若过近就会进行传送。

你的任务是用最少的时间让乌鸦移动到一个大于等于 \(l\) 的位置。你可以控制稻草人的移动以达到最优的时间。你需要输出 答案的两倍,可以证明这是一个整数。

题解

我们发现乌鸦要想移动,只有以下三种可能(图可能不好请轻喷喵):

第一种情况就是稻草人位置和乌鸦重合,乌鸦直接瞬移 \(k\) 个单位。

第二种情况就是乌鸦已经跑到最后一个稻草人之后了,那么最后一个稻草人每向前 \(1\) 个单位,乌鸦就往前移动 \(1\) 个单位。

第三种情况就是乌鸦在两个稻草人 \(i\)\(i + 1\) 之间,并且距离左边最近的稻草人 \(\ge k\),此时左边最近的稻草人就要往右走来让乌鸦瞬移到第 \(i+1\) 个稻草人右边,此时情况变为乌鸦在两个稻草人 \(i + 1\)\(i + 2\) 之间。

不难发现在这三种情况中乌鸦的移动只与离左边最近的稻草人有关,我们可以直接模拟这个过程。

具体的,我们维护三个变量 \(cur, jmp , t\) 分别表示乌鸦的位置,乌鸦瞬移的距离以及已流逝的时间。显然 \(t\) 的初始值是 \(a_1\)

接着我们依次处理后面的稻草人,第 \(i\) 个稻草人在当前时间能够到达最左侧的位置 \(lf = a_i - t\),然后每一次我们最优能够把乌鸦推到的位置记为 \(pos\),则 \(pos = \min(a_i + t, \max(cur + k, \frac{lf + cur + k}2))\),其中 \(a_i + t\) 是稻草人当前能到达的最右位置,乌鸦瞬移后的位置不能超过它; \(cur + k\) 是不消耗额外时间,直接利用前方稻草人能瞬移到的位置;\(\frac{lf + cur + k}2\)​ 是稻草人需要先向左移动“接头”,再向右拖拉乌鸦时最优会合的位置。

得到最优位置后,乌鸦就直接移动 \(d = pos - cur\) 的距离。然后超出 \(k\) 的部分 \(d - k\) 需要稻草人牵引 \(d - k\) 的距离,所以额外需要 \(d - k\) 的时间。

循环结束后,剩下的距离需要最后一个稻草人完成。它最多能让乌鸦再跳 \(\min(k,l - cur)\) 的距离,这部分需要计入跳跃的距离中。

最终答案就是 \((l - jmp + a_1) \times 2\)

Code

#include<bits/stdc++.h>
using namespace std;
const int M = 2e5 + 5;
double a[M];
void sol() {
	int n;
	double k, l;
	cin >> n >> k >> l;
	for (int i = 1; i <= n; i++) cin >> a[i];
	double tim = a[1], cur = 0, jmp = 0;
	for (int i = 2; i <= n; i++) {
		if (cur > l) break;
		double lf = a[i] - tim;
		double pos = min(l, min(a[i] + tim, max(cur + k, (lf + cur + k) / 2.0)));
		tim += max(0.0, pos - cur - k);
		jmp += min(k, pos - cur);
		cur = pos;
	}
	jmp += min(k, l - cur);
	cout << (int)round(2.0 * (l - jmp + a[1])) << '\n';
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int T;
	cin >> T;
	while (T--) sol();
	return 0;
}

CF1454E Number of Simple Paths

题目描述

给你一张 $ n $ 个节点 $ n $ 条边的无向连通简单图,请你计算出这张图中长度大于等于 $ 1 $ 的不同的简单路径的数量,保证图中没有自环和重边。其中,简单路径中的节点必须互不相同,一条路径的长度定义为它所包含的边的数量。

两条路径仅有方向不同时被认为是同一条,例如 $ 1 \rightarrow 2 $ 和 $ 2 \rightarrow 1 $。

题解

首先 $ n $ 个节点 $ n $ 条边让我们想到了基环树。我们先预处理出环的位置,然后考虑环上某点,假设这个点分树大小为 \(siz\)

  • 分树内两点构成的路径,显然情况数有 \(\frac{siz(siz - 1)}2\)
  • 分树内一点到分树外:点对有 \(\frac{siz(n - siz)}2\) 个,因为环上可以顺时针和逆时针所以要乘以 \(2\),所以情况数为 \(siz(n - siz)\) 种。

直接累加即可。

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 2e5 + 5;
vector<int>vec[M];
int cyc[M], dfn[M], from[M], siz[M], tim;
void dfs(int u){
	dfn[u] = ++tim;
	for(auto v : vec[u]){
		if(dfn[v] == 0) {
			from[v] = u;
			dfs(v);
		} else if(dfn[u] < dfn[v]){
			cyc[u] = 1;
			for(int nw = v; nw != u; nw = from[nw]) cyc[nw] = 1;
		}
	}
}
void dfs2(int u, int fa){
	siz[u] = 1;
	for(auto v : vec[u]){
		if(v == fa || cyc[v]) continue;
		dfs2(v, u);
		siz[u] += siz[v];
	}
}
void sol(){
	int n;
	cin >> n;
	for(int i = 1, u, v; i <= n; i++) {
		cin >> u >> v;
		vec[u].push_back(v);
		vec[v].push_back(u);
	}
	dfs(1);
	int ans = 0;
	for(int i = 1; i <= n; i++) {
		if(cyc[i]) {
			dfs2(i, 0);
			ans += siz[i] * (siz[i] - 1) / 2;
			ans += siz[i] * (n - siz[i]);
		}
	}
	cout << ans << '\n';
	for(int i = 1; i <= n; i++) {
		siz[i] = from[i] = cyc[i] = dfn[i] = 0;
		vec[i].clear();
	}
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int T;
	cin >> T;
	while(T--) sol();
	return 0;
}

CF1974G Money Buys Less Happiness Now

题目描述

作为一名物理学家,Charlie 喜欢以简单而精确的方式规划生活。

在接下来的 \(m\) 个月里,Charlie 从零开始,每个月努力工作赚取 \(x\) 英镑。在第 \(i\) 个月(\(1 \le i \le m\)),他有一次机会花费 \(c_i\) 英镑购买一个单位的幸福。你每个月最多只能买一个单位。

不允许借钱。在第 \(i\) 个月赚到的钱只能在之后的第 \(j\) 个月(\(j>i\))花掉。请你帮 Charlie 求出他最多能获得多少单位的幸福。

题解

反悔贪心。如果可以买就买,如果不能买就换之前最贵的一个月来换今天的一个月。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
void sol(){
	int m, x;
	cin >> m >> x;
	priority_queue<int>qu;
	int nw = 0, ans = 0;
	for(int i = 1, c; i <= m; i++) {
		cin >> c;
		if(nw >= c) {
			nw -= c, ans++, qu.push(c);
		} else if(!qu.empty()){
			if(qu.top() > c) {
				nw += qu.top() - c;
				qu.pop();
				qu.push(c);
			}
		}
		nw += x;
	}
	cout << ans << '\n';
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int T;
	cin >> T;
	while(T--) sol();
	return 0;
}

CF864E Fire

题目描述

某人的房子着火了,他想从大火中带走价值总和尽量多的物品,每次他只能带走一个,分别给出挽救某物品需要的时间 \(t\),该物品开始燃烧的时间 \(d\)(从 \(d\) 时间开始就不能再挽救该物品了),该物品的价值 \(p\)

题解

显然在不考虑 \(d\) 的情况下是一个朴素的 01 背包。

加上 \(d\) 也很简单,考虑转移的顺序,显然先选 \(d\) 较小的不会对 \(d\) 较大的造成影响。所以排序后 01 背包即可。

输出方案的话,用一个 vector 表示 \(f_j\) 所选的物品,每次从 \(f_{j - v_i}\) 转移过来时候就塞进去即可。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 2005;
struct node{
	int t, d, p, id;
}a[M];
int dp[M];
vector<int>vec[M];
bool cmp(node x, node y){
	if(x.d == y.d) return x.p > y.p;
	return x.d < y.d;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int n;
	cin >> n;
	for(int i = 1; i <= n; i++) {
		cin >> a[i].t >> a[i].d >> a[i].p;
		a[i].id = i;
	}
	sort(a + 1, a + 1 + n, cmp);
	for(int i = 1; i <= n; i++){
		for(int j = a[i].d - 1; j >= 0; j--){
			if(j >= a[i].t){
				if(dp[j] < dp[j - a[i].t] + a[i].p) {
					dp[j] = dp[j - a[i].t] + a[i].p;
					vec[j] = vec[j - a[i].t];
					vec[j].push_back(a[i].id);
				}
			}
		}
	}
	int mx = 0, pos = 0;
	for(int i = 1; i <= 2000; i++) {
		if(mx < dp[i]) {
			mx = dp[i];
			pos = i;
		}
	}
	cout << mx << '\n' << vec[pos].size() << '\n';
	for(auto v : vec[pos]) cout << v << ' ';
	return 0;
}
posted @ 2026-06-04 22:06  nick_zha  阅读(5)  评论(0)    收藏  举报