题解:P15939 [JOI Final 2026] 传奇团子吃家 / Legendary Dango Eater

· · 题解

一个贪心策略是对辣团子取相反数,能吃就吃,如果当前积攒的团子和小于 0 就放弃。有如下 O(nq) 代码:

for(int i = 1; i <= n; ++i){
    read(a[i]);
    if(i % 2 == 0) a[i] = -a[i];
}
//对于每个询问:
for(int i = l; i <= r; ++i){
    if(a[i] > 0){
        ans += (s + a[i]) / k;
        s = (s + a[i]) % k;
    }
    else s = max(s + a[i], 0ll);
}
write(ans), putchar('\n');

我们以 s 变为 0 为分界点(相当于重置成起点),这样就可以在每个询问倍增跳段了。设 sum_i 表示取相反数后一正一负的 a 的前缀和。现在要找到 f_{0, x},表示 (\sum_{j = x} ^{f_{0, x} - 1} a_j) \bmod k + a_{f_{0, x}} \le 0 的最小辣团子位置 f_{0, x}

这个式子等价于 (sum_{f_{0, x} - 1} - sum_{x - 1}) \bmod k \le -a_{f_{0, x}}。我们从小到大枚举 i,辣团子的 f_{0, i} = i + 1(因为以这个辣团子开头没用不如舍弃),把还没计算过甜团子的 f_{0, i}i 丢进一个 set 里。现在考虑当前辣团子 i 可以当哪些甜团子的 f

tmp 为取模过的 sum_{i - 1}(对应式子的 sum_{f_{0, x} - 1}),j 为 set 里的候选甜团子(sum_{j - 1} 就对应上述 sum_{x - 1})。取模是环形的,所以候选甜团子分为:

以上利用了 set 的自动排序,具体实现定义一个 set<pair<long long, int> > 即可,pair.first 表示 sum_{j - 1}pair.second 表示 j。对于所有满足条件的 j,有 f_{0, j} = i,然后把 j 从集合里删除。

直到最后还在 set 里的 j,说明没有令 j 满足上述条件不等式的 f_{0, j},这时令 f_{0, j} = n + 1

时间复杂度 $O(n \log n)$。 ::::success[Code] ```cpp #include<bits/stdc++.h> using namespace std; template<typename T> inline void read(T &x){ T s = 0; int st = 1; char c = getchar(); while(c < '0' || c > '9') (c == '-') && (st = -1), c = getchar(); while(c >= '0' && c <= '9') s = (s <<3) + (s << 1) + (c ^48), c = getchar(); x = s * st; } template<typename T, typename ...Args> inline void read(T &x, Args &...args){ read(x); read(args...); } template<typename T> inline void write(T x){ if(x < 0) putchar('-'), x = -x; if(x > 9) write(x / 10); putchar(x % 10 + 48); } #define LL long long #define PII pair<LL, int> const int N = 5e5 + 5; int a[N], f[20][N]; LL sum[N], g[20][N]; int main(){ int n, q; LL k; read(n, q, k); for(int i = 1; i <= n; ++i){ read(a[i]); if(i % 2 == 0) a[i] = -a[i]; f[0][i] = n + 1; sum[i] = sum[i - 1] + a[i]; } set<PII> s; //还没找到f[0][i]的集合 //(sum[f[0][x] - 1] - sum[x - 1]) % k <= a[f[0][x]] for(int i = 1; i <= n; ++i){ LL tmp = (sum[i - 1] % k + k) % k; if(i & 1){ s.insert({tmp, i}); continue; } auto j = s.lower_bound({tmp, n + 1}); while(!s.empty() && j != s.begin()){ //sum[x - 1] <= sum[f[0][x] - 1] --j; if((tmp - (*j).first) % k <= -a[i]){ f[0][(*j).second] = i; s.erase(j); j = s.upper_bound({tmp, n + 1}); } else break; } while(!s.empty()){ if((tmp - (*s.rbegin()).first + k) % k <= -a[i]){ f[0][(*s.rbegin()).second] = i; s.erase(*s.rbegin()); } else break; } f[0][i] = i + 1; } for(int i = 1; i <= n; ++i) g[0][i] = max((sum[f[0][i] - 1] - sum[i - 1]) / k, 0ll); f[0][n + 1] = n + 1; for(int i = 1; i < 20; ++i){ for(int j = 1; j <= n + 1; ++j){ f[i][j] = f[i - 1][f[i - 1][j]]; g[i][j] = g[i - 1][j] + g[i - 1][f[i - 1][j]]; } } while(q--){ int l, r; LL ans = 0; read(l, r); for(int i = 19; i >= 0; --i){ if(f[i][l] <= r){ ans += g[i][l]; l = f[i][l]; } } ans += max((sum[r] - sum[l - 1]) / k, 0ll); write(ans), putchar('\n'); } return 0; } ``` ::::