1 条题解
-
0
一、题意理解
我们有一个长度为 的序列 ,每个 。
定义:对于任意子区间 ,它的“恐惧总值”是 注意这里是 模 后的值,不是区间和本身。
给定 个询问 ,要求: $ \min_{l \le i \le j \le r} \left[ \left( \sum_{k=i}^j a_k \right) \bmod p \right] $ 即 内所有子区间模 的最小值。
二、初步分析
设 表示前缀和: 那么区间 的和为 。
我们要求的是: $ \min_{l \le i \le j \le r} \left[ (S[j] - S[i-1]) \bmod p \right] $ 注意模 运算,所以结果在 之间。
三、关键观察
设 。
- 如果 ,那么 。
- 如果 ,那么 。
因为 ,所以 。
因此: $ (S[j] - S[i-1]) \bmod p = \begin{cases} S[j] - S[i-1], & \text{if } S[j] - S[i-1] < p \\ S[j] - S[i-1] - p, & \text{if } S[j] - S[i-1] \ge p \end{cases} $
1. 最小值可能的情况
情况 1:存在 使得 ,那么差为 ,模 也是 ,这是可能的最小值。
情况 2:如果不存在差 ,那么最小值可能是某个很小的正数 ,即 且 。
情况 3:也可能最小值来自 的情况,此时模 的值是 ,这个值可能是负数吗?不会,因为模运算结果在 ,所以这里 吗?不一定,如果 ,那么 是对的。所以模 的结果总是非负的。
实际上, 就是取 除以 的余数,所以结果在 到 之间。
四、问题转化
我们要找: $ \min_{l \le i \le j \le r} ( (S[j] - S[i-1]) \bmod p ) $ 等价于: $ \min_{l-1 \le i < j \le r} ( (S[j] - S[i]) \bmod p ) $ 其中 从 到 , 从 到 。
设 ,那么 。
那么: 这里 表示取 模 的最小非负剩余。
注意: 等于:
- 如果 ,则为
- 如果 ,则为
所以我们要最小化: 其中 只在 时有效,否则用第二个。
五、进一步简化
我们要求: $ \min_{l-1 \le i < j \le r} \min\big( T[j] - T[i],\ T[j] - T[i] + p \big) $ 其中 在 时取,否则取 。
观察:如果我们把 数组在区间 排序,那么相邻两项的差(模 意义下)的最小值很可能就是答案。
定理:最小值要么是 (存在 ),要么是某个相邻差值(在排序后的环状意义下)。
理由:
在模 下, 值可以看作环上 的点。我们要找两个点 使得环上距离最小。这个最小距离一定出现在排序后相邻的项之间(环上相邻),或者等于 (重复点)。所以算法:
- 检查区间内是否有两个前缀和模 相等 → 答案是 。
- 否则,把区间内的 值排序,计算相邻差(包括首尾环状差 ),取最小差作为答案。
六、考虑数据范围与实现
最大 , 没说但应该很大。
我们不能对每个询问排序( 会超时)。
但题目有条件:
- 3/4 概率区间长度
- 1/4 概率区间长度
- 并且 在子任务中给出(如 2650, 8500)
所以我们可以:
- 对小区间直接暴力排序找最小差
- 对大连间,由于数据随机,很可能存在两个 值相等(生日悖论),直接返回 ;如果不存在,则最小值很可能很小,可以枚举较小的差值来检查。
七、高效算法思路
对于小区间(长度 ):
- 把区间内的 值取出,排序,计算相邻差以及环状差,取最小值。
- 复杂度 。
对于大连间(长度 ):
- 由于 随机, 可以看作在 随机。
- 区间长度很大时,根据生日悖论,几乎必然存在两个 值相等,直接返回 。
- 为了保险,可以检查是否存在 值重复(用哈希表),如果没有再采用其他方法(但概率极低)。
八、最终算法框架
- 读入 ,计算前缀和 和 。
- 对每个询问 :
- 如果 ,直接输出 (大概率正确,因为随机数据下几乎一定有重复 值)。
- 否则:
- 收集 到数组
- 排序
- 遍历 从 到 :
- 输出
九、代码实现(C++)
#include <cstdio> #include <iostream> #include <algorithm> #include <vector> #include <cstring> #include <cstdlib> #include <cmath> #include <set> using namespace std; #define LL long long #define LD long double const int inf = 1e9 + 17; const int NN = 6e6 + 17; const int MM = 1e3 + 17; const int MAXSIZE = 1 << 22; inline char gc() { static char In[MAXSIZE], *at = In, *en = In; if (at == en) { en = (at = In) + fread(In, 1, MAXSIZE, stdin); } return at == en ? EOF : *at++; } inline int read() { char c; while (c = gc(), !(c >= '0' && c <= '9') && c != '-') {} bool f = c == '-'; long long x = f ? 0 : c - '0'; for (c = gc(); c >= '0' && c <= '9'; c = gc()) { x = x * 10 + c - '0'; } return f ? -x : x; } void open() { freopen("sb.in", "r", stdin); // freopen("test.out","w",stdout); } void close() { fclose(stdin); fclose(stdout); } int n, m; int glob = 0; int t[NN] = {}; int a[NN] = {}; int mod; set<int> s; int main() { //open(); int n = read(); int mod = read(); for (int i = 1; i <= n; ++i) a[i] = (a[i - 1] + read()) % mod; int qu = read(); for (int ti = 1; ti <= qu; ++ti) { int l = read(), r = read(); int ans = mod - 1; int len = r - l + 1; if (len >= 2e4) { printf("0\n"); continue; } if ((LD)mod / (len * len / 2) >= 8 * log(len)) { s.clear(); s.insert(a[l - 1]); for (int i = l; i <= r; ++i) { set<int>:: iterator it = s.upper_bound(a[i]); if (it == s.begin()) it = s.end(); it--; ans = min(ans, (mod + a[i] - *it) % mod); s.insert(a[i]); } printf("%d\n", ans); continue; } for (int res = 0; res < mod; ++res) { ++glob; bool fl = 0; for (int j = l - 1; j <= r; ++j) { if (t[(a[j] + mod - res) % mod] == glob) { printf("%d\n", res); fl = 1; break; } t[a[j]] = glob; } if (fl) break; } } close(); return 0; }
十、总结
本题的关键在于:
- 将问题转化为在模 意义下找前缀和数组 在区间内的最小环上距离。
- 利用数据随机性质,对大连间直接返回 。
- 对小区间暴力排序找最小差。
这样可以在大数据范围内高效地回答询问。
- 1
信息
- ID
- 3934
- 时间
- 1000ms
- 内存
- 256MiB
- 难度
- 10
- 标签
- 递交数
- 1
- 已通过
- 1
- 上传者