202411 题目合集(2)
P10217
- 无论是最优化问题还是计数问题,如果没有想法->考虑判定。
- 答案能否为 ?
- 这个 很容易让人想到向量。同时 是一个周期一样的东西。记给定的 个分别是向量 ,。那么对于 仅仅考虑这些的位移就是 。要求能够通过加上一个 使得 。其中 为给定的向量。所以 是可以根据 唯一确定的。考虑这个 能否通过 次,每次横纵上的位移绝对值之和不超过 。也就是说对于 只需要 即可。我们得到了 的判定方法。
- 考虑优化。上面的式子根据 的剩余系分类。设 。问题变成对于每一个 求出最小的 使得 中 。以下的 均是合成的向量的坐标,不是读入的值。
- 真是讨厌的绝对值!但是由于这里绝对值就是这个式子可能的最大的,所以可以直接暴力去掉绝对值,列出关于 的四个不等式,求出 check(方法见上)即可。
#include <bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
const int N = 1e6;
struct line {
ll x, y;
} Q[N + 10];
il ll Abs(ll x) {
return ((x > 0) ? (x) : (-x));
}
int n;
ll k, edx, edy;
il bool check(ll p) {
if(!p) {
if(!edx && !edy) return 1;
else return 0;
}
if(p < 0) return 0;
ll wz = (p - 1) / n, wp = (p - 1) % n;
line tmp = (line){Q[n - 1].x * wz + Q[wp].x, Q[n - 1].y * wz + Q[wp].y};
// cout << tmp.x << ' ' << tmp.y << endl;
ll dist = Abs(tmp.x - edx) + Abs(tmp.y - edy);
return (dist <= p * k);
}
il ll ceildiv(ll n, ll m) {
if(n * m < 0) {
n = Abs(n), m = Abs(m);
return -(n / m);
}
else {
n = Abs(n), m = Abs(m);
return n / m + ((n % m == 0) ? (0) : (1));
}
}
il ll floordiv(ll n, ll m) {
if(n * m < 0) {
n = Abs(n), m = Abs(m);
return -ceildiv(n, m);
}
else {
n = Abs(n), m = Abs(m);
return n / m;
}
}
vector <ll> vec;
const int D[2] = {1, -1};
int cnt = 0;
il ll calc(int p) {
ll xz = Q[n - 1].x, yz = Q[n - 1].y;
ll xr = edx - Q[p].x, yr = edy - Q[p].y;
vec.clear();
for(int a = 0; a < 2; a++) {
for(int b = 0; b < 2; b++) {
ll faca = D[a] * xz + D[b] * yz - n * k;
ll facb = k * (p + 1) + D[a] * xr + D[b] * yr;
if(!faca) continue;
vec.push_back(Abs(ceildiv(facb, faca)) * n + p + 1);
}
}
sort(vec.begin(), vec.end());
for(int i = 0; i < vec.size(); i++)
if(check(vec[i])) return vec[i];
return -1;
}
void init() {
cin >> n >> k >> edx >> edy;
cin >> Q[0].x >> Q[0].y;
for(int i = 1; i < n; i++) {
ll xx, yy; cin >> xx >> yy;
Q[i] = (line){Q[i - 1].x + xx, Q[i - 1].y + yy};
}
if(!edx && !edy) {
cout << 0 << endl;
return ;
}
ll minn = -1;
//// cout << calc(0) << endl;
// cout << check(399999999) << endl;
// cout << check(399999998) << endl;
for(int i = 0; i < n; i++) {
// cout << i << endl;
ll rest = calc(i);
if(rest == -1) continue;
else {
if(minn == -1) minn = rest;
else minn = min(minn, rest);
}
}
cout << minn << '\n';
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T; cin >> T;
while(T--) init();
}
总结
- 可以利用向量来刻画 分别相加!
- 对于绝对值和的小于等于,可以把它暴力拆成 个不带绝对值的式子。
P1502
发现不会扫描线典题耶( •̀ ω •́ ),糖丸了
- 直接考虑似乎不太行,于是考虑每一颗星星对答案的贡献。
- 先特别考虑一下这个“在边框上不算在内”的条件。把最下面一行的星星放到框上,那么要求最上面一行的 。横向的
- 我们可以利用右上角唯一确定一个矩形,同时也可以据此将每颗星星可能出现在的矩形的范围划分出来:对于星星 ,能够圈住它的右上角在 到 构成的矩形内。
- 我们拿这个右上角代表整个矩形,那么问题变成了:求出一个重叠贡献最大的区域。
- 这个时候就可以扫描线了!令 上有一条 的线,边权为 , 上有一条 的线,边权为 。做普普通通的扫描线即可。
- 这道题的线段树维护的元素有两种形式:一种是维护 一样的线段的权值。在这种情况下所离散化的点就是 ,每次的编号也都要 。而另一种是单独维护各个点:,所以这个时候离散化下的点就是 ,线段树编号不需要 。这是一个需要注意的细节!我小时候应该是抄题解写的,非常不厚道。
#include <bits/stdc++.h>
#define il inline
using namespace std;
const int N = 1e5;
struct seg {
int l, r, x, val;
} Q[N * 2 + 10];
int cmax[N * 4 + 10], tag[N * 4 + 10];
il int ls(int x) {
return 2 * x;
}
il int rs(int x) {
return 2 * x + 1;
}
il void push_up(int x) {
cmax[x] = max(cmax[ls(x)], cmax[rs(x)]);
}
il void push_tag(int now, int val) {
cmax[now] += val;
tag[now] += val;
}
il void push_down(int now) {
if(tag[now] != 0) {
push_tag(ls(now), tag[now]);
push_tag(rs(now), tag[now]);
tag[now] = 0;
}
}
void upd(int ql, int qr, int s, int t, int now, int val) {
if(ql <= s && t <= qr) {
push_tag(now, val);
return ;
}
int mid = (s + t) >> 1;
push_down(now);
if(ql <= mid) upd(ql, qr, s, mid, ls(now), val);
if(qr > mid) upd(ql, qr, mid + 1, t, rs(now), val);
push_up(now);
}
int qry(int ql, int qr, int s, int t, int now) {
if(ql <= s && t <= qr) return cmax[now];
int mid = (s + t) >> 1, maxn = 0;
push_down(now);
if(ql <= mid) maxn = max(maxn, qry(ql, qr, s, mid, ls(now)));
if(qr > mid) maxn = max(maxn, qry(ql, qr, mid + 1, t, rs(now)));
return maxn;
}
bool cmp(seg &x, seg &y) {
if(x.x == y.x) return x.val > y.val;
else return x.x < y.x;
}
int pos[N * 2 + 10];
int n, w, h;
void init() {
memset(cmax, 0, sizeof(cmax));
memset(tag, 0, sizeof(tag));
cin >> n >> w >> h;
for(int i = 1, x, y, val; i <= n; i++) {
cin >> x >> y >> val;
Q[2 * i - 1] = (seg){x, x + w, y, val};
Q[2 * i] = (seg){x, x + w, y + h - 1, -val};
pos[2 * i - 1] = x, pos[2 * i] = x + w;
}
sort(Q + 1, Q + 2 * n + 1, cmp);
sort(pos + 1, pos + 2 * n + 1);
int ans = 0;
int len = unique(pos + 1, pos + 2 * n + 1) - pos - 1;
for(int i = 1; i <= 2 * n; i++) {
Q[i].l = lower_bound(pos + 1, pos + len + 1, Q[i].l) - pos;
Q[i].r = lower_bound(pos + 1, pos + len + 1, Q[i].r) - pos;
upd(Q[i].l, Q[i].r - 1, 1, len, 1, Q[i].val);
ans = max(ans, cmax[1]);
}
cout << ans << endl;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
int T; cin >> T;
while(T--) init();
}
P9190
这题没做出来还是太唐诗了吧 TAT
暴力 dp: 代表能否让前 个文本串匹配到模式串第 位(模式串看成是 bessie 反复重合,因为它没有 border,这样是可行的),发现 只有最后对 6 取余不同的 6 个才有用,所以设 代表目前匹配到 bessie 的第 位出现了多少 bessie,再带上一个最小删除权值即可。因为它是满足最优子结构性质的。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5, inf = 2e8;
struct node {
int len, val;
} f[N + 10][10];
int a[N + 10], n;
string str;
string b = "#bessie";
int main() {
cin >> str; str = "#" + str;
n = str.size() - 1;
for(int i = 1; i <= n; i++)
cin >> a[i];
f[0][0] = (node){0, 0};
for(int i = 1; i <= 6; i++)
f[0][i] = (node){-inf, inf};
for(int i = 1; i <= n; i++) {
f[i][0] = (node){f[i - 1][0].len, f[i - 1][0].val};
for(int j = 1; j <= 6; j++) {
f[i][j] = (node){f[i - 1][j].len, f[i - 1][j].val + a[i]};
if(str[i] == b[j]) {
if(f[i - 1][j - 1].len < f[i][j].len) continue;
else if(f[i - 1][j - 1].len == f[i][j].len) f[i][j].val = min(f[i][j].val, f[i - 1][j - 1].val);
else f[i][j] = (node){f[i - 1][j - 1].len, f[i - 1][j - 1].val};
}
}
if(f[i][6].len + 1 > f[i][0].len)
f[i][0] = (node){f[i][6].len + 1, f[i][6].val};
else if(f[i][6].len + 1 == f[i][0].len)
f[i][0].val = min(f[i][6].val, f[i][0].val);
// for(int j = 0; j <= 6; j++)
// cout << f[i][j].len << ' ' << f[i][j].val << endl;
//
// cout << endl;
}
cout << f[n][0].len << '\n' << f[n][0].val << '\n';
}
总结
- 要及时删除不必要的状态。显然,这题中大部分 都是无用的,只有最后 7 个 才有用。
- 对于 dp 如果满足最优子结构性质,不妨设一个二元组。这其实和 dp 本身的设计逻辑不相悖,即便他不常见。
P8272
模拟赛被创死,展现出普及组选手 cfm 强大的普及水准。
什么时候 可以接住苹果 ?()
- 当 时,有 。。
- 当 时,有 。。
赛时的时候列出了后面两个式子,但是仍然并不会这道题。
我们注意到后面两个式子一定同时成立!这一点在移向前的式子非常明显,因为 。但是移向后似乎就没有这么显然了(upd:其实也非常显然,上下两式相加得到 ……,反过来只要减一下就好了……)
因此令 ,那么 可以接住苹果 的充要条件就是 。(这里的 和上面的 不是一样的!)这是一个二维偏序问题,但一般的二维偏序是计数,这里是最优化:将数对匹配。
回顾二维偏序的过程,是先对 进行排序,然后利用数据结构维护 。这里我们也按照 排序以此消除 的约束。对于每个奶牛 考虑把能吃掉的苹果放到集合 中。从 小的奶牛往 大的奶牛考虑。不考虑删除时这个 是在增长的。接下来考虑删除,显然 越小的苹果越难被匹配。所以从 尽量小的奶牛开始匹配。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5;
struct node {
int opt, x, y, num;
bool operator < (const node &other) const {
return y < other.y;
}
} Q[N + 10];
bool cmp(node &x, node &y) {
return x.x > y.x;
}
multiset <node> S;
int n;
vector <node> vec[3];
int main() {
cin >> n;
for(int i = 1, t, x; i <= n; i++) {
cin >> Q[i].opt >> t >> x >> Q[i].num;
Q[i].x = t - x, Q[i].y = t + x;
vec[Q[i].opt].push_back(Q[i]);
}
sort(vec[1].begin(), vec[1].end(), cmp);
sort(vec[2].begin(), vec[2].end(), cmp);
int ia = 0, sum = 0;
for(int i = 0; i < vec[1].size(); i++) {
while(ia < vec[2].size() &&
vec[2][ia].x >= vec[1][i].x) {
S.insert(vec[2][ia]);
ia++;
}
while(1) {
if(!vec[1][i].num) break;
set <node>::iterator it = S.lower_bound(vec[1][i]);
if(it == S.end()) break;
node tmp = (*it);
if(tmp.num >= vec[1][i].num) {
sum += vec[1][i].num;
tmp.num -= vec[1][i].num;
vec[1][i].num = 0;
S.erase(it);
S.insert(tmp);
break;
}
else {
sum += tmp.num;
vec[1][i].num -= tmp.num;
S.erase(it);
}
}
}
cout << sum << endl;
}
总结
真的是一个很值得反思反思的题目。
复盘过程
- 在得到充要条件的一步,因为一般来讲是要根据 的大小讨论一下的。因此少前置约束是一个值得的动机。
- 从另一个角度,以少前置约束出发就是 ,等价于 同时成立。即 等价于 。这个变形我之前似乎没有用过,记下来
- 于是得到了一个类二维偏序的匹配问题。对于一个高维问题,最常用的思想是降维。三位偏序二维偏序如此,高维前缀和如此,高维统计也如此。
- 高维问题
- 降维
- 独立维度
- 高维问题
- 接下来的贪心中,是从“难以匹配”的奶牛和苹果出发。这也启发对于匹配类的贪心问题应该从“难以匹配”的元素出发思考策略。
- 这是因为一般来说匹配问题不带权(比如这道题万一苹果带权似乎就不好贪心了)。难以匹配的赶紧匹配,容易匹配的放到后面匹配也没关系。这是“决策包容性”。
- 关于这块似乎还要结合几个线段贪心详细总结总结
P10161
-
考虑对一个串有多少合法子串进行计数。
-
不妨考虑增量(类似与一个区间 dp 的思想,划分子问题)
- 如果往外套一层 (),那么合法子串数量 +1
- 如果往外面加一个 (),令目前这一层有 个 ()()()(),那么合法子串数量 +k
-
可以想到这样求出能够构造 个合法子串的最少序列长度 :每次先构造出
()()()()
的,然后给外面套层(()()()())
,接下来同理。转移只要考虑套一层 (),外面增加 个()
,一共会增加 个。dp 即可。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 1e5;
ll nd[N + 10], rt[N + 10];
int getn(int x) {
int l = 1, r = 1000, ans = 0;
while(l <= r) {
int mid = (l + r) >> 1;
if(mid * (mid + 1) / 2 <= x) ans = mid, l = mid + 1;
else r = mid - 1;
}
return ans;
}
void prep() {
for(int i = 1; i <= N; i++) {
int w = getn(i);
nd[i] = N * 10;
for(int j = 1; j <= w; j++) {
int ww = (j + 1) * j / 2;
if(nd[i - ww] + 2 * j < nd[i]) {
rt[i] = j;
nd[i] = nd[i - ww] + 2 * j;
}
}
}
}
void print(int x) {
if(x == 1) {
putchar('(');
putchar(')');
return ;
}
if(x == 0)
return ;
int w = rt[x];
putchar('(');
print(x - (w + 1) * w / 2);
putchar(')');
for(int i = 1; i < w; i++)
putchar('('), putchar(')');
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
prep();
int T; cin >> T;
while(T--) {
int n, qk;
cin >> n >> qk;
if(n >= nd[qk]) {
print(qk);
for(int i = nd[qk] + 1; i <= n; i++)
putchar('(');
puts("");
}
else puts("-1");
}
}
总结
- 这道题我一开始想贪心去了……确实不应该,我怎么知道这道题能不能贪?
- 不要把题目想太复杂,大胆一些。
- 感觉这题很萌?
P3226
和 的约束关系形成一颗树,问题变成树上最大独立集——错啦!比如 ——是 DAG 上最大独立集。我会网络流!
这道题 n = 1e5 网络流不了,怎么办,考虑图的性质。发现 1->2->6,1->3->6 构成一个很有意思的矩形,实际上如果把 1->2->4->...->x->2x,3->6->...->x->2x 之类列出来,可以得到一个矩形,问题变成了矩形上的最大独立集。列数最多 ,可以状压。由于互相间没有相邻 的数量并不大,所以跑得动。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 16, M = 11, V = 1e5;
const ll Mod = 1e9 + 1;
ll f[N + 3][(1 << M) + 3];
int num[N + 10], n, tot = 0;
bool flag[V + 10];
vector <int> sc;
void prepsc() {
for(int S = 0; S < (1 << 16); S++) {
bool flag = 0;
for(int i = 0; i < 15; i++)
if(((S >> i) & 1) && ((S >> (i + 1)) & 1)) {
flag = 1;
break;
}
if(flag) continue;
else sc.push_back(S);
}
}
ll calc() {
f[0][0] = 1;
for(int i = 1; i < tot; i++) {
for(int s1 = 0; s1 < sc.size(); s1++)
f[i][s1] = 0;
for(int s0 = 0; s0 < sc.size(); s0++) {
if(sc[s0] > (1 << num[i - 1]) - 1) break;
for(int s1 = 0; s1 < sc.size(); s1++) {
if(sc[s1] > (1 << num[i]) - 1) break;
int S0 = sc[s0], S1 = sc[s1];
if((S0 & S1) > 0) continue;
f[i][s1] = (f[i][s1] + f[i - 1][s0]) % Mod;
}
}
}
ll sum = 0;
for(int S = 0; S < sc.size(); S++)
sum = (sum + f[tot - 1][S]) % Mod;
return sum;
}
void work() {
ll ans = 1;
for(int i = 1; i <= n; i++) {
if(!flag[i]) {
tot = 1;
for(int j = i; j <= n; j *= 2) {
if(flag[j]) break;
num[tot] = 0;
for(int k = j; k <= n; k *= 3) {
flag[k] = 1;
num[tot]++;
}
tot++;
}
ans = ans * calc() % Mod;
}
}
cout << ans << endl;
}
int main() {
cin >> n;
prepsc();
work();
}
总结
- 用图刻画“能否同时出现”的信息。因为这类信息都具有一定传递性(?)或者相互影响。
- 无法继续往下做怎么办?——分析性质!这道题的性质在于每个点的出边都是 ,最多入边也都是 ,所以可以类似的考虑一个矩形上的独立集计数。
P2157
状态设 为 的人都吃过了,接下来 人内有 的人吃了,最后一个吃饭的人是 的最短等待时间。
转移并不难
- 如果 没有吃,那么就在 内选择 作为最后一个吃饭的,转移到 。要特别判断插队的不能互相间有影响。
- 如果 吃了(即 中包含 ),那么转移到 。
如果设出来了状态,转移相当平凡,但是怎么样设计出这个状态?
首先考虑:有很多排列的方案。于是可以从增量的角度考量一个队伍:最后一个放谁?
- 需要考虑一个极长存在的前缀 ,同时 不存在。这是因为 前缀内放的一定合法,其它的都是插了 的队。插了队的也互相间没有影响,所以关键就在 合不合法。
- 这就启发我们将这个 作为阶段。
- 接下来一切都顺其自然:因为我们需要知道队伍的全集,它可以用 来刻画。同时我们需要知道最后一个人计算代价,它可以用 刻画。
#include <bits/stdc++.h>
using namespace std;
const int N = 1000, B = 8;
const int inf = 1e9;
int t[N + 10], b[N + 10], n;
int f[N + 3][(1 << B) + 3][B * 2 + 3];
void upd(int &x, int y) {
x = min(x, y);
}
void init() {
cin >> n;
for(int i = 1; i <= n; i++)
cin >> t[i] >> b[i];
memset(f, 0x3f, sizeof(f));
for(int i = 0; i <= b[1]; i++)
f[1][(1 << i)][i + B] = 0;
for(int i = 1; i <= n; i++) {
for(int S = 0; S < (1 << B); S++) {
for(int P = 0; P <= 2 * B; P++) {
int p = i + P - B;
if(p <= 0) continue;
for(int x = i; x <= i + b[i]; x++) {
bool flag = 0;
for(int j = (x - i); j <= 7; j++)
if((S >> j) & 1)
if(j + i - x > b[x]) {//以防选 S 内插队的出现不合法
flag = 1;
break;
}
if(flag) continue;
if(!((S >> (x - i)) & 1)) {
upd(f[i][S | (1 << (x - i))][x - i + B],
f[i][S][P] + (t[x] | t[p]) - (t[x] & t[p]));
}
}
if((S & 1) && P > 0) upd(f[i + 1][S >> 1][P - 1], f[i][S][P]);
}
}
}
// cout << f[1][2][1 + B] << endl;
// cout << f[1][10][3 + B] << endl;
// cout << f[1][14][2 + B] << endl;
// cout << f[1][46][5 + B] << endl;
// cout << f[1][62][4 + B] << endl;
// cout << f[1][190][7 + B] << endl;
// cout << f[1][191][B] << endl;
int minn = inf;
for(int i = max(n - 7, 1); i <= n; i++) {
for(int P = 0; P <= 2 * B; P++) {
int S = (1 << (n - i + 1)) - 1;
// if(f[i][S][P] == 9) {
// cout << i << ' ' << S << ' ' << P << endl;
// cout << f[i][S][P] << endl;
// }
minn = min(minn, f[i][S][P]);
}
}
cout << minn << endl;
}
int main() {
int T; cin >> T;
while(T--) init();
}
总结
- 无论是计数问题还是最优化问题,在没有思路的时候都应该考虑判定。
- 如果考虑判定没有什么启发(这道题就没有),可以尝试增量(这道题就是通过对”增加最后一个元素“考量分析的)
- 我 noip 炸了,这是 noip 后第一篇总结。真的不能再摆烂了,我与 cfm 共勉。
P4107
- 对于菊花图
- 显然是从 尽量小的点向父亲合并。
- 对于链的合并。
- 贪心的从链底向上合并。
- 为什么是正确的。
- 对于菊花图实际上就是按照 大小排序。
- 反证法,假设不是正确的,即从链底 开始一直向上最多可以合并到 ,但是从另外一个点 开始最多可以合并到更高的 。那么从 开始首先会至少失去 到 这一段的受益,其次从 在网上可以继续合并到 。失去的收益(没有删除的点)至少为 ,而增加的收益(删除了的点)最多为 (也就是 可以被删除),因此是不优的,可以直接贪。
- 对于全局的合并,就是直接按照深度从大往小 大小向父亲合并即可。
#include <bits/stdc++.h>
using namespace std;
const int N = 2e6;
vector <int> gra[N + 10];
int dep[N + 10], fat[N + 10], son[N + 10];
int n, m, c[N + 10];
void dfs(int u) {
dep[u] = dep[fat[u]] + 1;
for(int i = 0; i < gra[u].size(); i++) {
int v = gra[u][i];
dfs(v);
}
}
struct node {
int to, cval;
bool operator < (const node &other) const {
return cval > other.cval;
}
};
int ord[N + 10];
bool cmp(int &x, int &y) {
return dep[x] > dep[y];
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i++)
cin >> c[i];
for(int i = 1; i <= n; i++) {
int x; cin >> son[i];
ord[i] = i;
for(int j = 1; j <= son[i]; j++) {
cin >> x;
fat[x + 1] = i;
gra[i].push_back(x + 1);
}
}
dfs(1);
int cnt = 0, last = 1;
sort(ord + 1, ord + n + 1, cmp);
priority_queue <node> que;
for(int i = dep[ord[1]]; i >= 2; i--) {
while(dep[ord[last]] == i && last <= n)
que.push((node){ord[last], c[ord[last]] + son[ord[last]] - 1}), last++;
while(!que.empty()) {
node fr = que.top(); que.pop();
int u = fr.to, cv = fr.cval;
int f = fat[u];
if(son[f] + c[f] + cv <= m) {
c[f] += c[u];
son[f] = son[f] - 1 + son[u];
cnt++;
}
}
}
cout << cnt << endl;
}
总结
- 对于树上最优化问题,如果没办法 dp(这东西看上去就不像 dp)可以考虑贪心
- 对于树上贪心策略
- 先从菊花图考虑,再从链考虑。
- 整体的框架很多时候都是从拓扑序开始。
- 菊花图的情况是为了得到策略,而链的情况是为了说明拓扑序更新的正确性。
- 正所谓:贪心向爹合并(X
P3594
答案显然具有单调性。于是二分。考虑判定。
如何判定一个区间最后可以不可以合法?只需要在其中取得长度为 和最大的一个子区间即可。这个单调队列就好了。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N = 2e6;
ll a[N + 10], S[N + 10], p;
int n, d;
int fr = 1, tl = 0;
int que[N + 10];
bool check(int x) {
int i = 1;
fr = 1, tl = 0;
for(int l = 1; l + x - 1 <= n; l++) {
while(fr <= tl && que[fr] < l) fr++;
while(i <= l + x - d) {
while(fr <= tl && S[que[tl] + d - 1] - S[que[tl] - 1] <=
S[i + d - 1] - S[i - 1]) tl--;
que[++tl] = i;
i++;
}
int u = que[fr];
if((S[l + x - 1] - S[l - 1]) - (S[que[fr] + d - 1] - S[que[fr] - 1]) <= p)
return 1;
}
return 0;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0), cout.tie(0);
cin >> n >> p >> d;
for(int i = 1; i <= n; i++)
cin >> a[i], S[i] = S[i - 1] + a[i];
int L = d + 1, R = n, ans = d;
while(L <= R) {
int mid = (L + R) >> 1;
if(check(mid)) ans = mid, L = mid + 1;
else R = mid - 1;
}
cout << ans << endl;
}
P1954
什么?CSP2023T4?(大雾
这道题的策略也是类似的:按照拓扑序从 最短的出发。证明
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】