2026-01-14~15 hetao1733837 的刷题笔记
01-14
突然感觉好困😴啊......下午第一节改成物理限时练了......就是啊,我咋这么困?我昨天晚上睡得还行吧......虽然小风帝说话把我从半睡半醒状态拉回来好多次,但是,总睡眠时长应该不低于 7 7 7 小时吧......
LGP3572 [POI 2014] PTA-Little Bird
原题链接:P3572 [POI 2014] PTA-Little Bird
分析
好困😩啊......
感觉挺板的,但是,我似乎并非很会......
那么,还是,我尽量走一个不上升的子序列。设 d p i , j dp_{i,j} dpi,j 表示第 i i i 只鸟,到达位置 j j j 的最小体力,答案即为
∑ i = 1 q d p i , n \sum\limits_{i=1}^{q}{dp_{i,n}} i=1∑qdpi,n
转移从前面转就行,这里就可以顺理成章地套单调队列了,时间复杂度应该是 O ( n q ) O(nq) O(nq) 吧,大概 2.5 e 7 2.5e7 2.5e7,可能我会写一个 #define int long long 加一个常数 2,总体上并非很劣。要不我自己写一下?
然后,这里还有一个,就是我每次加一下就行,没必要开二维。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1000005;
int n, d[N], q, k;
int que[N], head, tail;
int dp[N];
void work(int x){
head = 1;
tail = 1;
que[tail] = 1;
for (int i = 2; i <= n; i++){
while (head <= tail && i - que[head] > x)
head++;
if (d[que[head]] > d[i])
dp[i] = dp[que[head]];
else
dp[i] = dp[que[head]] + 1;
while (head <= tail && (dp[que[tail]] > dp[i] || (dp[que[tail]] == dp[i] && d[que[tail]] <= d[i])))
tail--;
que[++tail] = i;
}
}
signed main(){
scanf("%lld", &n);
for (int i = 1; i <= n; i++){
scanf("%lld", &d[i]);
}
scanf("%lld", &q);
while (q--){
scanf("%lld", &k);
work(k);
printf("%lld\n", dp[n]);
}
}
依旧写出来没调出来,转移有点小问题,还可以继续优化空间。
LGP3522 [POI 2011] TEM-Temperature
原题链接:[POI 2011] TEM-Temperature
分析
我决定先写一道绿题让大脑🧠回归状态......所以,看上一题。
晚上还有生化课😭双竞真的这么累吗?
没有精简版题意吗😭
温度会在 [ north , south ] [\operatorname{north},\operatorname{south}] [north,south] 之间。
暴力枚举是相当好的做法(因为我没看出来单调队列在哪)
其实,双竞不是压力很大,主要是很累,而且竞赛从来不是闹着玩的,就很......算了不说了。
我干嘛不手玩样例?玩出来了,变成了区间覆盖问题......的变种?
我要回班了。
还是不会......看一眼题解发现是 deque 老实了。
bur,那你的 DP 除了常数可能有一点,空间可能好一点基本没优势啊!而且 deque 是个纯纯的贪心构造啊!啥玩意啊......
正解
cpp
#include <bits/stdc++.h>
using namespace std;
int n;
struct node{
int x, y, id;
};
deque<node> q;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
int ans = 0;
for (int i = 1, x, y; i <= n; i++){
cin >> x >> y;
while (!q.empty() && q.front().x > y)
q.pop_front();
if (!q.empty())
ans = max(ans, i - q.front().id + 1);
int idx = i;
while (!q.empty() && q.back().x < x){
idx = q.back().id;
q.pop_back();
}
q.push_back({x, y, idx});
}
cout << ans;
}
AT_abc248_h [ABC248Ex] Beautiful Subsequences
原题链接1:[ABC248Ex] Beautiful Subsequences
原题链接2:Ex - Beautiful Subsequences
分析
方法一
经历近一周时间,我还是不会 aoao把 tag 都给我了,一个 data structure 和一个比较关键的 divide and conquer,啊?
难道,我考虑的是每一个数作为最大和最小值的时候可以构成的合法区间个数?要不开题解吧......
发现板刷云浅交过题解的题是个相当经济的找好题的方法。
这么牛?在点对问题中,我们常考虑分治算法 ,那么,由于这是一个排列,所以 m a x ( P L , ... , P R ) − m i n ( P L , ... , P R ) − R + L ≥ 0 \mathrm{max}(P_L,\ldots,P_R) - \mathrm{min}(P_L,\ldots,P_R) - R+L\ge 0 max(PL,...,PR)−min(PL,...,PR)−R+L≥0,那么,又由于 0 ≤ k ≤ 3 0\le k\le 3 0≤k≤3,所以,维护序列前 4 4 4 大值即可,然后考虑扫描线......我会吗?直接维护,转化为区间加,没了......
听完生化了,这个太考验时间安排了,老师讲的太粗略了,而且也不知道自己会不会,更没有题做,这要是能出成绩反而很奇怪🧐啊......那么,回寝室之后一定要把书认真往后看,要适应看全是字的书,然后生化看难受😫了,看会普动,结合一下,看能不能更好进步,双竞就这样,要吃的苦比我想象💭的更多。但是,我不会害怕!
把这题写✍了。可能小风帝整理笔记是对的。想一下,一天只睡 6 个小时能不能顶住?那么,腾出来除了上课时间,早上到班有一会,课间,大课间(这个是大头,因为吃饭完全可以省略,可以提前买饭之类的),中午的话,来机房......反正得安排好,像明天一下子整出来......好像三节生化课,预习问题很大啊......这个时间从哪来?其实,要是真 12 点再睡,也还行吧......累吗?难说啊......先写题吧。
感觉如果在预习的时候把笔记整了似乎是很对的选择,但是,这个时间是很大的消耗啊......有点困难了......那么,维持原有课程,看要不要把停课时间让给生竞一部分。再说吧。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 140005, M = 5, O = 10;
struct node{
int mn[M], cnt[M], tag;
node (){
mn[1] = mn[2] = mn[3] = mn[4] = 0x3f3f3f3f;
tag = cnt[1] = cnt[2] = cnt[3] = cnt[4] = 0;
}
};
int n, m, k, p[N], s1[N], t1, s2[N], t2;
struct segtree{
node tr[N << 2];
node zero;
int mn[O], cnt[O];
node pushup(node x, node y){
int tp = 0;
node z;
for (int i = 1; i <= 4; i++){
mn[++tp] = x.mn[i];
mn[++tp] = y.mn[i];
}
sort(mn + 1, mn + tp + 1);
tp = unique(mn + 1, mn + tp + 1) - mn - 1;
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i <= 4; i++){
cnt[lower_bound(mn + 1, mn + tp + 1, x.mn[i]) - mn] += x.cnt[i];
cnt[lower_bound(mn + 1, mn + tp + 1, y.mn[i]) - mn] += y.cnt[i];
}
for (int i = 1; i <= 4; i++){
z.mn[i] = mn[i];
z.cnt[i] = cnt[i];
}
return z;
}
void maketag(int p, int val){
tr[p].tag += val;
for (int i = 1; i <= 4; i++)
tr[p].mn[i] += val;
}
void down(int p){
if (!tr[p].tag)
return ;
maketag(p << 1, tr[p].tag);
maketag(p << 1 | 1, tr[p].tag);
tr[p].tag = 0;
}
void modify1(int p, int l, int r, int s){
if (l == r){
tr[p].mn[1] = 0;
tr[p].cnt[1] = 1;
return ;
}
int mid = (l + r) >> 1;
down(p);
if (s <= mid)
modify1(p << 1, l, mid, s);
else
modify1(p << 1 | 1, mid + 1, r, s);
tr[p] = pushup(tr[p << 1], tr[p << 1 | 1]);
}
void modify2(int p, int l, int r, int s, int t, int val){
if (s <= l && r <= t){
maketag(p, val);
return ;
}
int mid = (l + r) >> 1;
down(p);
if (s <= mid)
modify2(p << 1, l, mid, s, t, val);
if (t > mid)
modify2(p << 1 | 1, mid + 1, r, s, t, val);
tr[p] = pushup(tr[p << 1], tr[p << 1 | 1]);
}
node query(int p, int l, int r, int s, int t){
if (s > r || t < l){
return zero;
}
if (s <= l && r <= t){
return tr[p];
}
down(p);
return pushup(tr[p << 1], tr[p << 1 | 1]);
}
}T;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
for (int i = 1; i <= n; i++){
cin >> p[i];
}
int ans = 0;
for (int i = 1; i <= n; i++){
while (t1 && p[s1[t1]] > p[i]){
T.modify2(1, 1, n, s1[t1 - 1] + 1, s1[t1], p[s1[t1]]);
t1--;
}
s1[++t1] = i;
T.modify2(1, 1, n, s1[t1 - 1] + 1, i, -p[i]);
while (t2 && p[s2[t2]] < p[i]){
T.modify2(1, 1, n, s2[t2 - 1] + 1, s2[t2], -p[s2[t2]]);
t2--;
}
s2[++t2] = i;
T.modify2(1, 1, n, s2[t2 - 1] + 1, i, p[i]);
T.modify2(1, 1, n, 1, i, -1);
T.modify1(1, 1, n, i);
node res = T.query(1, 1, n, 1, i);
for (int j = 1; j <= 4; j++){
if (res.mn[j] <= k){
ans += res.cnt[j];
}
}
}
cout << ans;
}
然后,aoao 给出了这道题:aoao20 这真的是题,为的是让我写一下不满足 0 ≤ k ≤ 3 0\le k\le 3 0≤k≤3 的做法。
这个是要套分治的。但是,我很想知道这个数据强不强......aoao 还没传数据......
方法二
得出一个很重要的经验:分治的优势在于,我只需要考虑跨区间的问题,每个被包含的直接递归下去 。
算了,写文化课作业吧......
01-15
呃......一会再写上面那个题,先写《算法竞赛》上的神秘小题吧......
Vjudge 打不开了......那写花花的题单吧。打不开了......
hyw?让我学生物?那,写一个 ZR 暑假题单吧......
LGP4114 Qtree1
原题链接:Qtree1
分析
找到一道适合我体质的萌萌树剖,但是我真的会吗?看 tag 应该能写。
太久不写,忘了......下放边权,好像以前云浅写过类似的一个题解......
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100005;
const int INF = 0xc0c0c0c0c0c0c0c0;
int n, fa[N], son[N], sz[N], de[N], top[N], w[N], dfn[N], rk[N], ncnt, buc[N];
vector<pair<int, int>> e[N];
struct node{
int u, v, w;
};
vector<node> mp;
struct segtree{
int mx[N << 2];
void pushup(int p){
mx[p] = max(mx[p << 1], mx[p << 1 | 1]);
}
void build(int p, int l, int r){
if (l == r) {
mx[p] = w[l];
return ;
}
int mid = (l + r) >> 1;
build(p << 1, l, mid);
build(p << 1 | 1, mid + 1, r);
pushup(p);
}
void modify(int p, int l, int r, int s, int val){
if (l == r) {
mx[p] = val;
return ;
}
int mid = (l + r) >> 1;
if (s <= mid)
modify(p << 1, l, mid, s, val);
else
modify(p << 1 | 1, mid + 1, r, s, val);
pushup(p);
}
int query(int p, int l, int r, int s, int t){
if (s <= l && r <= t) {
return mx[p];
}
int res = INF;
int mid = (l + r) >> 1;
if (s <= mid)
res = max(res, query(p << 1, l, mid, s, t));
if (t > mid)
res = max(res, query(p << 1 | 1, mid + 1, r, s, t));
return res;
}
} T;
void dfs1(int u, int pa){
fa[u] = pa;
de[u] = de[pa] + 1;
sz[u] = 1;
for (auto tmp : e[u]){
int v = tmp.first;
if (v == pa){
buc[u] = tmp.second;
continue;
}
dfs1(v, u);
sz[u] += sz[v];
if (sz[v] > sz[son[u]]){
son[u] = v;
}
}
}
void dfs2(int u, int tp){
top[u] = tp;
dfn[u] = ++ncnt;
rk[ncnt] = u;
w[ncnt] = buc[u];
if (son[u] == 0)
return;
dfs2(son[u], tp);
for (auto tmp : e[u]){
int v = tmp.first;
if (v == fa[u] || v == son[u])
continue;
dfs2(v, v);
}
}
int query_mx(int u, int v){
int res = INF;
while (top[u] != top[v]){
if (de[top[u]] < de[top[v]])
swap(u, v);
res = max(res, T.query(1, 1, n, dfn[top[u]], dfn[u]));
u = fa[top[u]];
}
if (u == v)
return res;
if (de[u] > de[v])
swap(u, v);
res = max(res, T.query(1, 1, n, dfn[u] + 1, dfn[v]));
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
memset(son, 0, sizeof(son));
for (int i = 1; i < n; i++){
int u, v, w_val;
cin >> u >> v >> w_val;
e[u].push_back({v, w_val});
e[v].push_back({u, w_val});
mp.push_back({u, v, w_val});
}
dfs1(1, 0);
dfs2(1, 1);
T.build(1, 1, n);
while (true){
string s;
cin >> s;
if (s == "QUERY"){
int a, b;
cin >> a >> b;
if (a == b){
cout << 0 << '\n';
}
else{
cout << query_mx(a, b) << '\n';
}
}
else if (s == "CHANGE"){
int x, t;
cin >> x >> t;
x--;
if (de[mp[x].u] > de[mp[x].v])
x = mp[x].u;
else
x = mp[x].v;
T.modify(1, 1, n, dfn[x], t);
}
else if (s == "DONE"){
break;
}
}
return 0;
}
CF585E Present for Vitalik the Philatelist
原题链接1:Present for Vitalik the Philatelist
原题链接2:E. Present for Vitalik the Philatelist
分析
把这题从之前的某个刷题笔记里提出来了......也是花花的题单......
注意,他只买一张......这是很不错的东西。那似乎......我感觉有了大量的头猪,但是,分解质因数是不可能的,大概要 O ( n a i ) O(n\sqrt{a_i}) O(nai ),因为时间爆炸了......
注意到 a i a_i ai 似乎很大,所以,我们似乎更倾向与使复杂度只与 n n n 有关......而且还是 O ( n l o g n ) O(nlogn) O(nlogn),所以这是个紫题......居然开了 5s......那我的 O ( ∑ a i ) O(\sum{\sqrt{a_i}}) O(∑ai ) 似乎有点希望啊......
没希望了,正解复杂度是 O ( n + V l o g l o g V ) O(n+VloglogV) O(n+VloglogV),其中 V V V 应该是值域。那是筛了一下啊......然后枚举每一次购买的邮票,完全不合理啊!我需要 O ( 1 ) O(1) O(1) 知道与当前数互质的数的个数,还需要 O ( 1 ) O(1) O(1) 知道剩下的选法......后面那个组合数都有点吃力啊......

