刷CF #1800 二周目

CF772A Voltage Keepsake

题目描述

你有 \(n\) 个想要同时使用的设备。

\(i\) 个设备每秒消耗 \(a_{i}\) 单位的电量。这种消耗是连续的。也就是说,在 \(λ\) 秒内,这个设备将会消耗 \(λ·a_{i}\) 单位的电量。第 \(i\) 个设备目前储存着 \(b_{i}\) 单位的电量。所有设备都可以储存任意数量的电量。

你有一个充电器,可以同时为任意一个设备充电。充电器每秒可以为设备充入 \(p\) 单位的电量。充电过程是连续的。也就是说,如果你为某个设备充电 \(λ\) 秒,该设备就会获得 \(λ·p\) 单位的电量。你可以在任意时刻(包括任意实数时间)更换正在充电的设备,更换设备所需时间可以忽略不计。

你想知道,在某个设备的电量变为 \(0\) 之前,你最多能使用这些设备多长时间。

如果你可以无限期地使用这些设备,则输出 \(-1\)。否则,输出在任意一个设备电量变为 \(0\) 之前的最大使用时间。

题解

首先如果所有设备的功率 \(\le\) 充电器功率,答案显然为 \(\infty\),输出 \(-1\).

然后考虑二分答案,设本次二分的答案为 \(\lambda\),那么充电缺口为 \(\sum\limits_{i=1}^n a_i \times \lambda- \sum\limits_{i=1}^n b_i\),比较其和 \(\lambda \times p\) 的大小即可。

Code

#include<bits/stdc++.h>
using namespace std;
const int N = 100005;
int n, p;
struct node {
	int a, b;
} a[N];
bool check(double f) {
	double add = f * (1.0 * p);
	for (int i = 1; i <= n; i++) {
		if (add < 0.0) return 0;
		double x = a[i].a * f - a[i].b;
		add -= max(x, 0.0);
	}
	return add >= 0.0;
}
int main() {
	scanf("%d %d", &n, &p);
	long long num = 0;
	for (int i = 1; i <= n; i++) {
		cin >> a[i].a;
		cin >> a[i].b;
		num += a[i].a;
	}
	if (num <= p) {
		cout << -1;
		return 0;
	}
	double l = 0.0, r = 1e10 * 1.0;
	while (r - l > 1e-6) {
		double mid = (l + r) / 2;
		if (check(mid))l = mid + 0.000001;
		else r = mid - 0.000001;
	}
	printf("%.6lf", l);
	return 0;
}

CF478C Table Decorations

题目描述

您正在为宴会装饰桌子,有 \(r\) 个红色,\(g\) 个绿色和 \(b\) 蓝色的气球,一张桌子需要三个颜色不完全相同的气球来装饰。

您需要求出可以用所需方式进行装饰的桌子的最大数量 \(t\)

题解

我们注意到,一个桌子要么是 \(1:1:1\),要么是 \(2:1\),当数量最大的气球数量的一半小于另外两种颜色气球的数量之和,可以组成全部颜色数量之和的三分之一。

Code

#include<bits/stdc++.h>
using namespace std;
int main(){
	long long r, g, b;
	cin >> r >> g >> b;
	cout << min(min(min((r + g + b) / 3, r + g), r + b), b + g);
	return 0;
}	

CF161D Distance in Tree

题目描述

给定一棵 \(n\) 个节点的树和一个正整数 \(k\)。求树上有多少对 \((u,v)\),其距离恰好为 \(k\)

注意 \((u,v)\)\((v,u)\) 算作同一对节点。

题解

使用 dsu on tree 的一个比较模板的题。

设当前以 \(u\) 为根的子树正在被处理,对于已经完成统计的子树部分,我们维护一个全局的深度计数数组 cnt[dep],表示在已经处理过的子树节点中,深度为 \(dep\) 的节点个数。

