我们先想全是 \(\pm 1\) 的。令区间内最小子段和为 \(mn\),最大子段和为 \(mx\),注意到 \([mn,mx]\) 内的数全都能被凑出来。
-
证明:我们在区间 \([l,r]\) 内任意取一个子区间 \([l',r']\)。
- 定义【扩展】为将一个区间左边或右边添加一个数。
- 定义【收缩】为将一个区间左边或右边去掉一个数。
我们发现所有 \([l,r]\) 的子区间都能通过 \([l',r']\)【扩展】或【收缩】得到。而对于【扩展】或【收缩】,操作前后的 \(sum\) 一定是连续的。于是,所有子区间的 \(sum\) 都是连续的。所谓【连续】,当然是从最小和连续到最大和。
将 \([mn,mx]\) 内的数加入答案即可。
对于有特殊值的,类似分治的思想,劈两半算两遍,再管包含特殊值的区间。和一定是左半的后缀加特殊值加右半的前缀。同理,只会取到最小值到最大值的所有数。
排序,去重,输出即可。
#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define Linf 0x3f3f3f3f3f3f3f3f
#define pii pair<int, int>
#define all(v) v.begin(), v.end()
using namespace std;//#define filename "xxx"
#define FileOperations() freopen(filename".in", "r", stdin), freopen(filename".out", "w", stdout)namespace Traveller {const int N = 2e5+2;int n, a[N];vector<int> ans;int sum[N];void work(int *a, int n) {int x = 0, y = 0;int mx = 0, mn = 0; //初值的艺术int sum = 0;for(int i = 1; i <= n; ++i) {sum += a[i];mx = max(mx, sum - y);mn = min(mn, sum - x);x = max(x, sum);y = min(y, sum);}for(int i = mn; i <= mx; ++i) ans.push_back(i);}void main() {cin >> n;for(int i = 1; i <= n; ++i) scanf("%d", &a[i]);for(int i = 1; i <= n; ++i) sum[i] = sum[i-1] + a[i];int p = -1;for(int i = 1; i <= n; ++i)if(abs(a[i]) != 1) {p = i;break;}ans.clear();if(~p) {int mx = 0, mn = 0;int x = 0, y = 0;work(a, p-1);for(int i = p-1; i >= 0; --i)x = max(x, sum[p-1] - sum[i]), y = min(y, sum[p-1] - sum[i]);mx += x, mn += y;x = y = 0;work(a+p, n-p);for(int i = p; i <= n; ++i)x = max(x, sum[i] - sum[p]), y = min(y, sum[i] - sum[p]);mx += x, mn += y;for(int i = mn; i <= mx; ++i) ans.push_back(i + a[p]);}else work(a, n);sort(all(ans));ans.erase(unique(all(ans)), ans.end());printf("%d\n", (int)ans.size());for(int ele : ans) printf("%d ", ele);puts("");}
}signed main() {#ifdef filenameFileOperations();#endifint _;cin >> _;while(_--) Traveller::main();return 0;
}