刷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\)。
当高桥君的牙齿满足以下两个条件时,称为「良好咬合」:
- 存在一个整数 \(H\),使得对于所有 \(1 \leq i \leq N\),有 \(U_i + D_i = H\)。
- 对于所有 \(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,对于每个可以放的 x(mask 中为 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 i c,表示将 \(S\) 的第 \(i\) 个字符改为 \(c\); - 格式为
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\)。
那么一天的逻辑就是这几个操作:
- \(R \leftarrow R + A_i\)(实际就是当日可分配总额)
- 可能进行“调整”
- 为所有 \(K\) 个存活者每人支付 \(B_i\),\(R \leftarrow R - B_i * K\) ,若不够支付则方案失败。
- 选择 \(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
题目描述
一个由 0 和 1 组成的非空字符串 \(S\),当且仅当满足以下条件时,被称为美丽字符串:
- (条件)你可以重复执行以下操作,直到 \(S\) 的长度变为 \(1\),并且最终 \(S\) 中唯一剩下的字符为
1。- 选择任意整数 \(i\),满足 \(1 \leq i \leq |S| - 1\)。
- 按如下方式定义整数 \(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\)。
- 移除 \(S_i\) 和 \(S_{i+1}\),并在它们的位置插入对应的数字 \(x\)。
例如,如果 \(S = 10101\),选择 \(i = 2\),操作后字符串变为1001。
给定一个长度为 \(N\) 的只包含 0 和 1 的字符串 \(T\)。
请你求出 \(T\) 的子串中有多少个是美丽字符串。即使两个子串内容相同,只要它们取自不同的位置,也要分别计数。
什么是子串?一个字符串 \(S\) 的子串是通过从 \(S\) 的开头删除零个或多个字符,并从结尾删除零个或多个字符得到的字符串。
例如,10 是 101 的子串,但 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;
}

浙公网安备 33010602011771号