统计经过节点 \(u\) 的路径分为两部分:

  1. 一端是 \(u\) 本身:寻找已处理子树中深度为 \(dis_u + k\) 的节点,这样的节点与 \(u\) 的距离恰好为 \(k\)ans += cnt[dis[u] + k]
  2. 两端均在 \(u\) 的不同子树中:设某个轻儿子子树内的节点 \(x\),若存在一个已处理子树中的节点 \(y\) 使得 \(dis_x + dis_y - 2 \times dis_u = k\),则 \(dis_y = k + 2 \times dis_u - dis_x\)。因此对于轻儿子子树中的每一个节点 \(x\),我们检查 cnt[k + 2*dis[u] - dis[x]],并累加到答案。然后将该轻儿子子树的所有节点加入 cnt

为了保证复杂度,采用重轻儿子划分的启发式策略:

  • 先递归解决所有轻儿子,每次处理完轻儿子后清空 cnt 数组。
  • 再递归解决重儿子,处理完后保留 cnt 数组中的计数。
  • 此时 cnt 中已经包含了重儿子子树的所有节点,再依次处理每个轻儿子子树:先查询(统计跨越 \(u\) 的路径),再加入。
  • 最后加上 \(u\) 本身的查询和加入,并将 \(u\) 自身加入 cnt
  • 若当前子树是作为轻儿子被父节点调用,则最后需要调用 remove 清空整个子树的贡献。

Code

#include<bits/stdc++.h>
using namespace std;
const int M = 1e5 + 5;       
vector<pair<int, int>> vec[M];  
int siz[M], son[M], dis[M];
long long ans = 0;             
int n, k;
int cnt[M];                    
void dfs1(int u, int fa) {
    siz[u] = 1;
    son[u] = -1;
    int maxn = 0;
    for (auto &[v, w] : vec[u]) {
        if (v == fa) continue;
        dis[v] = dis[u] + w;  
        dfs1(v, u);
        siz[u] += siz[v];
        if (siz[v] > maxn) {
            maxn = siz[v];
            son[u] = v;
        }
    }
}
void dfs3(int u, int p, vector<int> &tmp) {
    tmp.push_back(dis[u]);
    for (auto &[v, w] : vec[u]) {
        if (v == p) continue;
        dfs3(v, u, tmp);
    }
}
void remove(int u, int p) {
    cnt[dis[u]]--;
    for (auto &[v, w] : vec[u]) {
        if (v != p) remove(v, u);
    }
}
void dfs2(int u, int fa, bool keep) {
    for (auto &[v, w] : vec[u]) {
        if (v == fa || v == son[u]) continue;
        dfs2(v, u, false);
    }
    if (son[u] != -1) {
        dfs2(son[u], u, true);
    }
    int need = dis[u] + k;
    if (need < M) {
        ans += cnt[need];
    }
    cnt[dis[u]]++;
    for (auto &[v, w] : vec[u]) {
        if (v == fa || v == son[u]) continue;
        vector<int> tmp;
        dfs3(v, u, tmp);
        for (int x : tmp) {
            int need = k + 2 * dis[u] - x;
            if (need >= 0 && need < M) {
                ans += cnt[need];
            }
        }
        for (int x : tmp) {
            cnt[x]++;
        }
    }
    if (!keep) {
        remove(u, fa);
    }
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cin >> n >> k;
    for (int i = 1; i < n; i++) {
        int u, v;
        cin >> u >> v;
        vec[u].push_back({v, 1});
        vec[v].push_back({u, 1});
    }
    dfs1(1, 0);
    dfs2(1, 0, true);
    cout << ans << "\n";
    return 0;
}

CF2081A Math Division

题目描述

Ecrade 有一个整数 \(x\)。他将以长度为 \(n\) 的二进制数的形式向你展示这个数。

存在两种操作:

  1. \(x\) 替换为 \(\left\lfloor \frac{x}{2}\right\rfloor\),其中 \(\left\lfloor \frac{x}{2}\right\rfloor\) 是小于等于 \(\frac{x}{2}\) 的最大整数。
  2. \(x\) 替换为 \(\left\lceil \frac{x}{2}\right\rceil\),其中 \(\left\lceil \frac{x}{2}\right\rceil\) 是大于等于 \(\frac{x}{2}\) 的最小整数。

