刷Atcoder

AT_abc322_e [ABC322E] Product Development

题目描述

AtCoder 社正在开发一款新产品。该产品有 \(K\) 个参数,目前所有参数的值均为 \(0\)。AtCoder 社的目标是让所有参数的值都达到 \(P\) 以上。

现在有 \(N\) 个开发方案。执行第 \(i\) 个开发方案后,对于每个 \(1 \leq j \leq K\),第 \(j\) 个参数会增加 \(A_{i,j}\),但执行该开发方案需要花费 \(C_i\) 的成本。

同一个开发方案不能执行一次以上。请判断 AtCoder 社能否达成目标,如果可以,求出达成目标所需的最小总成本。

题解

暴力01背包。设 \(f_{x1,x2,x3,x4,x5}\) 为达到这些属性的最小花费,对于每一条开发方案,我们有: \(f_{x1,x2,x3,x4,x5} = \min(f_{x1,x2,x3,x4,x5},f_{a1,a2,a3,a4,a5}+c_x)\)

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 6;
ll f[M][M][M][M][M], c[105];
int main(){
	int n, k, p;
	scanf("%d%d%d", &n, &k, &p);
	memset(f, 0x3f, sizeof(f));
	f[0][0][0][0][0] = 0;
	for(int i = 1; i <= n; i++){
		int a[M] = {0};
		scanf("%lld", &c[i]);
		for(int j = 1; j <= k; j++){
			scanf("%d", &a[j]);
		}
		ll g[M][M][M][M][M];
		memcpy(g, f, sizeof(f));            
		for(int v1 = 0; v1 <= p; v1++){
			for(int v2 = 0; v2 <= (k >= 2 ? p : 0); v2++){
				for(int v3 = 0; v3 <= (k >= 3 ? p : 0); v3++){
					for(int v4 = 0; v4 <= (k >= 4 ? p : 0); v4++){
						for(int v5 = 0; v5 <= (k >= 5 ? p : 0); v5++){
							if(g[v1][v2][v3][v4][v5] > 1e18) continue;
							int nv1 = min(p, v1 + a[1]);
							int nv2 = min(p, v2 + a[2]);
							int nv3 = min(p, v3 + a[3]);
							int nv4 = min(p, v4 + a[4]);
							int nv5 = min(p, v5 + a[5]);
							f[nv1][nv2][nv3][nv4][nv5] = min(f[nv1][nv2][nv3][nv4][nv5], g[v1][v2][v3][v4][v5] + c[i]);
						}
					}
				}
			}
		}
	}
	int t[5] = {0};
	for(int i = 0; i < k; i++) t[i] = p;
	ll ans = f[t[0]][t[1]][t[2]][t[3]][t[4]];
	if(ans > 1e18) puts("-1");
	else printf("%lld\n", ans);
	return 0;
}

AT_arc159_b [ARC159B] GCD Subtraction

题目描述

有两个变量 \(a, b\),初始时 \(a = A, b = B\)

高桥君决定在 \(a, b\) 都大于等于 \(1\) 的情况下,反复进行如下操作:

  • \(a\)\(b\) 的最大公约数为 \(g\)。然后,将 \(a, b\) 分别替换为 \(a-g, b-g\)

操作会被执行多少次?

题解

显然,两个数先除以它们的 gcd 不会影响答案。

然后会发现暴力模拟会被 \(a\)\(a + 1\) 卡爆,因为 \(\gcd(a, a + 1) = 1\) 。所以我们对于一个 \(\gcd(a,b) = 1\) 的数对 \((a,b)\),需要快速找到一个 \(x\) 使得 \(\gcd(a-x, b-x) \neq 1\)

假设 \(a-x \equiv b - x \pmod d\),则我们可知 \(a \equiv b \pmod d\),即 \(d |(a-b)\)。所以我们枚举 \(a - b\) 的因数,就可以 \(\mathcal{O}(\sqrt{n})\) 知道最小的 \(x\),剩下的就暴力即可。

十年 OI 一场空,____________。

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
int main() {
	ll a, b, ans = 0;
	scanf("%lld%lld", &a, &b);
	while(a >= 1 && b >= 1){
		if(a < b) swap(a, b);
		ll g = __gcd(a, b);
		a /= g, b /= g;
		if(a == 0 || b == 0) break;
		if(a == b) {ans++; break;}
		if(a == b + 1) {ans += b; break;}
		ll t = b, sub = a - b;
		for(ll i = 2; i * i <= sub; i++){
			if(sub % i == 0){
				if(a % i == b % i) t = min(t, a % i);
				if(a % (sub / i) == b % (sub / i)) t = min(t, a % (sub / i));
			}
		}
		if(a % sub == b % sub) t = min(t, a % sub);
		a -= t, b -= t;
		ans += t;
	}
	printf("%lld", ans);
	return 0;
}

AT_agc023_b [AGC023B] Find Symmetries

题目描述

Snuke 有两块板子。每块板都是一个 \(n\)\(n\) 列的网格。对于这两块板子,记第 \(i\)\(j\) 列的格子为 \((i,j)\)

第一块板子的每个格子上都写着一个小写字母:格子 \((i,j)\) 上的字母为 \(S_{i,j}\)。第二块板子上没有写任何东西。

Snuke 将以以下方法在第二块板子上写下字母:

首先,选择两个整数 \(A\)\(B\);然后在第二块板子的每个格子上写下一个字母。具体的说,第二块板子的格子 \((i+A,j+B)\) 将写上 \(S_{i,j}\)。这里第 \(n+k\) 行即第 \(k\) 行,第 \(n+k\) 列即为第 \(k\) 列。

此操作后,若对任意的 \(i,j\),第二块板的格子 \((j,i)\) 上的字母和格子 \((i,j)\) 上的字母相同,则称第二块板为“好板”。

请你求出有多少 \(A,B\) 满足 \(0\le A,B<n\),且经过上述操作后第二块板为“好板”。

题解

怎么都可以做。

暴力的话枚举 \(i,j\)\(\mathcal{O}(n^2)\) 的,暴力 check 是 \(\mathcal{O}(n^2)\),一共 \(\mathcal{O}(n^4)\),不可接受。

可以哈希 \(\mathcal{O}(1)\) check,那就做完了 太暴力了吧

而我们发现, \(T_{x,y} = S_{x - A,y - B}\),而又要求 \(T_{i,j} = T_{j,i}\),即 \(S_{x - A, y - B} = S_{x - B,y - A}\),而我们令 \(p = x - A, q = y - B\),则右边就是 \(S_{q + B - A, p + A - B}\)。我们再令 \(d = (B - A) \bmod n\),那么我们显然只需要枚举 \(d\) 即可。枚举时间复杂度降至 \(\mathcal{O}(n)\),可以通过。

Code

