憨豆先生卖豆子 ( 线段树 + 思维 + 小技巧 )
传送门 自家OJ 1313
题意 :
给你 n个数,让你找一段连续的区间, 使得这段 区间的和 S 除以 P 最大,且S%p <= k;
问你 最大的 S / P 为多少, 若没有这样的区间, 输出 -1;
解: 我们假设, pre[ i ] 为前 i 个数的和, succ[ i ] 为 i ~ n 这些数的和, 即后缀和。
假设 S 为 所有数的和。
那么, 假设我们选了一段区间 [ L, R ], 那么这段区间的 数的和即为, S - pre[ L - 1 ] - succ[ R + 1 ];
那么, 我们要使得 ( S - pre[ L - 1 ] - succ[ R + 1 ] ) % p <= k;
即 ( S % p - pre[ L - 1 ] % p - succ[ R + 1 ] % p + p) % p <= k 咯;
即 ( S % p - pre[ L - 1 ] % p + p) % p <= succ[ R + 1 ] % p <= ( S % p - pre[ L - 1 ] % p + p - k + p) % p;
那我们 搞个线段树,线段树下标 即为 succ[ R + 1 ] % p; 然后下标存的是位置;
即 满足 succ[ R + 1 ] % p = pos 中, 最大的R; ( 因为 R 越大, 加的数越多 );
那我们枚举 pre[ L - 1] % p; 然后对每个 pre[ L - 1 ] % p 都去线段树 找个 max, 然后更新答案。 即可。
具体细节,详见代码。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <bits/stdc++.h> #define LL long long #define INF 0x3f3f3f3f using namespace std; const int N = 1e6 + 5; LL pre[N], succ[N], a[N]; int b[N], c[N]; int T[N << 2]; void built(int rt, int l, int r) { /// 建树 if(l == r) { T[rt] = b[l]; return ; } int mid = (l + r) >> 1; built(rt << 1, l, mid); built(rt << 1 | 1, mid + 1, r); T[rt] = max(T[rt << 1], T[rt << 1 | 1]); } int query(int rt, int l, int r, int L ,int R) { /// 查询区间最大值。 if(L <= l && r <= R) { return T[rt]; } int mid = (l + r) >> 1; int ma = 0; if(L <= mid) ma = max(ma, query(rt << 1, l, mid, L, R)); if(R > mid) ma = max(ma, query(rt << 1 | 1, mid + 1, r, L, R)); return ma; } int main() { int _; scanf("%d", &_); int cas = 0; while(_--) { int n, p, k; scanf("%d %d %d", &n, &p, &k); for(int i = 0; i <= p * 4; i++) T[i] = 0; LL ans = -1; LL S = 0; for(int i = 0; i <= p; i++) b[i] = 0, c[i] = INF; for(int i = 1; i <= n; i++) { scanf("%lld", &a[i]); S += a[i]; pre[i] = pre[i - 1] + a[i]; c[pre[i] % p] = min(c[pre[i] % p], i); /// 维护个 下标为pre[i] % p的最大值 if(S % p <= k) ans=max(ans, S / p); ///前缀和满足条件也可直接更新 } succ[n + 1] = 0; for(int i = n; i >= 1; i--) { succ[i] = succ[i + 1] + a[i]; b[succ[i] % p] = max(b[succ[i] % p], i);/// 同理 if(succ[i] % p <= k) ans=max(ans, succ[i] / p); } built(1, 1, p); /// 建树 for(int i = 1; i < p; i++) { if(c[i] == INF) continue; int tmp = S % p - i; int R = (tmp + p) % p; int L = (tmp - k + p) % p; if(L == 0 || L > R) continue; int ma = query(1, 1, p, L, R); // cout << L << " " << R << " " << ma << endl; if(ma == 0) continue; if(ma <= c[i] + 1) continue; // puts("come"); LL sum = S - pre[c[i]] - succ[ma]; LL q = sum / p; ans = max(ans, q); // puts("come"); } printf("Case %d: ", ++cas); printf("%lld\n", ans); } return 0; }