Ecrade 将执行若干次操作直到 \(x\) 变为 \(1\)。每次操作时,他会独立地以 \(\frac{1}{2}\) 的概率选择执行第一种操作或第二种操作。

Ecrade 想知道他将执行的操作次数的期望值(模 \(10^9 + 7\))。由于问题有一定难度,请你帮助他!

题解

我们发现操作 \(2\) 相当于给前面的位 \(+1\),所以我们设 \(dp_{i,0/1}\) 表示当前为第 \(i\) 位,是否被 \(+1\) 的期望。

具体地,将二进制数从低位到高位编号 \(1,2,\dots,n\)\(1\) 为最低位),\(dp_{i,0}\) 表示处理完低 \(i\) 位后没有向第 \(i+1\) 位产生进位时的期望操作次数,\(dp_{i,1}\) 表示有进位时的期望。

初始化:\(dp_{1,0}=0,\ dp_{1,1}=1\)(对应最低位原为 \(0\) 的情况,若有进位则相当于最低位为 \(1\),需一次操作)。

对于第 \(i\) 位(\(i\ge2\)),考虑原数该位 \(s_i\) 的值:

  • \(s_i=0\)

    • 无进位(状态 \(0\)):实际位为 \(0\)。floor 和 ceil 均不产生新进位,高位状态保持 \(0\),每次操作加 \(1\),故 \(dp_{i,0}=dp_{i-1,0}+1\)
    • 有进位(状态 \(1\)):实际位为 \(1\)\(0+1\))。floor 操作后进位消失(高位状态 \(0\)),ceil 操作后产生新进位(高位状态 \(1\)),各以 \(1/2\) 概率发生,故 \(dp_{i,1}=\frac12(dp_{i-1,0}+1)+\frac12(dp_{i-1,1}+1)=\frac{dp_{i-1,0}+dp_{i-1,1}}{2}+1\)
  • \(s_i=1\)

    • 无进位(状态 \(0\)):实际位为 \(1\)。floor 操作后无进位(高位状态 \(0\)),ceil 操作后有进位(高位状态 \(1\)),故 \(dp_{i,0}=\frac12(dp_{i-1,0}+1)+\frac12(dp_{i-1,1}+1)=\frac{dp_{i-1,0}+dp_{i-1,1}}{2}+1\)
    • 有进位(状态 \(1\)):实际位为 \(0\)\(1+1=0\) 进位 \(1\))。floor 和 ceil 均不产生新进位(因为 \(0\) 的一半仍为 \(0\)),高位状态均为 \(0\),故 \(dp_{i,1}=dp_{i-1,0}+1\)

最终,处理完所有 \(n\) 位后,没有进位即为所求,答案为 \(dp_{n,0}\)

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
const int inv = 500000004;
const int M = 1e5 + 5;
int dp[M][2];
void sol(){
	int n;
	cin >> n;
	string s;
	cin >> s;
	s = " " + s;
	dp[1][0] = 0, dp[1][1] = 1;
	for(int i = 2; i <= n; i++) {
		if(s[i] == '0'){
			dp[i][0] = (dp[i - 1][0] + 1) % mod;
			dp[i][1] = (dp[i - 1][0] + dp[i - 1][1]) * inv % mod;
			dp[i][1] = (dp[i][1] + 1) % mod;
		} else {
			dp[i][0] = (dp[i - 1][0] + dp[i - 1][1]) * inv % mod;
			dp[i][0] = (dp[i][0] + 1) % mod;
			dp[i][1] = (dp[i - 1][1] + 1) % mod;
		}
	}
	cout << dp[n][0] << '\n';
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int T;
	cin >> T;
	while(T--) sol();
	return 0;
}

CF979B Treasure Hunt

题目描述