#include <bits/stdc++.h>
using namespace std;
const int M = 305;
char f[M][M];
int main() {
	int n;
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%s", f[i] + 1); 
	}
	int ans = 0;
	for (int d = 0; d < n; d++) {
		bool flag = true;
		for (int i = 1; i <= n && flag; i++) {
			for (int j = 1; j <= n; j++) {
				int x = (j + d - 1) % n + 1;
				int y = (i - d - 1 + n) % n + 1;
				if (f[i][j] != f[x][y]) {
					flag = false;
					break;
				}
			}
		}
		if (flag) ans++;
	}
	printf("%d\n", ans * n);  
	return 0;
}

AT_abc378_f [ABC378F] Add One Edge 2

题目描述

给定一棵有 \(N\) 个顶点的树。第 \(i\) 条边 \((1\leq i\leq N-1)\) 连接了顶点 \(u_i\) 和顶点 \(v_i\),且为无向边。

在给定的树上添加一条无向边后,得到的图一定恰好包含一个简单环。

请计算满足以下所有条件的图的个数:

  • 图是简单图。
  • 图中环上所有顶点的度数都为 \(3\)

题解

我们的目标是计算满足以下条件的顶点对 \((u, v)\) 的个数:

  • 顶点 \(u\)\(v\) 的度数都是 \(2\)
  • 连接 \(u\)\(v\) 的简单路径上的所有顶点(不包括 \(u\)\(v\) )的度数都是 \(3\)

这可以通过列举所有度数为 \(3\) 的顶点子图的连接部分来实现。对于每个连通子图,让 \(c\) 成为与之相邻的度数为 \(2\) 的顶点的个数。那么,来自该连通部分的有效配对数为 \(c(c−1)/2\)

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 2e5 + 5;
vector<int>vec[M];
bool vis[M];
int n, res = 0, cnt;
void dfs(int u){
	vis[u] = 1;
	for(auto v : vec[u]){
		if(vis[v]) continue;
		if(vec[v].size() == 3) dfs(v);
		if(vec[v].size() == 2) cnt++;
	}
}
int main(){
	scanf("%d", &n);
	for(int i = 1; i < n; i++){
		int u, v;
		scanf("%d%d", &u, &v);
		vec[u].push_back(v);
		vec[v].push_back(u);
	}
	for(int i = 1; i <= n; i++){
		if(vec[i].size() != 3 || vis[i]) continue;
		cnt = 0;
		dfs(i);
		res += cnt * (cnt - 1) / 2;
	}
	printf("%d", res);
	return 0;
}

AT_abc167_e [ABC167E] Colorful Blocks

题目描述

\(N\) 个方块横向排列成一行。现在要给这排方块涂色。 我们定义两种涂色方案不同,是指存在某个方块被涂成了不同的颜色。 请计算满足以下条件的涂色方案有多少种:

  • 每个方块可以被涂成颜色 \(1\) 到颜色 \(M\) 中的任意一种。可以有颜色未被使用。

  • 所有相邻的方块对中,被涂成相同颜色的对数不超过 \(K\)

    由于答案可能非常大,请输出答案对 \(998244353\) 取模的结果。

题解

