题目
题目概要
序列
h
h
h 的每个元素都是不超过
1
0
18
10^{18}
1018 的自然数。给出每相邻三个数的极差
w
i
=
max
(
h
i
,
h
i
+
1
,
h
i
+
2
)
−
min
(
h
i
,
h
i
+
1
,
h
i
+
2
)
w_i=\max(h_i,h_{i+1},h_{i+2})-\min(h_i,h_{i+1},h_{i+2})
wi=max(hi,hi+1,hi+2)−min(hi,hi+1,hi+2),请还原出
h
h
h 序列。多解输出任意解。无解则报告无解。
数据范围与提示
n
≤
1
0
6
n\le 10^6
n≤106 而
w
i
≤
1
0
12
w_i\le 10^{12}
wi≤1012 。提示:数据范围恰好使得
h
i
∈
[
0
,
1
0
18
]
h_i\in[0,10^{18}]
hi∈[0,1018] 必然有解。
思路
如果我们 n a i v e \rm naive naive 一点,可以先搞一个 O ( n h 2 ) \mathcal O(nh^2) O(nh2) 的做法。
这个做法所以慢者,极差需要三个数都完全知道才能求。可不可以把求极差的方法改良一下?然后没有想到好的改良方法。
一个不错的想法是,三者同时加上或者减去一个数字,极差是不会变的。那么规定第一个数字为 0 0 0,显然极差是 max ( ∣ x 2 ∣ , ∣ x 3 ∣ , ∣ x 2 − x 3 ∣ ) \max(|x_2|,|x_3|,|x_2-x_3|) max(∣x2∣,∣x3∣,∣x2−x3∣) 。
而规定第一个数字为零,本质上是 差分。记
d
i
=
h
i
+
1
−
h
i
d_i=h_{i+1}-h_i
di=hi+1−hi,式子改写为
w
i
=
max
(
∣
d
i
∣
,
∣
d
i
+
1
∣
,
∣
d
i
+
d
i
+
1
∣
)
w_i=\max(|d_i|,|d_{i+1}|,|d_i+d_{i+1}|)
wi=max(∣di∣,∣di+1∣,∣di+di+1∣)
于是我们已经有了一个 O ( n h ) \mathcal O(nh) O(nh) 的做法。为了方便,先要有 o b s e r v a t i o n \rm observation observation:如果差分值为 d d d 作为结尾是可行的,那么把前面所有差分值都取反,就得到一个 − d -d −d 结尾的方案。所以只需要考虑正数作为差分值结尾。
讨论一下
w
i
w_i
wi 究竟是哪一个值就行了。比如就是当前值,那么让上一个值取负数即可。
g
(
w
i
)
=
f
(
x
)
(
x
≤
w
i
)
g(w_i)=f(x)\;(x\le w_i)
g(wi)=f(x)(x≤wi)
或者是上一个的值,当前值取负数就行。
g
(
x
)
=
f
(
w
i
)
(
x
≤
w
i
)
g(x)=f(w_i)\;(x\le w_i)
g(x)=f(wi)(x≤wi)
要不然就是二者的和,二者都取正数。
g
(
x
)
=
f
(
w
i
−
x
)
(
x
≤
w
i
)
g(x)=f(w_i-x)\;(x\le w_i)
g(x)=f(wi−x)(x≤wi)
显然它不足以在十年之内跑出来。 考虑继续优化。
想起一道经典的题目(我找不到博客了,直接贴题面吧):
有一棵树,每个点是黑色、白色或无色,请问能否找到一个连通块,恰好有 a a a 个黑点、 b b b 个白点。
结论是,对于恰好有 a a a 个黑点的连通块,可能的白点数量是一个区间。这道题其实也一样。
对于区间如何变化的,直接看状态转移方程。
- f ( x ) → g ( w i ) f(x)\rightarrow g(w_i) f(x)→g(wi) 对应着:如果上一层有 x ( x ≤ w i ) x\;(x\le w_i) x(x≤wi),这一层加入 [ w i , w i ] [w_i,w_i] [wi,wi] 。
- f ( w i ) → g ( x ) f(w_i)\rightarrow g(x) f(wi)→g(x) 对应着:如果上一层有 w i w_i wi,这一层加入 [ 0 , w i ] [0,w_i] [0,wi] 。
- f ( w i − x ) → g ( x ) f(w_i-x)\rightarrow g(x) f(wi−x)→g(x) 对应着:如果上一层有 [ l , r ] [l,r] [l,r],这一层加入 [ max ( w i − r , 0 ) , w i − l ] [\max(w_i-r,0),w_i-l] [max(wi−r,0),wi−l] 。
显然每一层最多增加一个区间,即 [ w i , w i ] [w_i,w_i] [wi,wi] 。让区间保持有序,它相当于在末尾插入,很容易实现。我们要解决的就是其他的操作:翻转、平移、截断(和 0 0 0 取 max \max max 把负数部分给砍断了)和查询。
翻转和平移很简单,直接打标记(只是代码实现不简单)。而查询
w
i
w_i
wi 的存在咋办?考虑利用截断。本来我们就要把
w
i
−
r
≤
0
w_i-r\le 0
wi−r≤0 的部分截断,那不如先截断
r
≥
w
i
r\ge w_i
r≥wi 的部分再翻转。而截断之后,查询
w
i
w_i
wi 的存在就只需要检查最后一个区间,是
O
(
1
)
\mathcal O(1)
O(1) 的。
所以
O
(
n
)
\mathcal O(n)
O(n) 判断有解已经做到了。然而怎么输出方案呢?答曰:可回退。按照上面的策略直接找前驱即可。总复杂度不变,代码难度惊人。
代码实现优化
事实上,最后输出方案可以用构造法。由于区间多数是翻转的,所以 x x x 很可能是 w i − x w_i-x wi−x 得到的,那就直接这样交替选择呗。
特例一是 f ( w i ) → g ( x ) f(w_i)\rightarrow g(x) f(wi)→g(x),那就将上一个设定为 w i w_i wi,继续构造。特例二是 f ( x ) → g ( w i ) f(x)\rightarrow g(w_i) f(x)→g(wi),直接取上一个的 min \min min 就可以了。
代码
我干脆打了双向链表,翻转就很容易实现了。才怪咧。
回退的时候也要注意一些问题,比如截断操作导致区间分裂,回退的时候要合并上去。反正就挺麻烦的。
#include <bits/stdc++.h>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
typedef long long int_;
inline int_ readint(){
int_ a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(int_ x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) writeint(x/10);
putchar((x-x/10*10)^48);
}
const int MaxN = 1000001;
struct Range{
long long l, r;
Range(){ l = r = 0; }
Range(int_ L,int_ R){
l = L, r = R;
}
int_& operator[](const int &x){
return x ? r : l;
}
};
struct BestList{
struct Node{
Range r; Node *nxt[2];
};
int dir, sgn; int_ adv;
Node *head, *tail;
# define unzip(x) (sgn*(x)+adv)
BestList(){
dir = sgn = 1, adv = 0;
head = new Node();
tail = new Node();
head->nxt[dir] = tail;
tail->nxt[dir^1] = head;
}
bool empty() const {
return head->nxt[dir] == tail;
}
struct Event{
int opt; Range r;
Event(const Range &R):r(R){
opt = 1; // default
}
};
vector<Event> sta; // for undo
void reverse(){
dir ^= 1; // change direction
swap(head,tail); // keep ordered
adv = -adv, sgn = -sgn;
sta.push_back(Event(Range()));
sta.back().opt = 2; // inversion
}
void shift(int_ x){
adv += x; // stupid
sta.push_back(Event(Range(x,x)));
sta.back().opt = 3; // shift
}
void pop_back(){
Node *t = tail->nxt[dir^1];
tail->nxt[dir^1] = t->nxt[dir^1];
tail->nxt[dir^1]->nxt[dir] = tail;
t->r[0] = unzip(t->r[0]);
t->r[1] = unzip(t->r[1]);
if(t->r[0] > t->r[1])
swap(t->r[0],t->r[1]);
sta.push_back(Event(t->r));
delete t; // delete from list
}
void truncate(int_ x){
while(!empty()){
Node *t = tail->nxt[dir^1];
if(unzip(t->r[dir^1]) > x){
pop_back(); continue;
}
if(sgn == 1 && t->r[dir] > x-adv){
sta.push_back(Event(
Range(x+1,unzip(t->r[dir]))
));
sta.back().opt = 5;
t->r[dir] = x-adv;
}
if(!(~sgn) && t->r[dir] < adv-x){
sta.push_back(Event(
Range(x+1,unzip(t->r[dir]))
));
sta.back().opt = 5;
t->r[dir] = adv-x;
}
break; // tackled all
}
}
bool check(int_ x){
Node *t = tail->nxt[dir^1];
return t != head && unzip(t->r[dir]) == x;
}
int_ front() const {
return unzip(head->nxt[dir]->r[dir^1]);
}
bool tiny(int_ x){
return !empty() && front() != x;
}
void insert(int_ l,int_ r,bool f = false){
Node *t = tail->nxt[dir^1];
if(t != head && unzip(t->r[dir]) >= r)
return ; // involved already
if(f && t != head && unzip(t->r[dir]) >= l-1){
t->r[dir] = (r-adv)*sgn; return ; // link
}
t->nxt[dir] = new Node();
l = (l-adv)*sgn, r = (r-adv)*sgn;
t->nxt[dir]->r[dir^1] = l;
t->nxt[dir]->r[dir] = r; // range
t->nxt[dir]->nxt[dir^1] = t;
t->nxt[dir]->nxt[dir] = tail;
tail->nxt[dir^1] = t->nxt[dir];
sta.push_back(Event(Range()));
sta.back().opt = 4; // insert
}
void fillUp(int_ x){
while(!empty()) pop_back();
insert(0,x); // the whole thing
}
void stepback(){
vector<Event> jb; jb.swap(sta);
Range &r = jb.back().r;
switch(jb.back().opt){
case 2: reverse(); break;
case 3: shift(-r.r); break;
case 4: pop_back(); break;
case 1: insert(r.l,r.r); break;
case 5: insert(r.l,r.r,true);
}
jb.swap(sta); sta.pop_back();
}
};
BestList lis;
int cnt[MaxN][2];
int_ dif[MaxN], w[MaxN];
int main(){
int n = readint(); readint();
lis.insert(0,1e18);
for(int i=1; i<=n-2; ++i){
w[i] = readint();
cnt[i][0] = -lis.sta.size();
lis.truncate(w[i]);
cnt[i][1] = -lis.sta.size();
cnt[i][0] -= cnt[i][1];
if(lis.check(w[i])){
lis.fillUp(w[i]);
cnt[i][1] += lis.sta.size();
continue;
}
bool corner = lis.tiny(w[i]);
lis.reverse(); lis.shift(w[i]);
if(corner) lis.insert(w[i],w[i]);
cnt[i][1] += lis.sta.size();
}
if(lis.empty()){
puts("NO"); return 0;
} else puts("YES");
int_ now = lis.front();
for(int i=n-2,sgn=1; i>=1; --i){
dif[i] = sgn*now;
for(; cnt[i][1]; --cnt[i][1])
lis.stepback();
if(lis.check(w[i]))
now = w[i], sgn = -sgn;
else if(lis.tiny(w[i]) && now == w[i])
now = lis.front(), sgn = -sgn;
else now = w[i]-now;
while(cnt[i][0] --) lis.stepback();
if(i == 1 && !(~sgn)) now = -now;
}
dif[0] = now; int_ low = 0;
rep(i,now=0,n-2) // find valley
low = min(low,now += dif[i]);
writeint(now = -low);
rep(i,0,n-2){
putchar(' ');
writeint(now += dif[i]);
}
putchar('\n');
return 0;
}
代码实现优化
直接用 d e q u e \mathrm{deque} deque 实现,比双向链表快多了。原因就在于不需要回退。
#include <bits/stdc++.h>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
typedef long long int_;
inline int_ readint(){
int_ a = 0; char c = getchar(), f = 1;
for(; c<'0'||c>'9'; c=getchar())
if(c == '-') f = -f;
for(; '0'<=c&&c<='9'; c=getchar())
a = (a<<3)+(a<<1)+(c^48);
return a*f;
}
inline void writeint(int_ x){
if(x < 0) putchar('-'), x = -x;
if(x > 9) writeint(x/10);
putchar((x-x/10*10)^48);
}
const int MaxN = 1000001;
struct Range{
int_ l, r;
Range(int_ L,int_ R){
l = L, r = R;
}
bool check(int_ x){
return l <= x && x <= r;
}
};
deque<Range> lis;
int sgn; int_ adv;
# define unzip(x) (sgn*(x)+adv)
# define dozip(x) (((x)-adv)*sgn)
bool tag[MaxN];
int_ dif[MaxN], w[MaxN], sy[MaxN];
int main(){
int n = readint(); readint();
lis.push_back(Range(0,1e18));
for(int i=sgn=1; i<=n-2; ++i){
w[i] = readint();
# define oneMax ((~sgn) ? lis.back().r : lis.front().l)
# define twoMax ((~sgn) ? lis.back().l : lis.front().r)
while(!lis.empty() && unzip(oneMax) > w[i])
if(unzip(twoMax) <= w[i]){
oneMax = dozip(w[i]); break;
} else if(~sgn) lis.pop_back();
else lis.pop_front();
# define meow(cat) cat.check(dozip(w[i]))
if(lis.empty() == false)
if(meow(lis.front()) or meow(lis.back())){
tag[i] = true, lis.clear();
lis.push_back(Range(0,w[i]));
sgn = 1, adv = 0; continue;
}
# define oneMin ((~sgn) ? lis.front().l : lis.back().r)
bool corner = !lis.empty()
and unzip(oneMin) != w[i];
sgn = -sgn, adv = -adv+w[i];
if(corner && unzip(oneMax) != w[i]){
if(sgn == -1)
lis.push_front(Range(
dozip(w[i]),dozip(w[i])));
else lis.push_back(Range(
dozip(w[i]),dozip(w[i])));
}
if(lis.empty()){
puts("NO"); return 0;
}
sy[i] = unzip(oneMin); // record
}
puts("YES"); // found a solution
int_ now = unzip(oneMin); // whatever
for(int i=(n-2)*(sgn=1); i>=1; --i){
dif[i] = now*sgn, sgn = -sgn;
if(tag[i]) now = w[i];
else if(now == w[i])
now = sy[i-1];
else{
now = w[i]-now;
sgn = -sgn;
}
}
dif[0] = now*sgn; int_ low = 0;
rep(i,now=0,n-2) // find valley
low = min(low,now += dif[i]);
writeint(now = -low);
rep(i,0,n-2){
putchar(' ');
writeint(now += dif[i]);
}
putchar('\n');
return 0;
}