每只猫都随机得到一条彩色的彩带。每种颜色可以用一个大写或小写拉丁字母表示。我们称彩带中连续的一段颜色为“子彩带”。一条彩带的美丽值定义为其某个子彩带在整条彩带中出现的最大次数。子彩带出现次数越多,彩带就越美丽。例如,彩带 aaaaaaa 的美丽值为 \(7\),因为其子彩带 a 出现了 \(7\) 次;而彩带 abcdabc 的美丽值为 \(2\),因为其子彩带 abc 出现了 \(2\) 次。

游戏规则很简单。游戏将进行 \(n\) 轮。每一轮,每只猫必须将自己的彩带中严格一个位置的颜色更改为任意一种与原来不同的颜色。例如,彩带 aaab 可以在一轮中变为 acab。经过 \(n\) 轮后,谁的彩带最美丽,谁就赢得宝藏。

如果三只猫都采取最优策略,你能判断谁会赢吗?

题解

对于每个人,显然改成全部一样的是最优的,这个字母也显然是原字符串里面出现最多的字符。

如果修改次数不够,那能改几次就答案加多少。如果修改次数够,我们可以把一个字符随便乱改来浪费次数,除非原串本来就全部一样且只有一次修改机会。做完了。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
string a, b, c;
int get_max(int f, int L, int n) {
	if (n <= L - f) return f + n;
	else {
		if (n == 1 && f == L) return L - 1;
		else return L;
	}
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int n, len;
	cin >> n;
	cin >> a >> b >> c;
	len = a.size();
	a = " " + a, b = " " + b, c = " " + c;
	int mxa = 0, mxb = 0, mxc = 0;
	vector<int> fa(128, 0), fb(128, 0), fc(128, 0);
	for (int i = 1; i <= len; ++i) {
		fa[a[i]]++;
		fb[b[i]]++;
		fc[c[i]]++;
	}
	for (int ch = 'A'; ch <= 'Z'; ++ch) {
		mxa = max(mxa, get_max(fa[ch], len, n));
		mxb = max(mxb, get_max(fb[ch], len, n));
		mxc = max(mxc, get_max(fc[ch], len, n));
	}
	for (int ch = 'a'; ch <= 'z'; ++ch) {
		mxa = max(mxa, get_max(fa[ch], len, n));
		mxb = max(mxb, get_max(fb[ch], len, n));
		mxc = max(mxc, get_max(fc[ch], len, n));
	}
	if (mxa > mxb && mxa > mxc) cout << "Kuro" << endl;
	else if (mxb > mxa && mxb > mxc) cout << "Shiro" << endl;
	else if (mxc > mxa && mxc > mxb) cout << "Katie" << endl;
	else cout << "Draw" << endl;
	return 0;
}

CF1108E1 Array and Segments (Easy version)

题目描述

给定你一个长度为 \(n\) 的数组 \(a\),再给定你 \(m\) 对数字 \([l_i,r_i]\)。你可以选择其中的几对数字作为两个端点,再将数组 \(a\) 中的两个端点内的数字全部减一。(例如现有一对 \([l_i,r_i]\)\([1,3]\) ,而数组 \(a\)1 2 3 4 5,若使用这对 \([l_i,r_i]\) 数组就会变成 0 1 2 4 5

现在请你求出怎样使得数组 \(a\) 中的最大值减去最小值最大。

题解

我们枚举每一个数字,假设这个数是最小值,那么所有包含这个数的区间我们都必须选。这样直接枚举所有操作就可以了。原因如下:

我们考虑每一个区间,如果这个区间包括了最小值和最大值,那么答案不变;如果只包含最小值,答案会 \(+1\)。所以选中所有包含这个数的区间一定不会使答案变大。

Code

#include <bits/stdc++.h>
using namespace std;
const int N = 305;
int a[N], l[N], r[N], b[N];
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) cin >> a[i];
	for (int i = 1; i <= m; i++) cin >> l[i] >> r[i];
	int ans = -1e9;
	vector<int> op;
	for (int idx = 1; idx <= n; ++idx) {
		memcpy(b + 1, a + 1, n * sizeof(int));   
		vector<int> cur_op;
		for (int i = 1; i <= m; i++) {
			if (l[i] <= idx && idx <= r[i]) continue;
			cur_op.push_back(i);
			for (int j = l[i]; j <= r[i]; j++) --b[j];
		}
		int mx = *max_element(b + 1, b + n + 1), mn = *min_element(b + 1, b + n + 1);
		if (mx - mn > ans) {
			ans = mx - mn;
			op = cur_op;
		}
	}
	cout << ans << '\n' << op.size() << '\n';
	for (int x : op) cout << x << ' ';
	return 0;
}