我们枚举 \(i\)\(0 \to k\),对于一个 \(i\),我们可以先把相邻 \(i\) 个缩成一个点,显然这并不影响答案。第一个元素有 \(m\) 种选法,剩余 \(n - i - 1\) 种元素有 \(m - 1\) 种选法。然后考虑这个缩成的点的位置,显然是在 \(n - 1\) 个位置选出 \(i\) 个,为 \(C_{n - i} ^ i\)。所以答案为 \(\sum\limits_{i = 0}^k m \cdot (m - 1) ^ {n - i - 1} \cdot C_{n - 1}^i\)

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 2e5 + 5;
const ll mod = 998244353;
ll fac[M], inv[M];
ll qpow(ll x, ll y) {
	ll res = 1;
	while (y) {
		if (y & 1) res = res * x % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return res;
}
void init(int n) {
	fac[0] = inv[0] = 1;
	for (int i = 1; i <= n; i++) {
		fac[i] = 1ll * fac[i - 1] * i % mod;
	}
	inv[n] = qpow(fac[n], mod - 2);
	for (int i = n; i >= 1; i--) {
		inv[i - 1] = inv[i] * i % mod;
	}
}
ll C(int n, int m) {
	return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int main() {
	int n, m, k;
	scanf("%d%d%d", &n, &m, &k);
	init(n);
	ll ans = 0;
	for (int i = 0; i <= k; i++) {
		ans = (ans + m * qpow(m - 1, n - 1 - i) % mod * C(n - 1, i) % mod) % mod;
	}
	printf("%lld", ans);
	return 0;
}

AT_abc395_f [ABC395F] Smooth Occlusion

题目描述

高桥君共有 \(2N\) 颗牙齿,其中 \(N\) 颗是上牙,剩余的 \(N\) 颗是下牙。

左数第 \(i\) 颗(\(1 \leq i \leq N\))上牙的长度为 \(U_i\),左数第 \(i\) 颗(\(1 \leq i \leq N\))下牙的长度为 \(D_i\)

当高桥君的牙齿满足以下两个条件时,称为「良好咬合」:

  1. 存在一个整数 \(H\),使得对于所有 \(1 \leq i \leq N\),有 \(U_i + D_i = H\)
  2. 对于所有 \(1 \leq i < N\),有 \(|U_i - U_{i+1}| \leq X\)

高桥君可以执行以下操作任意次:

  • 支付 \(1\) 日元使用磨牙工具,选择一个长度为正的牙齿,将其长度减少 \(1\)

除上述操作外,无法通过其他方式改变牙齿长度。请计算高桥君达成良好咬合所需支付的最小金额。

题解

总花费等于 \(\sum (U_i + D_i) - N \cdot H\),因此最小化花费等价于最大化可行的 \(H\)

对于给定的 \(H\),每个 \(u_i\) 的取值范围为 \([\max(0, H-D_i),\; U_i]\)。利用相邻差约束 \(|u_i - u_{i+1}| \le X\),可以递推维护当前 \(u_i\) 的可行区间:\([l_{i+1}, r_{i+1}] = \big[\max(L_{i+1}(H),\; l_i - X),\; \min(U_{i+1},\; r_i + X)\big]\)

若某步 \(l_{i+1} > r_{i+1}\),则 \(H\) 不可行。

\(H\) 越大,下限 \(L_i(H)\) 越大,可行性单调递减。因此二分最大的可行 \(H\),答案即为 \(\sum(U_i + D_i) - N \cdot H_{\max}\)

Code

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 2e5 + 5;
struct T {
    ll u, d;
} a[M];
ll x, tot;
int n;
bool chk(ll h) {
    ll l = 0, r = h;
    for (int i = 1; i <= n; i++) {
        ll u = a[i].u, d = a[i].d;
        l = max({l + d, h + x, x + d}) - x - d;
        r = min(r + x, u);
        if (l > r) return false;
    }
    return true;
}
int main() {
    scanf("%d%lld", &n, &x);
    for (int i = 1; i <= n; i++) {
        scanf("%lld%lld", &a[i].u, &a[i].d);
        tot += a[i].u + a[i].d;
    }
    ll l = 0, r = 2e9;
    while (l < r) {
        ll mid = (l + r + 1) >> 1;
        if (chk(mid)) l = mid;
        else r = mid - 1;
    }
    printf("%lld\n", tot - n * l);
    return 0;
}

AT_abc272_e [ABC272E] Add and Mex

题目描述

给定一个长度为 \(N\) 的整数序列 \(A=(A_1,A_2,\ldots,A_N)\)

请进行以下操作 \(M\) 次。

  • 对于每个 \(i\ (1\leq i \leq N)\),将 \(i\) 加到 \(A_i\) 上。之后,求出不在 \(A\) 中的最小非负整数。

题解

神。

我们注意到 mex 的范围肯定在 \([0,n]\) 之间,所以对于 \(A_i\) 来说只有 $A_i \in [0,n] $ 的时候才有效。所以我们直接快进累加,到 $A_i \in [0,n] $ 的时候统计答案即可。时间复杂度 \(\sum\limits_{i = 1}^n \frac{n}{i} = \mathcal{O}(n \lg n)\)

Code

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int M = 2e5 + 10;
int n, m, a[M];
vector<int> v[M];
bool vis[M];
int main() {
	scanf("%d%d", &n, &m);
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
	for (int i = 1; i <= n; i++) {
		int k = max((-a[i] - 1) / i, 0) + 1;
		a[i] += k * i; // 拉到 [0,n] 区间内
		for (int j = k; j <= m; j++) {
			if (a[i] >= n) break;
			v[j].push_back(a[i]), a[i] += i;
		}
	}
	for (int i = 1, p; i <= m; i++) {
		for (auto x : v[i]) vis[x] = 1;
		for (int j = 0; j <= n; j++) {
			if (!vis[j]) { p = j; break; }
		}
		for (auto x : v[i]) vis[x] = 0;
		printf("%d\n", p);
	}
	return 0;
}

AT_arc143_b [ARC143B] Counting Grids

题目描述

在一个 \(N \times N\) 的方格中,每个格子填入 \(1\)\(N^2\) 的整数,每个数恰好出现一次。请计算有多少种填数方式,使得每个格子都至少满足以下两个条件之一,将答案对 \(998244353\) 取模。

  • 在同一列中,存在一个格子,其数比当前格子的数大。
  • 在同一行中,存在一个格子,其数比当前格子的数小。

题解

正难则反,考虑不符合条件的情况。那么就是存在一个格子,他是同一行最小,同一列最大。

注意到只有一个点符合条件,因为假设 \(\ge2\) 个点,令其中两个为 \((x_1, y_1)\)\((x_2, y_2)\),根据定义有 \(a_{x1,y1} > a_{x1, y2} > a_{x2,y2}\) ,也有 \(a_{x1,y1} < a_{x1, y2} < a_{x2,y2}\),显然矛盾。

那么假设这个点的数字是 \(i\),那么大于 \(i\) 的数字有 \(n^2 - i\) 个,总个数是 \((i-1)^2! * A_{n^2-i}^{n-i} * A_{i-1}^{n-1}\)

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 350234;
const int mod = 998244353;
ll fac[M], inv[M];
ll qpow(ll x, ll y) {
	ll res = 1;
	while (y) {
		if (y & 1) res = x * res % mod;
		x = x * x % mod;
		y >>= 1;
	}
	return res;
}
void init(int T) {
	fac[0] = 1;
	for (ll i = 1; i <= T; i++) {
		fac[i] = fac[i - 1] * i % mod;
	}
	inv[T] = qpow(fac[T], mod - 2);
	for (ll i = T - 1; i >= 0; i--) {
		inv[i] = inv[i + 1] * (i + 1) % mod;
	}
}
ll C(int n, int m) {
	if (n < 0 || m > n) return 0;
	return fac[n] * inv[m] % mod * inv[n - m] % mod;
}
int main() {
	int n;
	scanf("%d", &n);
	init(M - 2);
	ll sum = fac[n * n];
	ll ret = 0;
	for (int i = n; i <= n * n - n + 1; i++){
		ll t = C(i - 1,n - 1) * fac[n - 1] % mod;
		t = t * C(n * n - i,n - 1) % mod * fac[n - 1] % mod;
		ret = (ret + t) % mod;
	}
	ret = ret * n % mod * n % mod;
	ret = ret * fac[n * n - 2 * n + 1] % mod;
	sum = (sum - ret + mod) % mod;
	printf("%lld", sum);
	return 0;
}

AT_arc179_b [ARC179B] Between B and B

题目描述

给定一个由 \(1\)\(M\) 之间的整数构成的长度为 \(M\) 的数列 \((X_1, X_2, \dots, X_M)\)

请计算满足以下条件的长度为 \(N\) 的数列 \(A = (A_1, A_2, \dots, A_N)\) 的个数,并对 \(998244353\) 取模。

  • 对于每个 \(B=1,2,\dots,M\),在 \(A\) 中任意两个不同位置的 \(B\) 之间(包括两端),都存在 \(X_B\)

更准确地说,对于每个 \(B=1,2,\dots,M\),都满足以下条件:

  • 对于所有满足 \(1 \leq l < r \leq N\)\(A_l = A_r = B\) 的整数对 \((l, r)\),都存在一个整数 \(m\),使得 \(l \leq m \leq r\)\(A_m = X_B\)

题解

发现 \(M\) 的大小只有 \(10\),考虑使用状压 dp。

我们用一个二进制 mask 表示对于每个 \(i \in [1,m]\),如果我们在序列里放了 \(i\),但还没有放 \(X_i\),那么第 \(i\) 位为 1。如果 \(X_i\) 已经出现过,那么这一位为 \(0\)(表示该限制已满足,可以再次放 \(i\))。

所以我们枚举当前 mask 中为 1 的位 \(i\)(表示可以放置的数)。放置 \(i\) 后,把 \(i\) 对应的那一位从 mask 中去掉(因为已经放过了),同时把 \(X_i\) 的那一位也去掉(因为 \(X_i\) 的放置可能会满足某些限制),注意这里是用 add[x] 表示 \(i\) 的放置能消除哪些条件。

dp[cur][mask] 为当前长度为 ii 时,状态为 mask 的方案数。

对于每一轮(增加序列长度):枚举当前状态 mask,对于每个可以放的 xmask 中为 1 的位,有新状态 nmask = (mask ^ (1<<(x-1))) | add[x],表示:已经放了 x(清除它的等待位),同时可能满足一些数的限制(加上 add[x] 的位)。

我们最终只需要枚举所有可能的 mask,求和即可,因为最后我们并不要求所有等待都清零,只要序列结束即可。

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int mod = 998244353;
const int M = 1e4 + 5;
int a[M], add[12];
ll dp[2][1025];
int main(){
	int m, n;
	scanf("%d%d", &m, &n);
	for(int i = 1; i <= m; i++){
		scanf("%d", &a[i]);
	}
	int full = (1 << m) - 1;
	for(int i = 1; i <= m; i++){
		add[a[i]] |= (1 << (i - 1));
	}
	memset(dp, 0, sizeof(dp));
	dp[0][full] = 1;
	int cur = 0;
	for(int i = 0; i < n; i++){
		int nxt = cur ^ 1;
		memset(dp[nxt], 0, sizeof(dp[nxt]));
		for(int mask = 0; mask <= full; mask++){
			if(dp[cur][mask] == 0) continue;
			ll val = dp[cur][mask];
			for(int x = 1; x <= m; x++){
				if(mask & (1 << (x - 1))){
					int nmask = (mask ^ (1 << (x - 1))) | add[x];
					dp[nxt][nmask] = (dp[nxt][nmask] + val) % mod;
				}
			}
		}
		cur = nxt;
	}
	ll ans = 0;
	for(int mask = 0; mask <= full; mask++){
		ans = (ans + dp[cur][mask]) % mod;
	}
	printf("%lld\n", ans);
	return 0;
}

AT_abc157_e [ABC157E] Simple String Queries

题目描述

给定一个长度为 \(N\) 且仅包含小写字母的字符串 \(S\),有 \(Q\) 次操作,每次操作是以下两种之一:

  1. 格式为 1 i c,表示将 \(S\) 的第 \(i\) 个字符改为 \(c\)
  2. 格式为 2 l r,表示查询区间 \(S_l,S_{l+1}\dots,S_r\) 内不同字符的个数。

题解

这是线段树?树状数组?带修莫队?哈哈哈都不是。

用一个 set 存下每个字母的位置,直接修改和查询就行了。

建议降黄。

Code

#include <bits/stdc++.h>
using namespace std;
const int N = 500005;
int n, q;
string s;
set<int> pos[26]; 
int main() {
	ios::sync_with_stdio(false);
	cin.tie(0);
	cin >> n >> s;
	s = " " + s;
	for (int i = 1; i <= n; i++) {
		pos[s[i] - 'a'].insert(i);
	}
	cin >> q;
	while (q--) {
		int op;
		cin >> op;
		if (op == 1) {
			int i;
			char c;
			cin >> i >> c;
			if (s[i] == c) continue;
			pos[s[i] - 'a'].erase(i);
			pos[c - 'a'].insert(i);
			s[i] = c;
		} 
		else {  
			int l, r;
			cin >> l >> r;
			int ans = 0;
			for (int k = 0; k < 26; k++) {
				auto it = pos[k].lower_bound(l);
				if (it != pos[k].end() && *it <= r) {
					ans++;
				}
			}
			cout << ans << '\n';
		}
	}
	return 0;
}

AT_abc382_f [ABC382F] Falling Bars

题目描述

有一个 \(H\)\(W\) 列的网格。我们用 \((i,j)\) 表示从上往下第 \(i\) 行、从左往右第 \(j\) 列的格子(\(1\leq i\leq H\)\(1\leq j\leq W\))。

在这个网格上放置了 \(N\) 个编号为 \(1\)\(N\) 的横向长条。第 \(i\) 个长条由 \(L_i\)\(1\times 1\) 的方块横向相连组成,其最左端的方块最初位于格子 \((R_i,C_i)\)。也就是说,第 \(i\) 个长条最初占据 \((R_i,C_i),\ (R_i,C_i+1),\ \dots,\ (R_i,C_i+L_i-1)\) 这些格子。保证不存在被两个不同长条同时占据的格子。

当前时刻为 \(t=0\)。对于所有可以表示为 \(t=0.5+n\) 的时刻(\(n\) 为非负整数),依次对 \(i=1,2,\dots,N\) 执行以下操作:

  • 如果第 \(i\) 个长条没有在最底下的一行(即第 \(H\) 行),并且它所占据的每个格子的正下方格子都没有被任何长条占据,则第 \(i\) 个长条整体向下移动一格。也就是说,如果此时第 \(i\) 个长条占据 \((r,C_i),(r,C_i+1),\dots,(r,C_i+L_i-1)\)\(r<H\)),且对于所有 \(j\ (0\leq j\leq L_i-1)\),格子 \((r+1,C_i+j)\) 都没有被其他长条占据,则第 \(i\) 个长条占据的格子变为 \((r+1,C_i),(r+1,C_i+1),\dots,(r+1,C_i+L_i-1)\)
  • 否则,不进行任何操作。

\(t=10^{100}\) 时,第 \(i\) 个长条最终占据的格子为 \((R'_i,C_i),\ (R'_i,C_i+1),\ \dots,\ (R'_i,C_i+L_i-1)\)。请你求出 \(R'_1,R'_2,\dots,R'_N\)

题解

我们注意到一个长条下落的高度跟下面的长条有关,所以我们先按照 \(R_i\) 排序,然后下落时会将 \([C_i,C_i + L_i - 1]\) 的高度都提升 \(1\),也就是区间赋值 + 区间最大值,线段树维护即可。

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 2e5 + 5;
struct line {
	int r, c, l, id;
} arr[M];
struct node {
	int tg, mx;
} Tree[M * 4];
void build(int p, int l, int r) {
	Tree[p].mx = 0;
	Tree[p].tg = -1;
	if (l == r) return;
	int mid = (l + r) >> 1;
	build(p << 1, l, mid);
	build(p << 1 | 1, mid + 1, r);
}
void pushdown(int p) {
	if (Tree[p].tg != -1) {
		int lz = Tree[p].tg;
		Tree[p << 1].tg = lz;
		Tree[p << 1].mx = lz;
		Tree[p << 1 | 1].tg = lz;
		Tree[p << 1 | 1].mx = lz;
		Tree[p].tg = -1;
	}
}
void modify(int p, int l, int r, int ql, int qr, int val) {
	if (ql <= l && r <= qr) {
		Tree[p].mx = val;
		Tree[p].tg = val;
		return;
	}
	pushdown(p);
	int mid = (l + r) >> 1;
	if (ql <= mid) modify(p << 1, l, mid, ql, qr, val);
	if (qr > mid) modify(p << 1 | 1, mid + 1, r, ql, qr, val);
	Tree[p].mx = max(Tree[p << 1].mx, Tree[p << 1 | 1].mx);
}
int qry(int p, int l, int r, int ql, int qr){
	if (ql <= l && r <= qr) return Tree[p].mx;
	pushdown(p);
	int mid = (l + r) >> 1;
	int res = 0;  
	if(ql <= mid) res = max(res, qry(p << 1, l, mid, ql, qr));  
	if(qr > mid) res = max(res, qry(p << 1 | 1, mid + 1, r, ql, qr));  
	return res;
}
int ans[M];
bool cmp(line x, line y) {
	if (x.r == y.r) return x.c < y.c;
	return x.r > y.r;
}
int main() {
	int h, w, n;
	scanf("%d%d%d", &h, &w, &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d%d%d", &arr[i].r, &arr[i].c, &arr[i].l);
		arr[i].id = i;
	}
	sort(arr + 1, arr + 1 + n, cmp);
	build(1, 1, w);
	for (int i = 1; i <= n; i++){
		int mx = qry(1, 1, w, arr[i].c, arr[i].c + arr[i].l - 1);
		ans[arr[i].id] = h - mx;
		modify(1, 1, w, arr[i].c, arr[i].c + arr[i].l - 1, mx + 1);
	}
	for (int i = 1; i <= n; i++) {
		printf("%d\n", ans[i]);
	}
	return 0;
}

AT_arc093_b [ABC092D] Grid Components

题目描述

给定两个整数 \(A\)\(B\)

请输出一个满足以下条件的网格(每个格子被涂成白色或黑色),并按照输出格式输出:

  • 网格的大小为 \(h\)\(w\) 列,\(h\)\(w\) 都不超过 \(100\)
  • 所有白色格子恰好分成 \(A\) 个连通块(关于“连通块”的定义见下方注释)。
  • 所有黑色格子恰好分成 \(B\) 个连通块。

在题目给定的限制条件下,保证至少存在一个解。如果有多个解,输出任意一个均可。

思路

对于每组数据,我们都构造 \(100 \times 100\) 的矩阵。

我们先将上面 \(50\) 行染成白,下面 \(50\) 行染成黑色。然后在白色区域里间隔着把一些单元格变成黑色,在黑色区域里间隔着把一些单元格变成白色。

这里给出一个参考图:

#.#.#.#
.......
#.#....
.......
#######
.#.#.#.
#######
.#.####

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
char mp[105][105];
int main(){
	int n, m;
	scanf("%d%d", &n, &m);
	int k = 100;
	printf("%d %d\n", k, k);
	for(int i = 1; i <= 50; i++){
		for(int j = 1; j <= 100; j++){
			mp[i][j] = '.';
		}
	}
	for(int i = 51; i <= 100; i++){
		for(int j = 1; j <= 100; j++){
			mp[i][j] = '#';
		}
	}
	n--, m--;
	int x = 1, y = 1;
	for(int i = 1; i <= m; i++){
		mp[y][x] = '#';
		if(x < 98) x += 2;
		else x = 1, y += 2;
	}
	x = 1, y = 52;
	for(int i = 1; i <= n; i++){
		mp[y][x] = '.';
		if(x < 98) x += 2;
		else x = 1, y += 2;
	}
	for(int i = 1; i <= 100; i++){
		for(int j = 1; j <= 100; j++){
			printf("%c", mp[i][j]);
		}
		printf("\n");
	}
	return 0;
}

AT_arc112_b [ARC112B] -- - B

题目描述

你持有一个整数 \(B\),并前往整数商店。在整数商店中,他可以通过支付金钱,将手中的整数变为另一个整数。

具体来说,他可以按任意顺序、任意次数购买以下两种服务:

  • 支付 \(1\) 日元,将手中的整数乘以 \(-1\)
  • 支付 \(2\) 日元,将手中的整数减去 \(1\)

请问你在不超过 \(C\) 日元的情况下,最多可以得到多少种不同的整数?

题解

先考虑恰好花费 \(C\) 的时候,能得到的取值方案数。花费为 \(1\) 的操作对拓宽方案数并无多大意义,应重点注意花费为 \(2\)\(1\) 的操作。

最后考虑至多花费 \(C\) 的时候,能得到的取值方案数。这么多方案很难一个个去容斥,思考一下:花费 \(C=k+2\) 能取到的方案数完全包含 \(C=k\) 的方案数,因而我们只需考虑 \(C\)\(C - 1\) 的方案数交集就好了。

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
ll b, c, ans = 0;
signed main() {
	cin.tie(0), cout.tie(0);
	cin >> b >> c;
	if (b == 0) {
		cout << c / 2 + 1 + (c - 1) / 2;
		return 0;
	}
	ll l1 = b - c / 2, r1 = b + (c - 2) / 2;
	ll l2 = -b - (c - 1) / 2, r2 = -b + (c - 1) / 2;
	if (b < 0) {
		swap(l1, l2);
		swap(r1, r2);
	}
	cout << (r1 - l1 + 1) + (r2 - l2 + 1) - (l1 <= r2 ? (r2 - l1 + 1) : 0);
	return 0;
}

AT_arc116_b [ARC116B] Products of Min-Max

题目描述

给定一个长度为 \(N\) 的整数序列 \(A\)\(A\) 的非空子序列 \(B\) 一共有 \(2^N - 1\) 个。对于每一个 \(B\),计算 \(\max(B) \times \min(B)\) 的值,并求这些值的总和。

由于答案可能非常大,请输出其对 \(998244353\) 取模的结果。

题解

很容易想到把 \(A\) 数组排序,然后答案显然为 \(\sum\limits_{i = 1}^{N}\sum\limits_{j = i + 1}^{N} A_i \times A_j \times 2^{j - i -1} + \sum\limits_{i=1}^{N} A_i^2\)。注意到前一项可以化为 \(\sum\limits_{i = 1}^{N} A_i \times \sum\limits_{j = i + 1}^{N} A_j \times 2^{j - i -1}\),然后我们令 \(S_i = 2 \times S_{i - 1} + A_{i - 1}\),可以发现 \(S_i\) 就是 \(\sum\limits_{j = i + 1}^{N} A_j \times 2^{j - i -1}\),于是就成功优化到 \(\mathcal{O}(n \log n)\)

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 2e5 + 5;
const ll mod = 998244353;
ll a[M], s[M];
void sol(){
	int n;
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
	sort(a + 1, a + 1 + n);
	ll ans = 0;
	for(int i = 1; i <= n; i++) ans = (ans + a[i] * a[i] % mod) % mod;
	for(int i = 2; i <= n; i++) s[i] = (s[i - 1] * 2) % mod + a[i - 1];
	for(int i = 1; i <= n; i++) ans = (ans + s[i] * a[i] % mod) % mod;
	printf("%lld", ans);
}
int main(){
	int T = 1;
	while(T--) sol();
	return 0;
}

AT_arc162_b [ARC162B] Insertion Sort 2

题目描述

给定一个 $ (1,2,\ldots,N) $ 的排列 $ P=(P_1,P_2,\ldots,P_N) $。

请判断是否可以通过不超过 \(2\times 10^3\) 次如下操作将 \(P\) 排成升序。如果可以,请给出一种实际的操作方案。

  • 选择满足 \(1\leq i \leq N-1, 0 \leq j \leq N-2\) 的整数 \(i,j\)。将 \(P\) 中的第 \(i\) 项和第 \(i+1\)\((P_i, P_{i+1})\) 拿出,剩下的序列记为 \(Q=(Q_1,Q_2,\ldots,Q_{N-2})\)。然后将 \(P\) 替换为 \((Q_1,\ldots,Q_j, P_i, P_{i+1}, Q_{j+1},\ldots,Q_{N-2})\)

题解

注意到我们可以模拟选择排序,每次从待排序的数据中选择最小的元素和它后面的数字,放到已排序序列的末尾。

还有如果这个数在最后一个怎么办?我们考虑把它拉到 \(N - 2\) 的位置上,这样就解决了。

然后考虑什么时候无解。在只剩最后两个数字的时候,如果 \(a_{n-1} > a_n\) 那么就无解。注意两个数的时候需要特判。

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 2e3 + 5;
int a[M];
int main(){
	int n;
	scanf("%d", &n);
	for(int i = 1; i <= n; i++){
		scanf("%d", &a[i]);
	}
	if(n == 2){
		if(a[1] > a[2]) {
			printf("No");
		}else{
			printf("Yes\n0");
		}
		return 0;
	}
	queue<pair<int, int>>qu;
	for(int i = 1; i <= n - 2; i++){
		int pos;
		for(pos = 1; pos <= n; pos++) if(a[pos] == i) break;
		if(pos == n){
			swap(a[n - 2], a[n - 1]);
			swap(a[n - 1], a[n]);
			qu.push(make_pair(n - 1, n - 3));
			pos = n - 1;
		}
		qu.push(make_pair(pos, i - 1));
		int x = a[pos], y = a[pos + 1];
		for(int j = pos - 1; j >= i; j--) a[j + 2] = a[j];
		a[i] = x; a[i + 1] = y;
	}
	if(a[n - 1] > a[n]){
		printf("No");
		return 0;
	}
	printf("Yes\n%d\n", (int)qu.size());
	while(!qu.empty()){
		auto p = qu.front(); qu.pop();
		printf("%d %d\n", p.first, p.second);
	}
	return 0;
}

AT_arc212_a [ARC212A] Four TSP

题目描述

有一个包含 \(4\) 个顶点(编号为 \(1,2,3,4\))的完全图。

现在你需要为每条边分配权值。每条边的权值应为正整数,且六条边的权值之和恰好等于 \(K\)
更正式地说,你需要选择正整数 \(x_{i,j}\ (1 \leq i < j \leq 4)\),使得 \(\sum_{1 \leq i < j \leq 4}x_{i,j} = K\),并将权值 \(x_{i,j}\) 分配给连接 \(i\)\(j\) 的边。

对于加权图 \(G\),定义 \(f(G)\) 为通过所有顶点的一个环(即经过全部 \(4\) 个顶点的一个环,其实是哈密顿环)上边权之和的最小值。请你计算所有可能的 \(G\)\(f(G)\) 之和,并对 \(998244353\) 取模后输出。

什么是完全图?完全图是指每一对不同的点之间都恰好有一条边的图。

题解

我们注意到环的形式只有三种,且相对的边一定都在环中或都不在环中,所以我们直接枚举相对的两条边的权值和,计算第三个的值,就可以 \(\mathcal{O}(k^2)\) 完成了。

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int mod = 998244353;
ll ans, k;
signed main() {
	cin >> k;
	for (ll x = 2; x <= (k - 4); x++) {
		for (ll y = 2; y + x <= k - 2; y++) {
			ll z = k - x - y;
			ll sum = k - max({x, y, z});
			ll cnt =  sum * (((x - 1) * (y - 1) * (z - 1)) % mod) % mod;
			ans = (ans + cnt) % mod;
		}
	}
	cout << ans;
	return 0;
}

AT_arc219_b [ARC219B] Reverse Permutation

题目描述

给你一个整数 \(N\) 和一个排列 \(P=(P_1,P_2,…,P_N)\),它是 \((1,2,…,N)\) 的一个排列。

对于一个排列 \(Q=(Q_1,Q_2,…,Q_N)\),它也是 \((1,2,…,N)\) 的排列,定义 \(Q^′=(Q_1^′,Q_2^′,…,Q_N^′)\) 是通过下面操作恰好执行一次后能得到的字典序最小的排列:

  • 选择一对整数 \((l,r)\),满足 \(1\le l \le r \le N\),然后将区间 \(Q_l,Q_{l+1},\cdots,Q_r\) 反转。更准确地说,就是用 \((Q_1,Q_2,\cdots,Q_{l−1},Q_r,Q_{r−1},\cdots,Q_l,Q_{r+1},Q_{r+2},\cdots,Q_N)\) 替换 \(Q\)

请你求出满足 \(Q^′=P\) 的排列 \(Q\) 的数量,结果对 \(998244353\) 取模。

你会得到 \(T\) 组测试数据,请依次解答。

题解

观察一下,一次操作等价于说遍历 \(i \in [1,n]\),找到最小的 \(i\) 满足 \(a_i \neq i\),然后把 \(i\) 旋转上去。

发现如何旋转分类都本质不同,也就是假设第一个不等于的是 \(i\),则有 \(n-i\) 种方案。不过要特判满足 \(i\in[1,n],a_i = i\) 的情况。

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const ll mod = 998244353;
const int N = 5e5 + 5;
ll a[N];
void sol() {
	int n;
	scanf("%d", &n);
	for(int i = 1; i <= n; i++) scanf("%lld", &a[i]);
	ll ans = 0;
	for(ll i = 1; i <= n; i++) {
		if(i != a[i]) break;
		if(i == n) {
			ans = (ans + 1) % mod;
		} else {
			ans = (ans + n - i) % mod;
		}
	}
	printf("%lld\n", ans);
}
int main() {
	int T;
	cin >> T;
	while(T--) sol();
	return 0;
}

AT_arc212_b [ARC212B] Stones on Grid

题目描述

有一个 \(N\)\(N\) 列的方格。第 \(i\) 行从上数、第 \(j\) 列从左数的格子被称作 \((i,j)\)

你需要依次进行 \(i=1,2,\ldots,M\) 次如下操作:

  • 操作 \(i\):选择是否在格子 \((x_i, y_i)\) 上放置一颗石子。如果选择放置石子,则需要支付 \(c_i\) 的花费;否则不花费任何费用。

但是,你必须在第 1 次操作中放一颗石子

请判断是否能够实现以下目标,如果可以,请求出最小的总花费。

  • 目标:对于每个 \(i\ (1 \leq i \leq N)\),第 \(i\) 行所放石子的数量恰好等于第 \(i\) 列所放石子的数量。

题解

我们把 \(x_i,y_i\) 的限制变为 \(x_i \to y_i\) 的有向边,权值为 \(c_i\),那么问题就变成必须选 \(x_1 \to y_1\) 这条边,然后求最小环大小。那我们就在建图的时候不加入 \(x_1 \to y_1\) 这条边,然后去求 \(y_1\)\(x_1\) 的最短路,最后加上 \(c_1\) 即可。

Code

#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1e6 + 2;
long long d[MAXN];
struct Node {
	int id;
	long long dis;
	friend bool operator <(Node a, Node b) {
		return a.dis > b.dis;
	}
};
vector<Node>vec[MAXN];
priority_queue<Node>que;
int n, m;
int main() {
	cin >> n >> m;
	memset(d, 0x3f3f, sizeof d);
	int u1 = 0, v1 = 0, w1 = 0;
	for (int i = 1; i <= m; ++i) {
		int u, v, w;
		cin >> u >> v >> w;
		if (i == 1) {
			u1 = u;
			v1 = v;
			w1 = w;
			if (u1 == v1) {
				cout << w1;
				return 0;
			}
		}
		vec[u].push_back((Node) {v, w});
	}
	d[v1] = 0;
	que.push((Node) {v1, 0});
	while (!que.empty()) {
		Node tmp = que.top();
		que.pop();
		int now = tmp.id;
		if (d[now] < tmp.dis)continue;
		for (long signed int i = 0; i < vec[now].size(); ++i) {
			int to = vec[now][i].id;
			if (now == u1 && to == v1)continue;
			if (d[to] > d[now] + vec[now][i].dis) {
				d[to] = d[now] + vec[now][i].dis;
				que.push((Node) {to, d[to]});
			}
		}
	}
	if (d[u1] > 1e18) cout << -1;
	else cout << w1 + d[u1];
	return 0;
}

AT_abc141_d [ABC141D] Powerful Discount Tickets

题目描述

高桥君计划依次购买 \(N\) 件商品,每次购买一件。

\(i\) 件商品的价格为 \(A_i\) 日元。

高桥君手中有 \(M\) 张优惠券。

在购买商品时,可以任意选择使用任意数量的优惠券。

如果在购买价格为 \(X\) 日元的商品时使用了 \(Y\) 张优惠券,则该商品可以以 \(\left\lfloor \frac{X}{2^Y} \right\rfloor\) 日元(向下取整)购买。

请问,至少需要多少钱才能买下所有商品?

题解

?这个难度放 D 题?

预处理每个数用几张优惠券节约的钱,排序。

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
const int M = 3e6 + 5;
ll a[M], sum;
int n, m, cnt;
void init(ll x){
	while(x){
		a[++cnt] = x - (x / 2);
		x /= 2;
	}
}
bool cmp(int x, int y){
	return x > y;
}
int main(){
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= n; i++){
		ll x;
		scanf("%lld", &x);
		init(x), sum += x;
	}
	sort(a + 1, a + 1 + cnt, cmp);
	for(int i = 1; i <= m; i++){
		sum -= a[i];
	}
	printf("%lld", sum);
	return 0;
}

AT_abc458_g [ABC458G] Children Yearn for the Evil Kindergarten

游戏场地里有 \(100^{100}\) 个小朋友,刚开始时他们一个都没拿到奖牌。

小朋友只有两种离场方式:退出 或者 逃脱

游戏共进行 \(N\) 天。第 \(i\)\((1 \le i \le n)\)按下面顺序执行操作:

  • 先收回场地里所有小朋友手上的奖牌,总数记为 \(s\)
  • 然后自由分发 \(s + A_i\) 枚奖牌给场地里的小朋友(如果场地没人,就跳过这步)。
  • 场地里的小朋友中,奖牌数少于 \(B_i\) 的退出,奖牌数不少于 \(B_i\) 的每人扣掉 \(B_i\) 枚奖牌。
  • 场地里的小朋友中,奖牌数不少于 \(C_i\) 的可以选择此刻逃脱,或者继续留在场地。

\(N\) 天结束后,所有还留在场地的小朋友都算作退出。

求最终最多能有多少小朋友成功逃脱。

你需要处理 \(T\) 组测试数据。

题解

妙妙题。

首先,题目答案具有单调性,可以考虑二分答案,假设需要检验最终逃脱人数为 \(m\) 是否合法。

初始有无限多小朋友,但我们只关心 \(m\) 个人是否能够逃跑,其他小朋友第一日不分配奖牌即会自然退出且不带奖牌,于是我们只需管理这 \(m\) 个人。

按题目流程,每天开始时收回所有小朋友手中奖牌,总数为 \(s\),当日可分配总额为 \(s + A_i\),随后经过分配、淘汰、逃脱。

由于每天回收全部奖牌,我们不关心奖牌在谁手上,只关心:

  • 当前“存活”人数 \(K\)(未退出且未逃脱者)。
  • 当前总奖牌池 \(R\)

那么一天的逻辑就是这几个操作:

  1. \(R \leftarrow R + A_i\)(实际就是当日可分配总额)
  2. 可能进行“调整”
  3. 为所有 \(K\) 个存活者每人支付 \(B_i\)\(R \leftarrow R - B_i * K\) ,若不够支付则方案失败。
  4. 选择 \(e\) 人逃脱 \((0 \le e \le K)\),每人额外支付 \(C_i\)\(R \leftarrow R - C_i * e\)\(K \leftarrow K - e\)

目标:\(N\) 天后 \(K = 0\)(所有人都逃脱)。

考虑一个在第 \(j\) 天逃脱的小朋友。他在第 \(j\) 天支付了 \(B_j + C_j\) 枚奖牌逃脱。假如他没有逃脱,而是作为存活者一直留到第 \(i\)\((j < i \le n)\),他对总奖牌池的净贡献(相对于逃脱)为:

\(\delta = C_j−\sum\limits_{k=j+1}^{i−1} B_k= (S_j+C_j)−S_{i−1}\)

其中 \(S_i = \sum\limits_{j = 1}^{i} B_j\)

\(\delta > 0\),我们就可以通过“让他重新加入场地并存活到第 \(i\) 天”来获得 \(\delta\) 枚奖牌。

代码中用优先队列(大根堆)存储已逃脱者,元素为 \((key,num)\),其中 \(key=S_t+C_t\),是逃脱时的累计成本。堆中的元素始终表示“可以在需要时被召回”的已逃脱者。

Code

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
using i128 = __int128;
const int N = 4e5 + 5;
ll a[N], b[N], c[N], S[N];
int n;
bool check(ll m) {
    priority_queue<pair<ll, ll>> pq;
    i128 res = 0;      
    ll cnt = m;        
    for (int i = 1; i <= n; ++i) {
        res += a[i];
        if (cnt == 0) break;
        while (!pq.empty()) {
            auto [key, num] = pq.top();
            if (key > S[i] + c[i]) {
                pq.pop();
                i128 gain = key - S[i] - c[i]; 
                res += (i128)num * gain;
                pq.push({S[i] + c[i], num});
            } else break;
        }
        i128 need = (i128)cnt * b[i] - res;
        if (need > 0) {
            while (need > 0 && !pq.empty()) { // 拉人回来凑钱
                auto [key, num] = pq.top();
                if (key > S[i]) {
                    pq.pop();
                    ll per = key - S[i - 1];
                    ll d = per - b[i];  
                    i128 x = (need + d - 1) / d;
                    if (x >= num) {
                        x = num;
                        res += (i128)num * per;
                        cnt += num;
                        need = (i128)cnt * b[i] - res;
                    } else {
                        ll take = (ll)x;
                        res += (i128)take * per;
                        cnt += take;
                        pq.push({key, num - take}); 
                        need = (i128)cnt * b[i] - res;
                        break; 
                    }
                } else break;
            }
            if (need > 0) return false; 
        }
        res -= (i128)cnt * b[i]; // 给钱存活
        if (c[i] == 0) {
            if (cnt > 0) {
                pq.push({S[i], cnt}); // 逃走了
                cnt = 0;
            }
        } else {
            i128 k = min((i128)cnt, res / c[i]);
            if (k > 0) {
                cnt -= (ll)k;
                res -= k * c[i];
                pq.push({S[i] + c[i], (ll)k});
            }
        }
    }
    return cnt == 0;
}
void solve() {
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i] >> b[i] >> c[i];
    for (int i = 1; i <= n; ++i) S[i] = S[i - 1] + b[i];
    ll l = 0, r = 1e15 + 5;   
    while (l <= r) {
        ll mid = (l + r) >> 1;
        if (check(mid)) l = mid + 1;
        else r = mid - 1;
    }
    cout << r << '\n';
}
int main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    int T; cin >> T;
    while (T--) solve();
    return 0;
}

