A
就是b - a
#include <bits/stdc++.h>
#define int long long
using namespace std;
void solve()
{
int a, b;
cin >> a >> b;
cout << b - a << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
cin >> T;
while(T -- )
{
solve();
}
}
B
简单模拟, 逆序输出即可
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100010;
int stk[N];
void solve()
{
int n;
cin >> n;
for(int i = 1; i <= n; i ++ )
{
string temp;
cin >> temp;
stk[i] = temp.find("#");
}
for(int i = n; i >= 1; i -- )
{
cout << stk[i] + 1 << " ";
}
cout << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
cin >> T;
while(T -- )
{
solve();
}
}
C
简单模拟
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100010;
int stk[N];
void solve()
{
int x, y, k;
cin >> x >> y >> k;
int ansx = (x + k - 1) / k, ansy = (y + k - 1) / k;
int ans = 0;
if(ansx <= ansy)
{
ans = ansy * 2;
}
else
{
ans = ansx * 2 - 1;
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
cin >> T;
while(T -- )
{
solve();
}
}
D
看似复杂, 实则简单模拟
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200010;
int n;
int d[N][2];
void init()
{
for(int i = 0; i <= n; i ++ )
{
d[i][0] = 0;
d[i][1] = 0;
}
}
void solve()
{
cin >> n;
init();
for(int i = 1; i <= n; i ++ )
{
int x, y;
cin >> x >> y;
d[x][y] ++;
}
int ans = 0;
for(int i = 0; i <= n; i ++ )
{
if(i != 0 && d[i][0] && d[i - 1][1] && d[i + 1][1])
{
ans += min({d[i][0], d[i - 1][1], d[i + 1][1]});
}
if(i != 0 && d[i][1] && d[i - 1][0] && d[i + 1][0])
{
ans += min({d[i][1], d[i - 1][0], d[i + 1][0]});
}
if(d[i][0] && d[i][1])
{
ans += n - d[i][0] - d[i][1];
}
}
cout << ans << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
cin >> T;
while(T -- )
{
solve();
}
}
E
可以分为大于 0 和小于 0 两部分, 答案是离 0 最近的那个数, 二分出最小的正数或负数对应的 i 即可
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200010;
int n, k;
int ss(int k, int i)
{
return (2 * k + i - 1) * i / 2 - (2 * k + n + i - 1) * (n - i) / 2;
}
bool check(int x)
{
return ss(k, x) <= 0;
}
int b_search(int l, int r)
{
while(l < r)
{
int mid = l + r + 1 >> 1;
if(check(mid)) l = mid;
else r = mid - 1;
}
return l;
}
void solve()
{
cin >> n >> k;
cout << min(abs(ss(k, temp)), abs(ss(k, temp + 1))) << endl;
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
cin >> T;
while(T -- )
{
solve();
}
}
F
前缀和, 预处理
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200010;
int n, q, all = 0;
int a[N];
int b[N];
int solve2(int l, int r)
{
int ll = (l + n - 1) / n - 1;
l %= n;
if(l == 0) l = n;
int rr = (r + n - 1) / n - 1;
r %= n;
if(r == 0) r = n;
int res = (rr - ll - 1) * b[n];
res += b[ll + n] - b[ll + l - 1];
res += b[rr + r] - b[rr];
return res;
}
void solve()
{
cin >> n >> q;
for(int i = 1; i <= n; i ++ )
{
cin >> a[i];
b[i] = b[i - 1] + a[i];
}
for(int i = n + 1; i <= 2 * n; i ++ )
{
b[i] = b[i - 1] + a[i - n];
}
for(int i = 1; i <= q; i ++ )
{
int l, r;
cin >> l >> r;
cout << solve2(l, r) << endl;
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
cin >> T;
while(T -- )
{
solve();
}
}
G1
滑动窗口
这里有一个非常好用的技巧, 当我们需要找一个连续的加一上升序列时, 我们在输入时可以输入a[i]后减 i, 然后找相同的数就是连续的加一上升序列
一开始没有想到用滑动窗口, 但是发现超时, 所以用滑动窗口提前处理好, 这样不需要每一次都求一遍
rbegin() 是一个迭代器, 指向最后一个数, rend() 与之相反, 指向第一个数
本题中的 multiset 与 set 的不同在于其中可以有多个相同的数
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200010;
int n, k, q;
int a[N], b[N];
void solve()
{
map<int, int> mp;
multiset<int> S;
cin >> n >> k >> q;
for(int i = 1; i <= n; i ++ )
{
cin >> a[i];
a[i] -= i;
}
for(int i = 1; i <= k; i ++ )
{
if(mp.find(a[i]) != mp.end()) S.erase(S.find(mp[a[i]]));
mp[a[i]] ++;
S.insert(mp[a[i]]);
b[i] = k - *S.rbegin();
}
for(int i = k + 1; i <= n; i ++ )
{
S.erase(S.find(mp[a[i - k]]));
mp[a[i - k]] --;
S.insert(mp[a[i - k]]);
if(mp.find(a[i]) != mp.end()) S.erase(S.find(mp[a[i]]));
mp[a[i]] ++;
S.insert(mp[a[i]]);
b[i] = k - *S.rbegin();
}
for(int i = 1; i <= q; i ++ )
{
int l, r;
cin >> l >> r;
cout << b[r] << endl;
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int T = 1;
cin >> T;
while(T -- )
{
solve();
}
}
G2
这道题目的内容相对来讲比较复杂, 我会在这里尽可能详细的解释代码及思路, 以便我自己梳理清楚思路
原题链接: G2. Yunli‘s Subarray Queries (hard version)
原题目
题目解析:
这道题给我们一个长度为 n 的数组, q 次询问, 让我们在 l 到 r 的范围内构建一个长度为 k 的连续子数组, 问分别以 l + k - 1 到 r 为连续子数组的结尾, 求需要的操作次数之和
与 easy 版不同的是, 这道题里每个询问都是上道题里一段答案之和, 因此本能的想用一个简单的求和, 然后中间不断取最小值
但是会超时
那么如何去优化这一问题?
我们发现, 这道题要求我们求一段值, 但是这段的值会随着左右区间的不同改变, 将其按左区间从大到小排序, 每次修改一段, 那么在这种情况下, 就能解决左面更小的值让后面的值变小的问题, 而同时修改一段并求一段之和, 这可以用线段树在很短的时间内解决
代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200010;
typedef long long LL;
int a[N], l[N], r[N], f[N], ans[N], p[N], stk[N];
struct Node
{
int l, r, tag;
int sum;
}tr[N * 4];
void build(int u, int l, int r)
{
tr[u] = {l, r, -1};
if(l != r)
{
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
}
void pushup(int u, int c)
{
tr[u].sum = (tr[u].r - tr[u].l + 1) * c;
tr[u].tag = c;
}
void pushdown(int u)
{
if(tr[u].tag == -1)
{
return;
}
pushup(u << 1, tr[u].tag);
pushup(u << 1 | 1, tr[u].tag);
tr[u].tag = -1;
}
void modify(int u, int l, int r, int c)
{
if(tr[u].l >= l && tr[u].r <= r)
{
pushup(u, c);
}
else
{
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(l <= mid) modify(u << 1, l, r, c);
if(r >= mid + 1) modify(u << 1 | 1, l, r, c);
tr[u].sum = tr[u << 1].sum + tr[u << 1 | 1].sum;
}
}
int query(int u, int l, int r)
{
if(l <= tr[u].l && r >= tr[u].r)
{
return tr[u].sum;
}
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if(r <= mid) return query(u << 1, l, r);
if(l >= mid + 1) return query(u << 1 | 1, l, r);
return query(u << 1, l, r) + query(u << 1 | 1, l, r);
}
void solve()
{
int n, k, q;
map<int, int> mp;
multiset<int> s;
cin >> n >> k >> q;
for(int i = 1; i <= n; i ++ )
{
cin >> a[i];
a[i] -= i;
s.insert(0);
}
for(int i = 1; i <= n; i ++ )
{
s.erase(s.find(mp[a[i]]));
mp[a[i]] ++;
s.insert(mp[a[i]]);
if(i - k > 0)
{
s.erase(s.find(mp[a[i - k]]));
s.insert(-- mp[a[i - k]]);
}
if(i >= k)
{
f[i - k + 1] = k - *s.rbegin();
}
}
for(int i = 1; i <= q; i ++ )
{
cin >> l[i] >> r[i];
p[i] = i;
}
sort(p + 1, p + q + 1, [&] (int a, int b) {
return l[a] > l[b];
});
build(1, 1, n - k + 1);
int tp = 0;
stk[ ++ tp] = n - k + 2;
f[n - k + 2] = -1;
for(int i = 1, j = n - k + 1; i <= q; i ++ )
{
while(j >= l[p[i]])
{
while(f[stk[tp]] > f[j])
{
tp --;
}
modify(1, j, stk[tp] - 1, f[j]);
stk[++ tp] = j --;
}
ans[p[i]] = query(1, l[p[i]], r[p[i]] - k + 1);
}
for(int i = 1; i <= q; i ++ )
{
cout << ans[i] << "\n";
}
}
signed main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr);
int T = 1;
cin >> T;
while(T -- )
{
solve();
}
}
注释
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 2e5 + 5;
int a[N], f[N], l[N], r[N], p[N];
struct Node
{
int l, r, tag;
LL s;
} tr[N * 4];
int stk[N];
LL ans[N];
// 创建线段树, 模板
void build(int u, int l, int r)
{
// 当前这一节点
tr[u] = {l, r, -1};
// 如果还可以再细分
if (l != r)
{
int mid = l + r >> 1;
build(u << 1, l, mid);
build(u << 1 | 1, mid + 1, r);
}
}
void pushup(int u, int c)
{
// 区间长度 * 值
tr[u].s = (tr[u].r - tr[u].l + 1ll) * c;
tr[u].tag = c;
}
void pushdown(int u)
{
// tag == -1, 代表没改过, 不用管
if (tr[u].tag == -1) return;
// 否则把 tag 传下去
pushup(u << 1, tr[u].tag);
pushup(u << 1 | 1, tr[u].tag);
// 当前就没改过了
tr[u].tag = -1;
}
//将一个区间里的每个值修改为 c
void modify(int u, int l, int r, int c)
{
// 如果在范围内, 就直接算一下当前这段的和为 c * 值个数
if (tr[u].l >= l && tr[u].r <= r)
{
pushup(u, c);
}
else {
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
if (l <= mid) modify(u << 1, l, r, c);
if (r >= mid + 1) modify(u << 1 | 1, l, r, c);
tr[u].s = tr[u << 1].s + tr[u << 1 | 1].s;
}
}
LL query(int u, int l, int r)
{
// 如果查询的区间就包裹了当前的node, 那么直接返回即可
if (tr[u].l >= l && tr[u].r <= r)
return tr[u].s;
// 先 pushdown 下一层
pushdown(u);
int mid = tr[u].l + tr[u].r >> 1;
// 如果查询的只有左半部分
if (r <= mid) return query(u << 1, l, r);
// 如果查询的只有右半部分
if (l >= mid + 1) return query(u << 1 | 1, l, r);
// 查询的不单独在一侧
return query(u << 1, l, r) + query(u << 1 | 1, l, r);
}
void solve() {
int n, m, k;
cin >> n >> k >> m;
// 读入a[i] - i
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[i] -= i;
}
map<int, int> mp;
multiset<int> st;
// 插入 n 个 0 以便后续 erase
for (int i = 0; i < n; i++) {
st.insert(0);
}
for (int i = 1; i <= n; i++) {
// 删掉原先当前这个数有几个
st.erase(st.find(mp[a[i]]));
// 插入现在这个数有几个
st.insert(++mp[a[i]]);
// 如果够 k 个, 就要删掉窗口左面的, 维持窗口的数量为 k 个
if (i - k > 0) {
st.erase(st.find(mp[a[i - k]]));
st.insert(--mp[a[i - k]]);
}
// 如果 i >= k, 这时候就知道 k 个数里最少改几个数能行了
if (i >= k) f[i - k + 1] = k - *st.rbegin();
}
// 读入要查询的数
for (int i = 1; i <= m; i++) {
cin >> l[i] >> r[i];
// 离散化
p[i] = i;
}
// 将 p 数组按照查询的左区间从大到小去排序
sort(p + 1, p + m + 1, [&](int i, int j) {
return l[i] > l[j];
});
// 创建线段树
build(1, 1, n - k + 1);
// tp = 0, 作为记录栈顶, 这里手动模拟了一个栈
int tp = 0;
// 栈一开始存放 n - k + 2
stk[ ++ tp] = n - k + 2;
// 栈对应的位置存一个 -1
f[n - k + 2] = -1;
// 一共 m 次询问
for (int i = 1, j = n - k + 1; i <= m; i++)
{
// 当 j 大于等于 查询的左区间
while (j >= l[p[i]])
{
// 拿最小的答案
while (f[stk[tp]] > f[j])
{
tp--;
}
// 用f[j]修改一片区域的值, 因此需要懒标记
modify(1, j, stk[tp] - 1, f[j]);
// 栈中存入 j, j --
stk[ ++ tp] = j --;
}
// 存答案, 由于离散化, 将答案存到了对应的位置上去, query 是线段树的查询, 这里查询的正如题意, 求取总和
ans[p[i]] = query(1, l[p[i]], r[p[i]] - k + 1);
}
for (int i = 1; i <= m; i++) {
cout << ans[i] << '\n';
}
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
solve();
}
return 0;
}