题目描述

旋转木马由 \(n\) 个动物模型组成。模型按照旋转木马的移动顺序从 \(1\)\(n\) 编号。因此,在第 \(n\) 个模型之后,编号为 \(1\) 的模型接着出现。每个模型都有自己的类型——即该模型所对应的动物类型(如马、虎等)。第 \(i\) 个模型的动物类型为 \(t_i\)

你希望用若干种颜色为每个模型上色。你认为,如果旋转木马上有两个相邻的不同类型的模型被涂成了相同的颜色,这样会很无聊。

你的任务是为所有模型上色,使得所用颜色的种类数尽可能少,并且不存在两个相邻的不同类型的模型被涂成相同颜色的情况。如果你使用了恰好 \(k\) 种不同的颜色,则模型的颜色编号应为 \(1\)\(k\) 的整数。

题解

首先,如果所有 \(t_i\) 都相等,答案就应该是 \(1\)。接着,我们考虑对于 \(n\) 的奇偶对答案的影响。

\(n\) 为偶数时,我们可以构造 \(1,2,1,2\cdots\) 这样的颜色序列,显然满足条件。

\(n\) 为奇数时,这样填会出问题,因为首尾的颜色相同,但考虑这是一个环,如果有两个相邻且相等的数,我们就让这两个位置颜色相等就可以了。如果不存在有两个相邻且相等的数,那只能把最后一个数字染成第三种颜色了。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 2e5 + 5;
int t[M];
void sol(){
	int n, f = 0;
	cin >> n;
	for(int i = 1; i <= n; i++){
		cin >> t[i];
		if(i > 1 && t[i] != t[i - 1]) f++;
	}
	if(f == 0){
		cout << "1\n";
		for(int i = 1; i <= n; i++) cout << "1 ";
		cout << "\n";
		return;
	}
	if(n % 2 == 0) {
		cout << "2\n";
		for(int i = 1; i <= n / 2; i++) cout << "1 2 ";
		cout << "\n";
		return;
	} else {
		if(f == n - 1 && t[n] != t[1]) {  
			cout << "3\n";
			for(int i = 1; i <= n / 2; i++) cout << "1 2 ";
			cout << "3\n";
		} else {                            
			int pos;
			for(pos = 1; pos < n; pos++) {
				if(t[pos] == t[pos + 1]) break;
			}         
			cout << "2\n";
			int nw = 0;
			for(int i = 1; i <= n; i++) {
				cout << nw + 1 << ' ';
				if(i != pos) nw ^= 1;
			}
			cout << '\n';
		}
	}
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int T;
	cin >> T;
	while(T--) sol();
	return 0;
}

CF652C Foe Pairs

题目描述

给定一个 \(1\)\(n\) 数字组成的全排列,同时给定 \(m\) 对元素 \((a_i,b_i)\)

你的任务是统计有多少个不同的区间 \((x,y)(1 \le x \le y \le n)\),这些区间不包含任意一个给定的元素对,即不能同时含有元素对 \((a_i,b_i)\) 的两个元素,这两个元素的先后顺序不限定。

题解

我们把每个数的位置记录下来,然后每个限制就相当于是不能选中一段区间。

像这种题我们考虑扫描线,那么对于一个位置 \(i\),以他为区间右端点的区间究竟有多少个?不难发现只需要找到在 \(i\) 之前线段的最右端点就可以了,假设位置为 \(lst_i\),那么符合条件的区间就是 \(\sum\limits_{i=1}^n (i - lst_i)\)