AT_abc418_d [ABC418D] XNOR Operation

题目描述

一个由 01 组成的非空字符串 \(S\),当且仅当满足以下条件时,被称为美丽字符串:

  • (条件)你可以重复执行以下操作,直到 \(S\) 的长度变为 \(1\),并且最终 \(S\) 中唯一剩下的字符为 1
    1. 选择任意整数 \(i\),满足 \(1 \leq i \leq |S| - 1\)
    2. 按如下方式定义整数 \(x\)
      • 如果 \(S_i = 0\)\(S_{i+1} = 0\),则 \(x = 1\)
      • 如果 \(S_i = 0\)\(S_{i+1} = 1\),则 \(x = 0\)
      • 如果 \(S_i = 1\)\(S_{i+1} = 0\),则 \(x = 0\)
      • 如果 \(S_i = 1\)\(S_{i+1} = 1\),则 \(x = 1\)
    3. 移除 \(S_i\)\(S_{i+1}\),并在它们的位置插入对应的数字 \(x\)
      例如,如果 \(S = 10101\),选择 \(i = 2\),操作后字符串变为 1001

给定一个长度为 \(N\) 的只包含 01 的字符串 \(T\)
请你求出 \(T\) 的子串中有多少个是美丽字符串。即使两个子串内容相同,只要它们取自不同的位置,也要分别计数。

