Codeforces 1500F. Cupboards Jumps
题目大意:给出一个长度为\(n-2\)的数组\(w\),要求构造出一个各元素取值在\([0,10^{18}]\)范围内,长度为\(n\)的数组\(h\),使得对任意\(i\in [1,n-2]\),有\(w_i = \max(h_{i}, h_{i + 1}, h_{i + 2}) - \min(h_{i}, h_{i + 1}, h_{i + 2})\)。保证\(3\le n \le 10^6,0\le w_i\le C \le 10^{12}\)。
题解:首先考虑一个转换,三个数字之间的最大值减最小值其实就是两两做差的最大值,因此有\(w_i = \max(|h_i-h_{i+1}|,|h_{i+1}-h_{i+2}|,|h_i-h_{i+2}|)\)。
我们令\(d_{i}=h_{i+1}-h_{i}\),代入上式就能得出\(w_i = \max(|d_i+d_{i+1}|,|d_i|,|d_{i+1}|)\)。显然就有\(|d_i|\le w_i \le C\)。
设\(f[i][j]\)表示是否存在一种方案,使得\(d_i=j\)且对于前\(i\)个位置的\(d_i\)均满足条件,我们就能得到一个\(O(nC)\)的DP做法(确定\(f[i][j]=1\)时,对于\(w_i\)的限制,可以得出\(f[i+1][L...R]\)是可以满足条件的,在下一次循环就可以用前缀和的方式逐一赋值并判断是否满足下一个\(w\)的条件)。另外我们可以发现,若存在一个\(d_i=j\)的合法方案,那么一定也存在一个\(d_i=-j\)的合法方案(可以选择将前面所有的\(d\)都取反),因此第二维的取值就可以限制在\([0,C]\)的范围内。
从上述的DP过程中可以发现,如果把每个\(i\)合法的点都看成若干个区间,我们通过题目的一些条件,考虑式子在什么时候和\(w_i\)取等号,可以得到如下的转移方式:
- 首先,如果\(w_i\)存在于\(d_{i}\)的合法取值范围内,那么对于\(d_{i+1}\),\([0,w_i]\)均合法,且不需要再考虑其他情况。(\(w_i=|d_i|\))
- 如果存在一个小于\(w_i\)的数\(x\)使得\(f[i][x]=1\),即\(x\)存在于某一区间内,那么\(w_i\)一定是一个合法的取值(令\(d_i=-x,d_{i+1}=w_i\)),相当于一个合法区间\([w_i,w_i]\)。(\(w_i=|d_{i+1}|\))
- 剩下还有一种情况,就是\(w_i=|d_i+d_{i+1}|\),那么对当前任意一个合法的区间\([l,r]\),就能推出\(w_i=|d_i+d_{i+1}|\le |d_i|+|d_{i+1}|, |d_{i+1}|\ge w_i-|d_i|\ge w_i-r\),且有\( |d_{i+1}|=w_i-|d_i|\le w_i-l\),由此可以推出另一个合法区间\([w_i-r,w_i-l]\)。
在上述转移过程中,每轮转移至多只会产生一个新的合法区间,如果我们暴力维护这些区间,就在\(O(n^2)\)的时间复杂度内,完成对每个位置的合法区间的判断,值得注意的是每次开始转移前,要把当前的所有合法区间与\([0,w_i]\)取一次交。
现在考虑如何优化这个区间转移的过程。
回顾转移的方式,我们发现,当出现了第一种情况时,所有的区间都会被统一为一个区间\([0,w_i]\),这是我们最乐意看到的。而当出现了其他情况时,都是将当前区间以\(\frac{w_i}{2}\)为中心进行一个反转,并出现一个新的单点。而这个单点在之后的转移过程中,也始终会是一个单点,并且也会跟着翻转,而区间数目在整个过程中有且只有一个。于是我们开始考虑不再翻转这一个个点,而是考虑翻转整个数轴。为了统一,我们一律对数轴以零点为中心进行翻转,并记录当前数轴是正着的还是倒着的(即记录翻转次数的奇偶性),此外还要记录当前数轴被平移了多少个单位长度,用两个量来确定当前数轴的变换量。
接下来,我们需要倒着求出一个合法方案,确定所有\(d_i\)的绝对值。同样地,我们还是考虑式子在什么时候和\(w_i\)取等号,得到如下的转移方式(若未说明默认\(d\)外面有绝对值符号):
- \(w_i=d_i\):这个很好判断,只要\(w_i\)在\(d_i\)的合法区间内,直接令\(d_i=w_i\)即可。
- \(w_i=d_{i+1}\):在转移过程中,如果当前位置的值已经和\(w_i\)相等,那么\(d_i\)可以取合法区间内的任意一个值。
- 如果上述两种条件均不能满足,由当前取值说明当前\(d_{i+1}\)的取值是通过\(w_i=|d_i+d_{i+1}|\)转移过来的,因此令\(d_i=w_i-d_{i+1}\)即可。
于是我们发现,在倒推的过程中,我们只需要记下每个\(d_i\)是否可以取到\(w_i\),以及每个\(d_i\)的所有合法取值中的任意一个即可。在实现的时候,设这个合法值为\(v_i\),那么在存储的时候优先令\(v_i=w_i\)(如果可以的话)就可以方便判断。
确定完所有\(|d_i|\)的值后,我们需要确定他们的符号,显然,除了\(|d_i|+|d_{i+1}|=w_i\)时他们可以同号外,其余情况必须异号,否则就会出现加起来的绝对值超出\(d_i\)的情况。
由于存储单点时需要用到set,因此总时间复杂度为\(O(n\log n)\)。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define N 1000010 4 #define LL long long 5 LL n,C,w[N],d[N],v[N],l,r,o,k; 6 set<LL>s; 7 LL f(LL x){return o&1?k-x:k+x;} 8 LL g(LL y){return o&1?k-y:y-k;} 9 int main() 10 { 11 scanf("%lld%lld",&n,&C); 12 l=0,r=C; 13 for(LL i=1;i<=n-2;i++){ 14 scanf("%lld",&w[i]); 15 LL L=g(0),R=g(w[i]); 16 if(L>R)swap(L,R); 17 l=max(l,L),r=min(r,R); 18 while(!s.empty() && (*s.begin())<L)s.erase(s.begin()); 19 while(!s.empty() && (*s.rbegin())>R)s.erase(*s.rbegin()); 20 if(s.empty() && l>r)return printf("NO\n"),0; 21 LL W=g(w[i]); 22 if(s.count(W) || (l<=W && W<=r)){ 23 l=0,r=v[i]=w[i]; 24 s.clear(); 25 o=k=0; 26 continue; 27 } 28 if(l<=r)v[i]=f(l); 29 else v[i]=f(*s.begin()); 30 o^=1,k=w[i]-k; 31 s.insert(g(w[i])); 32 } 33 if(l<=r)d[n-1]=f(l); 34 else d[n-1]=f(*s.begin()); 35 for(LL i=n-2;i>=1;i--){ 36 if(v[i]==w[i]){ 37 d[i]=w[i]; 38 continue; 39 } 40 if(d[i+1]==w[i]){ 41 d[i]=v[i]; 42 continue; 43 } 44 d[i]=w[i]-d[i+1]; 45 } 46 k=1; 47 for(LL i=n-2;i>=1;i--){ 48 if(abs(d[i])+abs(d[i+1])!=w[i])k*=-1; 49 d[i]=k*d[i]; 50 } 51 LL mn=0; 52 for(LL i=1;i<=n-1;i++)d[i]+=d[i-1],mn=min(mn,d[i]); 53 printf("YES\n"); 54 for(LL i=1;i<=n;i++)printf("%lld%c",d[i-1]-mn,i<n?' ':'\n'); 55 }