Code

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int M = 3e5 + 5;
int a[M], pos[M], lim[M]; 
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int n, m;
	cin >> n >> m;
	for (int i = 1; i <= n; i++) {
		cin >> a[i];
		pos[a[i]] = i;
	}
	for (int i = 1; i <= m; i++) {   
		int u, v;
		cin >> u >> v;
		int p1 = pos[u], p2 = pos[v];
		if (p1 > p2) swap(p1, p2);
		lim[p2] = max(lim[p2], p1);     
	}
	int cur = 0, ans = 0;
	for (int y = 1; y <= n; y++) {
		cur = max(cur, lim[y]);      
		ans += y - cur;              
	}
	cout << ans << '\n';
	return 0;
}

CF45J Planting Trees

题目描述

瓦西亚是一位“Greencode”野生动植物保护协会的倡导者。一天,他发现了一块没人拥有的空地,将其划分成 \(n \times m\) 个方格,并决定在这里种植一片森林。瓦西亚计划在每个方格中各种下一棵高度不同的树,总共 \(nm\) 棵,高度分别为 \(1\)\(nm\)。为了让森林看起来更加自然,他希望任何两个在边上相邻的方格里的树,它们的高度差的绝对值必须严格大于 \(1\)。请帮助瓦西亚:设计一种种树方案,使这一条件得到满足。

题解

首先判断无解,手玩一下可以发现有 \(1 \times 2, 1 \times 3,2 \times 2, 2 \times 1, 3 \times 1\),此时 \(n + m < 5\),所以直接特判输出。但是要注意 \(1 \times 1\) 是有解的,输出一个 \(1\) 就行了。

然后我们考虑把 \(x\)\(\frac{nm}{2} + x\) 配成一对,假设 \(i + j\) 为奇数就放 \(x\),为偶数就放 \(\frac{nm}{2} + x\),这样每条斜线上的数都是连续的,可以保证相邻的数字绝对值之差大于 \(1\)

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 105;
const int N = 1e4 + 5;
int ans[M][M], vis[N];
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int n, m;
	cin >> n >> m;
	if(n == 1 && m == 1) {
		cout << "1";
		return 0;
	} else if(n + m < 5) {
		cout << "-1";
		return 0;
	} else {
		int t1 = 1, t2 = (n * m) / 2 + 1;
		for(int i = 1; i <= n; i++){
			for(int j = 1; j <= m; j++) {
				if((i + j) % 2 == 0) cout << t2++ << ' ';
				else cout << t1++ << ' ';
			} 
			cout << '\n';
		}
	}
	return 0;
}

CF1468J Road Reform

题目描述

在 Berland 有 \(n\) 个城市和 \(m\) 条双向道路。第 \(i\) 条道路连接城市 \(x_i\)\(y_i\),限速为 \(s_i\)。该道路网络保证任意两个城市之间都可以互相到达。Berland 交通部计划进行道路改革。

首先,维护所有 \(m\) 条道路的成本太高,因此将拆除 \(m - (n - 1)\) 条道路,使得剩下的 \((n - 1)\) 条道路依然保证任意两个城市之间可以互相到达。形式上,剩下的道路应构成一棵无向树。

其次,剩余道路的限速可以调整。每次调整可以将某条道路的限速加 \(1\) 或减 \(1\)。由于调整限速的工作量很大,交通部希望最小化调整次数。

交通部的目标是让剩下的 \((n - 1)\) 条道路中,所有道路的最大限速恰好等于 \(k\)。你的任务是计算,为了满足要求,最少需要多少次限速调整。

题解