什么是子串?一个字符串 \(S\)子串是通过从 \(S\) 的开头删除零个或多个字符,并从结尾删除零个或多个字符得到的字符串。
例如,10101 的子串,但 11 不是 101 的子串。

题解

我们注意到这种操作叫做同或,跟异或是一个非运算关系,所以是有结合律的。

于是可能成为答案的子串 \(0\) 的个数必定为偶数。

那这样就好办了,前缀和统计到当前节点的 \(0\) 的个数,用一个桶记录一下 \(i\) 前面的前缀中 \(0\) 的个数分别为奇数和偶数的个数,然后每次计算出以 \(i\) 结尾的前缀子串的 0 的个数,取桶中储存的同奇偶的信息即可。

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
ll n, tot, ans, t[2];
string s;
int main() {
	cin >> n >> s;
	t[0]++;
	for (int i = 0; i < n; i++) {
		tot += (s[i] == '0');
		ans += t[tot % 2];
		t[tot % 2]++;
	}
	cout << ans;
	return 0;
}

AT_agc017_a [AGC017A] Biscuits

题目描述

\(N\) 袋饼干。第 \(i\) 袋中有 \(A_i\) 块饼干。

高木君可以从中选择若干袋,将选中的袋中的所有饼干都吃掉。这里可以一个袋子都不选,也可以选所有的袋子。

