刷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)。
所以我们易得转移方程:
做完了。
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 家族的传统是在男子日互赠礼物。因为以普通的方式赠送礼物太过无聊,所以每年都会按以下流程赠送礼物:
- 准备一个候选人名单,名单中包含一部分(也可能是全部)\(n\) 名男性,并有一定的顺序。
- 每位男性都决定要赠送礼物。
- 选择赠送礼物的人时,编号为 \(A\) 的男性会遍历名单,选择列表中第一个是自己祖先的男性 \(B\),并将礼物送给他。注意,按照定义,一个人可能会把礼物送给自己。
- 如果某人没有在名单中找到自己的祖先,他会伤心地离开庆典,不向任何人送礼。
今年你打算帮助大家组织庆典,并已经询问每一位男性——他想要把礼物送给哪位祖先(只能从祖先中选择)。你能否制作一个候选人名单,使所有人的这一愿望都能按照上述方式实现?
题解
大概意思如下:
- 构造一个一个排列。对于每个人,他会按顺序扫描这个名单,找到第一个是他的祖先的人(包括自己),并把礼物送给那个人。
- 你已经知道每个人想给谁送礼。能否构造一个候选人名单,使得每个人找到的祖先就是他想送的?
首先注意到如果 \(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;
}

浙公网安备 33010602011771号