显然看到 \(n - 1\) 条边就该想到最小生成树。我们直接求一次,并且记这个最小生成树的最大边权为 \(val\),有以下三种情况:

  • \(val < k\):显然给最小生成树换一条边还是生成树,于是在原图中找 \(w_i - k\) 最小的边即可。
  • \(val = k\):输出 \(0\) 即可。
  • \(val > k\):因为这是最小生成树,所以答案一定最优,直接求 \(\sum \max(0, w_i - k)\)

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 2e5 + 5;
struct node {
	int u, v, w;
	bool operator <(const node &a) const {
		return w < a.w;
	}
};
vector<node>vec;
int fa[M];
int find(int x) {
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}
void merge(int x, int y) {
	int f1 = find(x), f2 = find(y);
	if (f1 != f2) fa[f1] = f2;
}
void sol() {
	int n, m, k;
	cin >> n >> m >> k;
	for (int i = 1, u, v, w; i <= m; i++) {
		cin >> u >> v >> w;
		vec.push_back((node) {u, v, w});
	}
	for (int i = 1; i <= n; i++) fa[i] = i;
	sort(vec.begin(), vec.end());
	int mx = -1, ans = 0;
	for (auto [u, v, w] : vec) {
		if (find(u) != find(v)) {
			merge(u, v);
			mx = max(mx, w);
			if(mx > k) ans += mx - k;		
		}
	}
	if(mx < k){
		ans = 1e18;
		for (auto [u, v, w] : vec) ans = min(ans, abs(k - w));
	}
	cout << ans << '\n';
	vec.clear();
}
signed main() {
	ios::sync_with_stdio(0);
	cin.tie(0), cout.tie(0);
	int T;
	cin >> T;
	while (T--) sol();
	return 0;
}

CF1572A Book

题目描述

给你一本有 \(n\) 章的书。

每一章都有一个为理解本章而需要理解的其他章节的特定列表。要理解一个章节,你必须在理解了所需列表中的每一章后再阅读。

目前你还不了解任何章节。你要从头到尾反复阅读这本书,直到你理解整本书。请注意,如果您在阅读某一章节时不理解某些必要章节,则表示您暂时不能理解本章。

输出你要读多少次书才能理解每一章。如果你永远不会理解整本书,那么输出 \(-1\)

题解

套路地对于每一条限制建有向边,显然如果出现环则无解。接下来考虑在 DAG 上 DP,拓扑排序时记录一下每个节点被访问的次数,直接取最大值即可。

Code

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int M = 2e5 + 5;
vector<int> vec[M];
int in[M], dp[M];
void sol() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) {
		dp[i] = 0;
		cin >> in[i];
		for (int j = 1, x; j <= in[i]; j++) {
			cin >> x;
			vec[x].push_back(i);   
		}
	}
	priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;
	for (int i = 1; i <= n; i++) {
		if (in[i] == 0) {
			dp[i] = 1;
			q.push({1, i});
		}
	}
	int cnt = 0, ans = 0;
	while (!q.empty()) {
		auto [r, u] = q.top(); q.pop();
		cnt++;
		ans = max(ans, r);
		for (int v : vec[u]) {
			dp[v] = max(dp[v], r + (u > v ? 1 : 0));
			if (--in[v] == 0) {
				q.push({dp[v], v});
			}
		}
	}
	if (cnt != n) cout << -1 << '\n';
	else cout << ans << '\n';
	for(int i = 1; i <= n; i++) 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;
}

CF863C 1-2-3

题目描述

有两个机器人同时从 \(1 \sim 3\) 中选择一个数字,如果他们选择相同的数字,那么这是平局,没有人得分。但是,如果这两个数字不同,那么其中一个机器人能得一分。\(3\) 能打败 \(2\),而 \(2\) 能打败 \(1\)\(1\) 能打败 \(3\)

这两个机器人的程序使得他们在第 \((i+1)\) 局中的选择只依赖于第 \(i\) 局的选择。

Ilya 知道机器人将会玩 \(k\) 局游戏,Alice 机器人将会在第一局选择数字 \(a\),而 Bob 机器人会选择数字 \(b\)。他也知道两个机器人的程序,可以根据他们在前一场比赛中的选择来判断每个机器人会选择什么。但 Ilya 太懒了不想等到机器人把那 \(k\) 局游戏玩完,所以他请你来预测在最后一场比赛后每个机器人的得分。