高木君希望吃掉的饼干总数除以 \(2\) 的余数等于 \(P\)。请你计算有多少种选袋的方法满足这一条件。

题解

注意到,答案只和奇偶有关,而且偶数袋不影响和的奇偶性(加偶数不改变奇偶),所以真正决定奇偶性的,只是选了多少个奇数袋

如果所有袋都是偶数,无论怎么选,总和永远是偶数。

  • \(p = 0\):所有 \(2^N\) 种选法都符合。
  • \(p = 1\):一个符合条件的都没有。

如果有奇数袋,那么在 \(2^N\) 中选择中,恰有一半是偶数,一半是奇数,所以直接输出 \(2^{N-1}\) 即可。

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
ll cnt[2], ans;
int main() {
	int n, p;
	scanf("%d%d", &n, &p);
	for (int i = 1; i <= n; i++) {
		int x;
		scanf("%d", &x);
		cnt[x % 2]++;
	}
	if (cnt[1] == 0) {
		ans = (p == 0) ? (1LL << n) : 0;
	} else {
		ans = (1LL << (n - 1));
	}
	printf("%lld", ans);
	return 0;
}

AT_abc148_e [ABC148E] Double Factorial

题目描述

对于所有大于等于 \(0\) 的整数 \(n\),定义 \(f(n)\) 如下:

  • \(n < 2\) 时,\(f(n) = 1\)
  • \(n \geq 2\) 时,\(f(n) = n \times f(n-2)\)

