[CF1500F]Cupboards Jumps

题目

传送门 to CF

题目概要
序列 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 n106 w i ≤ 1 0 12 w_i\le 10^{12} wi1012 。提示:数据范围恰好使得 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,x2x3)

而规定第一个数字为零,本质上是 差分。记 d i = h i + 1 − h i d_i=h_{i+1}-h_i di=hi+1hi,式子改写为
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)(xwi)

或者是上一个的值,当前值取负数就行。
g ( x ) = f ( w i )    ( x ≤ w i ) g(x)=f(w_i)\;(x\le w_i) g(x)=f(wi)(xwi)

要不然就是二者的和,二者都取正数。
g ( x ) = f ( w i − x )    ( x ≤ w i ) g(x)=f(w_i-x)\;(x\le w_i) g(x)=f(wix)(xwi)

显然它不足以在十年之内跑出来。 考虑继续优化。

想起一道经典的题目(我找不到博客了,直接贴题面吧):

有一棵树,每个点是黑色、白色或无色,请问能否找到一个连通块,恰好有 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(xwi),这一层加入 [ 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(wix)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(wir,0),wil]

显然每一层最多增加一个区间,即 [ 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 wir0 的部分截断,那不如先截断 r ≥ w i r\ge w_i rwi 的部分再翻转。而截断之后,查询 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 wix 得到的,那就直接这样交替选择呗。

特例一是 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;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值