题解

数据范围极大,考虑倍增。

我们记状态 \(s = (i−1) \times 3+j\) 表示 Alice 出 \(i\),Bob 出 \(j\),同时记录 \(f_{s, t}\) 表示从状态 \(s\) 出发经过 \(2^k\) 轮后会到达的状态,\(sum_{s, t}\) 表示 Alice 的得分,\(sum2_{s,t}\) 表示 Bob 的得分。

初始化的时候根据题目描述处理 \(t = 0\) 的情况,然后开始预处理倍增数组,我们有 \(f_{s,t} = f_{f_{s, t - 1}, t - 1}\)\(sum\)\(sum2\) 同理。

最终求答案的时候将 \(k\) 进行二进制拆分,加上 \(sum\)\(sum2\) 数组对应的值并转移即可。

Code

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 62;       
int f[M][10], a[4][4], b[4][4];            
ll sum[M][10], sum2[M][10];
int main() {
	ll k;
	int x, y;
	cin >> k >> x >> y;    
	for (int i = 1; i <= 3; i++){
		for (int j = 1; j <= 3; j++){
			cin >> a[i][j]; 
		}
	}
	for (int i = 1; i <= 3; i++){
		for (int j = 1; j <= 3; j++){
			cin >> b[i][j];
		}
	}
	for (int i = 1; i <= 3; i++) {
		for (int j = 1; j <= 3; j++) {
			int s = (i - 1) * 3 + j;
			int ni = a[i][j];
			int nj = b[i][j];
			f[0][s] = (ni - 1) * 3 + nj; 
			if ((i == 1 && j == 3) || (i == 2 && j == 1) || (i == 3 && j == 2))
				sum[0][s] = 1, sum2[0][s] = 0;
			else if ((j == 1 && i == 3) || (j == 2 && i == 1) || (j == 3 && i == 2))
				sum[0][s] = 0, sum2[0][s] = 1;
			else sum[0][s] = 0, sum2[0][s] = 0;
		}
	}
	for (int i = 1; i < M; i++){
		for (int s = 1; s <= 9; s++) {
			int mid = f[i - 1][s];
			f[i][s] = f[i - 1][mid];
			sum[i][s] = sum[i - 1][s] + sum[i - 1][mid];
			sum2[i][s] = sum2[i - 1][s] + sum2[i - 1][mid];
		}
	}
	int cur = (x - 1) * 3 + y;
	ll ans1 = 0, ans2 = 0;
	for (int i = 0; i < M; i++) {
		if (k & (1LL << i)) {
			ans1 += sum[i][cur];
			ans2 += sum2[i][cur];
			cur = f[i][cur];
		}
	}
	cout << ans1 << ' ' << ans2;
	return 0;
}

CF49D Game

题目描述

Vasya 和 Petya 发明了一个新游戏。Vasya 拿出一条由 \(1×n\) 的方格条,并把这些方格涂成黑色和白色。随后 Petya 可以开始移动:每次移动时,他可以选择任意两个相邻、颜色相同的方格,将它们重新涂成任意颜色,也可以涂成不同的颜色。Petya 只能用黑色和白色进行涂色。Petya 的目标是把这条方格条涂成没有两个相邻方格同色的状态。请帮 Petya 计算实现目标所需的最少操作次数。

题解

我们注意到两个相邻方格同色的状态只有 \(2\) 种,于是直接模拟即可。

Code

#include<bits/stdc++.h>
using namespace std;
int main() {
	int n, ans1 = 0, ans2 = 0;
	cin >> n;
	for(int i = 1; i <= n; i++) {
		char x;
		cin >> x;
		if(x - '0' == i % 2) ans1++;
		else ans2++;
	}
	cout << min(ans1, ans2);
}
posted @ 2026-06-10 21:42  nick_zha  阅读(10)  评论(0)    收藏  举报