P1956 题解

· · 题解

题意

找出满足子段和在膜 p 意义下大于等于 k 的最小子段和。

题解

根据膜法的性质,我们在膜 p 意义下,进行前缀和操作是可以的。那么我们就优化了找子段和这一步骤。

那么现在剩下的问题就是找哪一段子段和。

因为前缀和都膜了 $p$,所以所有的前缀和都是在 $0$ ~ $p - 1$ 内的。 那么对于右端点 $i$, 当子段和大于等于 $k$ 的时候,满足条件的左端点 $j$, 满足如下性质。 $$sum[i] - sum[j] \geq k(sum[i]> sum[j])$$ $$sum[i] - sum[j] + p \geq k(sum[i]\leq sum[j])$$ 稍作变形之后,就得到了如下柿子: $$sum[j] \leq sum[i] - k(sum[i]> sum[j])$$ $$sum[j] \leq sum[i] + mod - k(sum[i]\leq sum[j])$$ 我们得到了满足条件的 $j$ 的柿子,那么我们为了答案最优,我们就要找到其中最大的 $sum[j]$。 所以引用数据结构来优化。 因为原数组不是单调的,普通的二分查找肯定是不行的。 不难发现平衡树是满足我们的需要的,但是懒得手打,于是可以用 set。 因为 set 只有大于等于和大于,所以可以把上柿同时取反,然后枚举 $i$, 查找 $j$,就行了。 # 代码 ```cpp #include<cstdio> #include<cctype> #include<set> #include<iostream> using namespace std; typedef long long LL; const int N = 1e5 + 5; LL a[N], sum[N], mod, k, n, ans = 1e18, x1, x2; set < LL > s; inline LL read() { LL x = 0; int f = 1, c = getchar(); for(; !isdigit(c); c = getchar()) if(c == '-') f = -1; for(; isdigit(c); c = getchar()) x = x * 10 + c - 48; return x * f; } int main() { n = read(), k = read(), mod = read(); for(int i = 1; i <= n; i++) { a[i] = read(); if(a[i] >= mod) a[i] %= mod; } for(int i = 1; i <= n; i++) { sum[i] = (1ull * sum[i - 1] + a[i]) % mod; s.insert(-sum[i]); } for(int i = 1; i <= n; i++) { x1 = *s.lower_bound(k - sum[i]); x2 = *s.lower_bound(k - sum[i] - mod); x1 = (sum[i] + x1) % mod; x2 = (sum[i] + x2) % mod; if(x1 < 0) x1 += mod; if(x2 < 0) x2 += mod; if(x1 >= k) ans = min(ans, x1); if(x2 >= k) ans = min(ans, x2); } printf("%lld\n", ans); return 0; } ```