【题解】Dvoniz [COCI2010]
【题目描述】
对于一个长度为 \(2*K\) 的序列,如果它的前 \(K\) 个元素之和小于等于 \(S\) 且后 \(K\) 个之和也小于等于 \(S\),我们则称之为 \(\text{interesting}\) 。现给定一个长度为 \(N\) 的序列 \(a\),要求输出以每个元素开头能找到的最长 \(\text{interesting}\) 序列 的长度。
【输入】
第一行两个整数 \(N,S\) 。
接下来 \(N\) 行,每行一个整数,表示序列中每个元素 \(a[i]\)。输入保证每个元素为正数且和小于 \(2*10^9\)。
【输出】
\(N\) 行每行一个整数,第 \(i\) 行表示以 \(a[i]\) 开头的最长的 \(\text{interesting}\) 序列。如果不存在,则输出 \(0\)。
【样例】
样例输入: 5 10000 1 1 1 1 1 样例输出: 4 4 2 2 0
【数据范围】
\(100 \%:\) \(2 \leqslant n \leqslant 10^5,\) \(1 \leqslant S \leqslant 2*10^9\)
【分析】
一道灰常 \(\text{interesting}\) 的题。
蒟蒻英语差没有细看官方题解,貌似是 \(O(nlogn)\) 的,我自己 \(yy\) 了一种 \(O(n)\) 的神奇算法。
设以 \(a[i]\) 为起点的最长合法序列的中点为 \(mid_i\)(前半段为 \([i,mid]\),后半段为 \([mid+1,mid*2-i+1]\)),则 \(ans_i=2(mid_{i}-i+1)\)。
假设现已求出了 \(mid_{i-1}\),考虑 \(mid_i\) 与之有何联系,是否可以继承,如图:
由于 \(mid_{i-1}\) 左右两边的绿色部分都小于等于 \(S\),那么向前推移了一位的 \(i\) 以 \(mid_{i-1}\) 为中点也可以构成合法序列,如下图(易知两边的蓝色部分都一定小于等于 \(S\) ):
所以对于任意 \(i \in [2,n]\),都有 \(mid_{i-1} \leqslant mid_{i}\) 。
那么就可以用一个变量 \(p\) 来维护 \(mid\),从 \(1\) 开始不断地向后移动。
但有可能 \(mid[i-1]\) 并非是以 \(a[i]\) 开头的最优解,继续考虑对每个 \(i\) 求出最大的 \(mid\):
分开处理合法序列的左右两边,当 \(i\) 固定时,如果只看左边是否合法的话,那么从第一个不合法的位置开始,后面的都不合法(这不是理所当然的嘛),所以直接从 \(mid_{i-1}\) 开始向后暴力移动 \(p\)(也可以二分,但不便于后面的证明),扫到不合法的位置时就结束。此时在 \([mid_{i-1},p]\) 中任取一个位置作为 \(mid_{i}\) 都可以满足序列左边合法,现在开始处理右边。
右边对于 \(p\) 的移动是不具有单调性的,那么就暴力往回移动 \(p\),找到第一个使得右边序列合法的位置,此时 \(p\) 停留的位置必定是 \(mid_{i}\) 的最优值。
暴力,暴力,全都是暴力。对于每次 \(i\) 都要把 \(p\) 向后移动若干位置再移回来,时间复杂度似乎为 \(O(n^2)\),但实际上是线性的,可以几十 \(ms\) 轻松跑过(\(n\) 方过百万)。
【时间复杂度证明】
对于每个 \(i\),设 \(p\) 从 \(mid_{i-1}\) 开始向后移动了 \(x_{i}\),又从 \(mid_{i-1}+x_{i}\) 开始向前移回去了 \(y_{i}\),那么总时间复杂度可以表示为 \(\Theta=\sum_{i=1}^{n} (x_{i}+y_{i})\) —— ①。
对于每个 \(i\),\(p\) 从 \(mid_{i-1}\) 开始移动了 \(x_{i}-y_{i}\) 后到达了 \(mid_{i}\),那么 \(mid_{i}\) \((i \in [1,n])\) 的总移动距离就可以表示为 \(\sum_{i=1}^{n} (x_{i}-y_{i})\) 。
又因为 \(mid_{i}\) 具有决策单调性,必定是从 \(1\) 移到 \(n\),所以总移动距离应为 \(n\),即:\(n=\sum_{i=1}^{n} (x_{i}-y_{i})\) —— ②。
由于序列 \([mid_{i-1},mid_{i-1}+x_{i}]\) 中元素之和一定是小于等于 \(S\) 的,那么取其中点 \(M(mid_{i-1}+\frac{x_{i}}{2})\),一定可以使得 \(M\) 两边都合法,即 \(mid_{i} \geqslant M\),于是有 \(mid_{i-1}+x_{i}-y_{i} \geqslant mid_{i-1}+\frac{x_{i}}{2}\),即 \(y_{i} \leqslant \frac{x_{i}}{2}\) —— ③。
由②③可知:
\(n=\sum_{i=1}^{n} (x_{i}-y_{i}) \geqslant \sum_{i=1}^{n} \frac{x_{i}}{2}\),即 \(\sum_{i=1}^{n} \frac{3}{2}x_{i} \leqslant 3n\) 。
由①③可知:
\(\Theta=\sum_{i=1}^{n} (x_{i}+y_{i}) \leqslant \sum_{i=1}^{n} \frac{3}{2}x_{i}\) 。
于是有 \(\Theta \leqslant 3n\) 。
时间复杂度得证,为 \(O(n)\) 。
(这样看来,向后移时的二分貌似都没必要写了)
另外,有个 \(\text{julao}\) 认为上述证明有问题,但具体说不清,如有不严谨处欢迎指出。
【Code】
#include<algorithm> #include<iostream> #include<cstring> #include<cstdlib> #include<cstdio> #define LL long long #define Re register int using namespace std; const int N=1e5+5; int n,s,a[N];LL S[N]; inline void in(Re &x){ int f=0;x=0;char c=getchar(); while(c<'0'||c>'9')f|=c=='-',c=getchar(); while(c>='0'&&c<='9')x=(x<<1)+(x<<3)+(c^48),c=getchar(); x=f?-x:x; } inline int judge1(Re i,Re mid){return S[mid]-S[i-1]<=s;} //judge1()判断序列前半段 inline int judge2(Re i,Re mid){return S[(mid<<1)-i+1]-S[mid]<=s;} //judge2()判断序列前半段 int main(){ // freopen("b.in","r",stdin); // freopen("b.out","w",stdout); in(n),in(s); for(Re i=1;i<=n;++i)in(a[i]),S[i]=S[i-1]+a[i]; Re p=0;S[n+1]=S[n+2]=1e18;//为防止玄学错误,先把最后面的覆盖一下 while((p+1<<1)<=n&&judge1(1,p+1))++p;//预处理出第一个mid while(p&&!judge2(1,p))--p; printf("%d\n",(p<<1)); for(Re i=2;i<=n;++i){ // if(p<i-1)p=i-1;//这句可加可不加 while((p+1<<1)-i+1<=n&&judge1(i,p+1))++p;//向后移时注意判断右边界不能超过n while(p>=i&&!judge2(i,p))--p;//向前移回去,找到最大的合法mid_i // printf("i=%d, p=%d, ",i,p); printf("%d\n",(p-i+1)<<1);//输出为长度 } fclose(stdin); fclose(stdout); return 0; }