......那还玩啥?
那么,莫反启动!先学数论分块和狄利克雷卷积。
UVA11526 H(n)
原题链接:H(n)
分析
没啥可分析的吧。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
int T;
int n;
int calc(int x){
int res = 0, l = 1, r;
while (l <= x){
r = (n / (n / l));
res += (r - l + 1) * (n / l);
l = r + 1;
}
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while (T--){
cin >> n;
cout << calc(n) << '\n';
}
}
LGP2261 [CQOI2007] 余数求和
原题链接:[CQOI2007] 余数求和
分析
刨除一部分直接是 k k k 的,剩下的似乎还是一个 ∑ ⌊ k i ⌋ \sum{\left\lfloor\frac{k}{i}\right\rfloor} ∑⌊ik⌋,转化为 ∑ i = 1 k { k − ⌊ k i ⌋ × k } \sum\limits_{i=1}^{k}{\{k-\left\lfloor\frac{k}{i}\right\rfloor\times k\}} i=1∑k{k−⌊ik⌋×k},化简为 k 2 − k × ∑ i = 1 k ⌊ k i ⌋ k^2-k\times\sum\limits_{i=1}^{k}{\left\lfloor\frac{k}{i}\right\rfloor} k2−k×i=1∑k⌊ik⌋。
那不是爽完了?开写!
但是,有一个小问题,这是 n ≥ k n\ge k n≥k 的情况,要是 n < k n<k n<k 怎么办?改一下不就行了?二者取 min \min min,算 ∑ ⌊ k i ⌋ \sum{\left\lfloor\frac{k}{i}\right\rfloor} ∑⌊ik⌋
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
int n, k;
int calc(int x){
int res = 0, l = 1, r;
while (l <= x){
r = min(x, k / (k / l));
res += (k / l) * (l + r) * (r - l + 1) / 2;
l = r + 1;
}
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> k;
int ans = n * k - calc(min(n, k));
cout << ans;
}
我得去跑操了。
发现有个题单里的所有东西我都会,而且是去年十月份 CSP-S \operatorname{CSP-S} CSP-S 前留下的模拟赛原题,我觉得可以写了。