构造
CF1833D Flipper - 洛谷
思维 + 分情况构造
多看几遍
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
//#define ll long long
#define vi vector<int>
#define vii vector<vi>
#define pii pair<int, int>
#define arr array<int, 3>
#define per(i, a, b) for(int i = (a); i <= (b); i++)
#define rep(i, a, b) for(int i = (a); i >= (b); i--)
//const int N = 2e5 + 7;
/*
核心目标:通过一次翻转,让前缀尽可能大(第一位最大,第二位次之,第三位再次之)。
通用逻辑(优先处理最大值 n):
步骤 1:找到 n 的位置 r。
步骤 2:特殊情况 1:若 r=1(n 在首位),则将目标改为次大值 n-1,重新找其位置 r。
步骤 3:确定翻转区间右端点 r':若 r≠n,则 r'=r-1;若 r=n(n 在末尾),则 r'=n。
步骤 4:确定翻转区间左端点 l:初始 l=r',从 r'-1 向左遍历,只要 a[i]>a[1],就 l--(纳入更多大数),遇到 a[i]≤a[1] 停止。
步骤 5:翻转区间 [l, r'],等价于输出顺序:r'+ 1~n → r'~l(逆序)→ 1~l-1。
本质:翻转的核心是 “让 n(或次大值)移动到第一位”,同时将其左侧所有比原第一位大的数纳入翻转,保证前缀都是大数。
*/
void solve(){
int n, l = 0, r = 0; cin >> n;
vi a(n + 1);
per(i, 1, n){
cin >> a[i];
if(a[i] == n) r = i;
}
if(r == 1)per(i, 1, n)if(a[i] == n - 1)r = i;
if(r != n)--r;
l = r;
rep(i, r - 1, 1){
if(a[i] > a[1]) --l;
else break;
}
int f = 0;
per(i, r + 1, n)++f, cout << a[i] << " \n"[f == n];
rep(i, r, l)++f, cout << a[i] << " \n"[f == n];
per(i, 1, l - 1)++f, cout << a[i] << " \n"[f == n];
}
signed main() {
ios :: sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T--) solve();
return 0;
}
AT_abc390_f [ABC390F] Double Sum 3 - 洛谷
AT_abc390_f [ABC390F] Double Sum 3 - 洛谷
为什么结束标志是「x∈S 且 x+1∉S」:
连续整数段的本质是 “前后相邻的数都在集合里”,比如 {3,4,5} 是一段,因为 3+1=4∈S、4+1=5∈S;而这段的结束,必然是 “某个数的下一个数不在集合里”——5+1=6∉S,所以 5 就是这段的结束标志。
反过来想:
- 若 x∈S 且 x+1∈S → x 后面还有连续的数,说明 x 是某段的 “中间元素”,不是结束;
- 若 x∈S 且 x+1∉S → x 后面没有连续的数,这段只能到 x 为止,x 就是 “这段的最后一个数”,也就是结束标志;
- 若 x∉S → x 根本不在集合里,自然不可能是任何分段的结束
往前找x 和 x + 1是因为 x 是重复的,x + 1 是连续的,而一个集合中如果有x + 1,则证明x是连续的,不符合x作为结束这一条件
void solve(){
int n, ans = 0; cin >> n;
vi a(n + 1), pos(n + 2), pre(n + 1), nxt(n + 1);
per(i, 1, n)cin >> a[i];
per(i, 1, n){//找到连续的数的前驱的最大下标
pre[i] = max(pos[a[i]], pos[a[i] + 1]);
pos[a[i]] = i;
}
per(i, 1, n + 1) pos[i] = n + 1;//都包含在里面时就不用 + 1了
rep(i, n, 1){//找到连续的数的后驱
nxt[i] = pos[a[i] + 1];
pos[a[i]] = i;
}
per(i, 1, n)ans += (i - pre[i]) * (nxt[i] - i);
cout << ans << endl;
}
D - On AtCoder Conference
构造(破环成链)+ 贡献 + 二分查找
将所有 Ai 排序后,“走到直到他遇到的总人数不少于 C 的位置” 即可转化为 “从某个位置开始向后数 C 个数,此数为 x,找到第一个 >x 的位置”(即把所有位置 x 上的人都算进去)。
所有上一个有人的位置到当前有人的位置(不包括当前位置)这个范围内的点都会对应相同的停止位置,因此可以枚举每个有人的位置作为起点,计算其对应的贡献。
排序并拆环成链后,使用 upper_bound 完成上文中 “找到第一个 >x 的位置” 的操作即可。
细节:破环成链:复制排序后的数组,每个元素加 M(如原数组是 [0,1,2],M=3,则复制后为 [3,4,5]),最终数组 a[1..2n] 覆盖环形的两次遍历,线性遍历即可模拟顺时针绕圈。
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
//#define ll long long
#define vi vector<int>
#define vii vector<vi>
const int inf = 0x3f3f3f3f;
void solve(){
int n, m, c, ans = 0;
cin >> n >> m >> c;
vi a(2 * n + 1);
for(int i = 1; i <= n; ++i)cin >> a[i];
sort(a.begin() + 1, a.begin() + 1 + n);
for(int i = 1; i <= n; ++i) a[i + n] = a[i] + m;
for(int i = 1; i <= n; ++i){
int p = upper_bound(a.begin() + 1, a.end(), a[i + c]) - a.begin() - 1;
/*
i :1 2 3 4 5
a[i]: 0 1 1 1 2
i = 1, c = 3;
找到第一个大于 i + c 人位置的下标的下标即人数 p(5, 即a[5] = 2),那么 p - 1就正好对应 == c 且相同的不落下的人的最后序号(4)
*/
ans += (p - i) * (a[i + 1] - a[i]);
}
cout << ans << endl;
}
signed main() {
ios :: sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T = 1;
//cin >> T;
while (T--) solve();
return 0;
}
CF1851D Prefix Permutation Sums - 洛谷
判断题
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
//const int N = 1e2 + 7;
#define vi vector<int>
void solve(){
int n, d; cin >> n;
vi a(n + 1), b;
map<int, int>mp, vis;
vi p;//同统一储存异常差
for(int i = 1; i < n; ++i){
cin >> a[i];
d = a[i] - a[i - 1];
++mp[d];
if(d > n || mp[d] > 1)p.push_back(d);//记录异常的
vis[d] = 1;
}
//前缀和少一个要么是首或尾丢了一个,要么是中间丢了两个(仔细感悟一下中间的前缀和起到承前启后的作用)
for(int i = 1; i <= n; ++i)
if(!vis[i])p.push_back(i);//记录缺失的
sort(p.begin(), p.end());
bool ok = 0;
if(p.size() == 1 && !vis[p[0]])ok = 1;//缺失的数在首位或末尾
else if(p.size() == 3 && p[0] + p[1] == p[2] && !vis[p[0]] && !vis[p[1]])ok = 1;
cout << (ok? "YES\n" : "NO\n");
}
signed main(){
ios :: sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--)solve();
return 0;
}
1. C. Divine Tree
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
#define pii pair<int, int>
#define vi vector<int>
#define vii vector<vi>
#define lowbit(x) (x & (-x))
const int N = 3e5 + 7, mod = 998244353;
void solve(){
int n, m, f = 1; cin >> n >> m;
if(m < n || m > (n * (n + 1) / 2)){cout << "-1\n"; return ;}
vector<int>q, vis(n + 1);
int sum = 0, mx = n;
for(int i = 1; i <= n && f; ++i){//对于每一位
for(int j = mx; j && f; --j){
if(n - i + j + sum <= m){
//从大大小找,前面一些是sum,前面一个填j,后面填(n - i)个1
q.push_back(j), mx = j - 1;
if(n - i + j + sum == m) f = 0;
vis[j] = 1; sum += j;
break;
}
}
}
for(int i = 1; i <= n; ++i){
if(!vis[i])q.push_back(i);
}
cout << q[0] << endl;
for(int i = 1; i < q.size(); ++i)cout << q[i - 1] << " " << q[i] << endl;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) solve();
return 0;
}
C - ~
-
若当前为上升(Aᵢ > Aᵢ₋₁):
此时当前上升段可与上一个上升段(存储在p中)通过中间的下降段形成有效子数组。每多一步上升,就会新增p个有效子数组(因上一个上升段的每个起点都可与当前上升段的终点形成新子数组),因此ans += p,同时t加 1(延长当前上升段)。 -
若当前为下降(Aᵢ < Aᵢ₋₁):
表示当前上升段结束,将其长度t存入p,并重置t为 0(开始记录下一个上升段)。
如果是连续下降的情况,则不进行记录,但是前面上升的序列依旧要保存作为后面合法子数组的一部分
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
#define pii pair<int, int>
#define vi vector<int>
#define vii vector<vi>
#define lowbit(x) (x & (-x))
const int N = 1e2 + 7, mod = 1e9 + 7, inf = 1e18;
void solve(){
int n, p = 0, t = 0, ans = 0; cin >> n;
vector<int> a(n + 1);
for(int i = 1; i <= n; ++i)cin >> a[i];
for(int i = 2; i <= n; ++i){
if(a[i] > a[i - 1]) ans += p, ++t;//p是之前上升的,t是现在上升的,中间隔了
else if(t)p = t, t = 0;
}
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T = 1;
//cin >> T;
while(T--) solve();
return 0;
}
C. XOR and Triangle
我们可以先按照非退化三角形的定义,列出一个不等式组。
⎧x + (x ⊕ y) > y
⎨x + y> x ⊕ y
⎩y + (x ⊕ y) > x
首先,第二个不等式是一定满足的,因为 y > x,并且 x ⊕ y ≥ 0,所以一定满足。再将第三个不等式移项,得到 x ⊕ y > x − y。
所以,我们对 y 的限制就是 x + y > x ⊕ y > x − y。
先给出结论:x+y≥x⊕y,当且仅当 x and y=0 取等,x − y ≤ x ⊕ y,当且仅当 x and y = y 取等。
证明如下:
考虑异或运算的本质,就是不进位加法 和 不借位减法。所以,当出现两个数的同一位上都为 1 的情况,则 x + y 会因为进位高位比 x ⊕ y 大 1,若不存在这种情况,则 x + y = x ⊕ y。同理,若两个数的同一位不同时为 1 时,x − y 的高位会因为借位比 x ⊕ y 少 1,若不存在同一位不同,则 x − y = x ⊕ y
(所以可得异或的性质 : x + y ≥ x ⊕ y ≥ x − y)
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
#define int long long
#define pii pair<int, int>
#define vi vector<int>
#define vii vector<vi>
#define lowbit(x) (x & (-x))
const int N = 3e5 + 7;
void solve(){
int x; cin >> x;
int y = 1;
while(y <= x)y <<= 1;
y >>= 1;
y --;
int z = x ^ y;
if(x + y == z || x - y == z){cout << "-1\n"; return;}
cout << y << endl;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while(T--) solve();
return 0;
}
C - Equilateral Triangle
对于圆上每个点去找与它相隔的三等分点
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
//#define ll long long
#define int long long
#define pii pair<int, int>
#define fi first
#define se second
#define vi vector<int>
#define lowbit(x) (x & (-x))
const int N = 6e1 + 5, mod = 998244353, inf = 0x3f3f3f3f;
void solve() {
//题目说了是满足条件的整数
int n, m, x, sum = 0, ans = 0; cin >> n >> m;
if(m % 3){cout << "0\n"; return ;}//可以用取模来判断是否整除
vi a(m + 1); a[0] = 1;
int k = m / 3;
for(int i = 1; i < n; ++i){
cin >> x;
sum += x;
sum %= m;
++a[sum];
}
for(int i = 0; i <= k; ++i)
ans += a[i] * a[i + k] * a[i + 2 * k];
cout << ans << endl;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while (T--) solve();
return 0;
}
E. Do You Love Your Hero and His Two-Hit Multi-Target Attacks?
#include<bits/stdc++.h>
using namespace std;
#define endl '\n'
//#define ll long long
//#define int long long
#define pii pair<int, int>
#define fi first
#define se second
#define vi vector<int>
#define lowbit(x) (x & (-x))
const int N = 500, mod = 998244353, inf = 0x3f3f3f3f;
//倒序遍历
void solve() {
int k, num = -1e9, cnt = 0; cin >> k;
vector<pii>a;
if(!k){cout << "0\n"; return;}
for(int i = N; i >= 2; --i){
int t = i * (i - 1) / 2;
while(k >= t){//把一个大数给用完
k -= t; ++cnt;
for(int j = 1; j <= t; ++j)
a.push_back({cnt, ++num});//t是不同的对数,我们要存的是坐标
}
}
cout << a.size() << endl;
for(auto &it : a)cout << it.fi << " " << it.se << endl;
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T = 1;
cin >> T;
while (T--) solve();
return 0;
}
F1. Dyn-scripted Robot (Easy Version)
点击查看解析
我们发现每次碰到边界反转不好维护,于是我们不考虑反转,让它走出边界,再考虑它对应原矩形的哪个点。
显然行和列两个方向是独立的,我们接下来从讨论列的方向上来讨论。
我们先从简单的情况开始,令 R 表示每次遇到边界都反转操作的机器人,B 为遇到边界不反转操作的机器人,当他们即将越出上边界 H ,而下一个操作又是往上走时,R 会反转操作往下走,而 B 会继续往上走,接下来在 R 没有碰到下一个边界之前,我们发现,它们经过的路径是关于上边界 H 成镜像的。
接下来如果 B 继续向上走,走到 2H,再继续往上走,对应的,R 将走到 0,反转为原来的操作,也继续往上走,那么它们接下来的操作将是一样的,而它们的坐标差为 2H。
同理当 B 走到 3H 时,它们的操作又会成镜像,走到 4H 时,它们操作又会相同而坐标差变为 4H,走到 0 以下,−H 以下也是同理。于是我们可以归纳出以下性质:R 的纵坐标 y 与 y 模 2H 意义下同余的坐标是等价的。
横坐标也是同理,x 与 xmod2H 是等价的。
那么我们把矩形扩大到 2W×2H,就可以在这个范围内维护出 B 的运动轨迹。
回到题意,题目要求经过点 (0,0) 的次数,我们发现,刚好当且仅当在 B 也经过 (0,0) 时,R 才经过 (0,0)。
我们可以处理出 B 沿着操作走一次的终止位置 (x,y),并统计每个点经过多少次。令 xi=(i−1)xmod2W,yi=(i−1)ymod2H,那么第 i 轮操作的起始位置就在 (xi,yi),这轮操作走到 (0,0) 的次数实际上就是第一轮操作走到 (2W−xi,2H−yi) 的次数,由于本题 k 较小,直接枚举当前是第几轮即可通过。
如果用 map 维护每个点经过次数的话,时间复杂度为 O((n+k)logn)。
void solve() {
int n; cin >> n;
if (n < 7) { cout << "NO\n"; return; }
cout << (vis[n] ? "YES\n" : "NO\n");
}
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
init(); // 预处理所有有效和
int T;
cin >> T;
while (T--) solve();
return 0;
}
# [AT Bonfire](https://www.luogu.com.cn/problem/AT_abc398_d)
```cpp
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2e5 + 10;
#define vi vector<int>
#define vii vector<vi>
#define pii pair<int, int>
//篝火和高桥移动的方向和烟雾的移动方向是相对的(所以可以把烟雾看作静止不动)
void solve(){
int n, r, c; cin >> n >> r >> c;
string s; cin >> s;
map<pii, int>mp;
int r0 = 0, c0 = 0;
mp[{0, 0}] = 1;
for(int i = 0; i < n; ++i){
if(s[i] == 'N')++r0, ++r;
else if(s[i] == 'S')--r0, --r;
else if(s[i] == 'W')++c0, ++c;
else --c0, --c;
mp[{r0, c0}] = 1;
//高桥要和篝火同时移动,先计算所有篝火路径的话会导致当前高桥位置在未来篝火路径上而被计入答案
cout << mp[{r, c}];
}
cout << endl;
}
signed main(){
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int T = 1;
// cin >> T;
while(T--) solve();
return 0;
}

浙公网安备 33010602011771号