给定一个整数 \(N\),请你求出 \(f(N)\) 用十进制表示时末尾有多少个连续的 \(0\)

题解

如果 \(N\) 为奇数,答案为 \(0\),因为永远是奇数。

如果 \(N\) 为偶数,显然只有末位为 \(0\) 的数有贡献,他们都有因子 \(5\),于是不断除计数就可以了。

Code

#include<bits/stdc++.h>
using namespace std;
long long n, ans;
int main() {
	scanf("%lld", &n);
	if (n % 2 == 0) {
		n >>= 1;
		while (n) {
			ans += n / 5;
			n /= 5;
		}
		printf("%lld", ans);
	} else {
		printf("0");
	}
	return 0;
}

AT_arc127_a [ARC127A] Leading 1s

题目描述

将整数 \(x\) 用十进制表示时,记其开头连续出现的 \(1\) 的个数为 \(f(x)\)。例如,\(f(1)=1\)\(f(2)=0\)\(f(10)=1\)\(f(11)=2\)\(f(101)=1\)

给定一个整数 \(N\),请计算 \(f(1)+f(2)+\cdots+f(N)\) 的值。

题解

我们考虑开头为 \(n\)\(1\) 时候,可以发生贡献的数。可以发现一个 \(1\) 时,区间为 \([1,2),[10,20),[100,200)\),以此类推。所以我们直接枚举有几个连续的 \(1\),再枚举 \(1\) 之后多少个数位进行统计。

Code

#include<bits/stdc++.h>
using namespace std;
using ll = long long;
ll n, ans;
int main() {
	scanf("%lld", &n);
	ll nw = 1, cnt = 0;
	while(nw <= n){
		ll sp = 1;
		while(nw * sp <= n){
			ll l = nw * sp, r = (nw + 1) * sp;
			cnt += min(r, n + 1) - l;
			sp *= 10;
		}
		nw = (nw * 10) + 1;
	}
	printf("%lld", cnt);
	return 0;
}
posted @ 2026-05-07 15:14  nick_zha  阅读(20)  评论(0)    收藏  举报