[ROI2018]无进位加法

280 篇文章 1 订阅
89 篇文章 0 订阅

题目

题目背景
接上文。一块从天而降的巨石砸在 O n e I n D a r k \sf OneInDark OneInDark 身上。只剩下左半边脸露在外面。

“我没什么可送你的,只能送你……”

“十拳剑封印!” 从暗处出现的 D D ( X Y X ) \sf DD(XYX) DD(XYX) 忽然一刀结束了 O n e I n D a r k \sf OneInDark OneInDark 报团取暖的人生。“原谅我,小卷。我将作为你需要越过的标杆,永远地陪伴着你!”

真相却依旧在电线杆之上。电线杆上是 D D G \sf DDG DDG 的爪印,和四个未曾褪色的金字——

非吾邮箱!”

题目描述
传送门 to LOJ:给出序列 { a i } \{a_i\} {ai},每次操作可以给某个 a i a_i ai 1 1 1 。求最小操作次数使得 ∑ a i = ⨁ a i \sum a_i=\bigoplus a_i ai=ai 。输出以二进制形式给出。

数据范围与约定
L i L_i Li a i a_i ai 在二进制下的位数,则 ∑ L i ⩽ 3 × 1 0 5 \sum L_i\leqslant 3\times 10^5 Li3×105

思路

经过求证,总算拼凑出了这篇完整的题解。

我一直思考怎样让 a i a_i ai 增加,就全完蛋了。二分答案 的优点就在于 只问结果,不看过程

假设我们已知 χ = ∑ b i \chi=\sum b_i χ=bi,那么 { a i } \{a_i\} {ai} 变化后得到的序列 { b i } \{b_i\} {bi} 是对 χ \chi χ 的二进制位拆分。而合法的条件就是 b i ⩾ a i b_i\geqslant a_i biai 呗!盍自讨苦吃,让 a i a_i ai 一点点增大?

于是我们看出 χ 1 ⫅ χ 2 \chi_1\subseteqq \chi_2 χ1χ2 时, χ 2 \chi_2 χ2 更可能合法,因为对应的 b i b_i bi 只会更大。所以二分答案法是可行的——从高位开始,逐个确定 χ \chi χ b i t \rm bit bit,检验只需令后面的 b i t \rm bit bit 为全 1 1 1

怎样检验呢?找到 χ \chi χ 最高二进制位 p p p 。再找到 v = max ⁡ a i v=\max a_i v=maxai 。若 2 p + 1 ⩽ v 2^{p+1}\leqslant v 2p+1v,则 v v v 不可能对应合法 b i b_i bi,失败。若 2 p > v 2^p>v 2p>v,则该位单独作为某个 b i b_i bi 时,就可以与任意剩余 a i a_i ai 匹配,贪心地想肯定是与 v v v 匹配;将 v v v 删掉,将 2 p 2^p 2p χ \chi χ 中移除,继续进行该匹配。

v ∈ [ 2 p , 2 p + 1 ) v\in[2^p,2^{p+1}) v[2p,2p+1),则 2 p 2^p 2p 必须分配给 v v v 对应的 b i b_i bi,并且这还不够用。将 v v v 的最高位,即 2 p 2^p 2p,与 χ \chi χ 中的 2 p 2^p 2p 一同移除,然后继续进行该匹配。

那么过程中, a i ′ a'_i ai 会是 a i a_i ai 的一个后缀(较低的若干 b i t \rm bit bit 位)。为了找 max ⁡ \max max,我们需要预处理其大小关系。从低位到高位做基数排序,就能确定相同长度的后缀之间的大小关系,于是可以 O ( ∑ L i ) \mathcal O(\sum L_i) O(Li) 进行预排序。——由于 n ⩽ ∑ L i n\leqslant\sum L_i nLi,其已被忽略。

上面的做法显然不够快。我们知道,在大小比较时,最高位起到决定性作用。于是我们设 k i k_i ki a i a_i ai 的最高位,即 2 k i ⩽ a i < 2 k i + 1 2^{k_i}\leqslant a_i<2^{k_i+1} 2kiai<2ki+1,考虑能不能搞出些幺蛾子。

答案的下界是 a i = 2 k i a_i=2^{k_i} ai=2ki 。不言而喻的前提是 a i a_i ai 减小(或增大)不会得到更劣(或更优)的解。记 h h h χ \chi χ 的最高 b i t \rm bit bit,设 { a i } \{a_i\} {ai} 单调递减,我们震惊地发现 h = max ⁡ ( i − 1 + k i ) h=\max(i{-}1{+}k_i) h=max(i1+ki) 。因为用掉前 ( i − 1 ) (i{-}1) (i1) 1 1 1 b i t \rm bit bit 后,第 i i i 个的位置不能矮于 k i k_i ki 。归纳法可知其可实现。

答案的上界是 a i = 2 k i + 1 a_i=2^{k_i+1} ai=2ki+1 。类型完全相同!此时 h = max ⁡ ( i + k i ) h=\max(i{+}k_i) h=max(i+ki),也就是上面的那个 h h h + 1 +1 +1

回到原问题。记 h 0 = max ⁡ ( i − 1 + k i ) h_0=\max(i{-}1{+}k_i) h0=max(i1+ki),我们只需要检验 h = h 0 h=h_0 h=h0 是否可行,不可行则选取 h = h 0 + 1 h=h_0{+}1 h=h0+1 。也就是说,我们可以直接造出最优解,所谓二分答案的过程则被废弃了。

考虑 h 0 h_0 h0 究竟是谁的限制:找到最小的 t t t 使得 t − 1 + k t = h 0 t{-}1{+}k_t=h_0 t1+kt=h0 。由于处理 a t a_t at 之前至少有 ( t − 1 ) (t{-}1) (t1) b i t \rm bit bit 会被直接用掉,所以必须是 [ k t , h 0 ] [k_t,h_0] [kt,h0] 这些 b i t \rm bit bit 为全 1 1 1 。但同时我们又发现, t t t 是最小足以保证这些 b i t \rm bit bit 恰好让那些 a i    ( i < t ) a_i\;(i<t) ai(i<t) 完成了匹配!

于是 a t a_t at k t k_t kt 上的 b i t \rm bit bit 匹配,得到 a t a_t at 移除 2 k t 2^{k_t} 2kt 的结果。将 a i    ( i < t ) a_i\;(i<t) ai(i<t) 移除,剩下的数字只能递归了。为了加速判断,我们同样先求出一个估计值 h 0 ′ h_0' h0,则实际值 h ′ ∈ { h 0 ′ , h 0 ′ + 1 } h'\in\{h_0',h_0'{+}1\} h{h0,h0+1} 。合法的条件显然是 k t > h ′ k^t>h' kt>h,互不干扰嘛。可以发现只有 k t = h 0 ′ + 1 k_t=h_0'{+}1 kt=h0+1 时,合法性不确定,需要再顺着 t ′ t' t 递归检验。

我们还能窥见, h = h 0 + 1 h=h_0{+}1 h=h0+1 时,必须是 ( k t + 1 , h ] (k_t{+}1,h] (kt+1,h] 这些 b i t \rm bit bit 为全 1 1 1 。因为其他情况其实都递归到上面的 χ ′ , h ′ \chi',h' χ,h 答案;该情况却能让 a t a_t at 直接获得匹配,因而消失。所以求出 h h h 之后,我们就能造出最优解了。

时间复杂度是什么呢?若 h = h 0 h=h_0 h=h0 成功,则递归过程没有回溯,每次至少将 a t a_t at 的一个 b i t \rm bit bit 移除。若 h = h 0 h=h_0 h=h0 失败,由于 k t − 1 = h 0 ′ ⩾ k t ′ k_t{-}1=h_0'\geqslant k_{t'} kt1=h0kt,最多递归 O ( k t ) \mathcal O(k_t) O(kt) 次,然后会将 a t a_t at 移除。

于是记势能 Φ = ∑ k i \Phi=\sum k_i Φ=ki,则两种情况的操作次数 ⩽ \leqslant 势能减小量 Δ Φ \Delta\Phi ΔΦ 。所以总次数和就是 O ( Φ 0 ) = O ( ∑ L i ) \mathcal O(\Phi_0)=\mathcal O(\sum L_i) O(Φ0)=O(Li)

过程中 疯狂地 使用线段树或堆或平衡树等维护 max ⁡ \max max,总复杂度 O [ ∑ L i log ⁡ ( ∑ L i ) ] \mathcal O\big[\sum L_i\log(\sum L_i)\big] O[Lilog(Li)]

代码

看上去还蛮简单的。

#include <cstdio> // megalomaniac JZM yydJUNK!!!
#include <iostream> // Almighty XJX yyds!!!
#include <algorithm> // XYX yydLONELY!!!
#include <cstring> // XEZ yydSON & SY yydMT!!!
#include <cctype> // oracle: ZXY yydBUS!!!
#include <vector>
typedef long long llong;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
# define rep0(i,a,b) for(int i=(a); i!=(b); ++i)
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar()) if(c == '-') f = -f;
	for(; isdigit(c); c=getchar()) a = a*10+(c^48);
	return a*f;
}

const int MAXN = 300005;
std::vector<bool> num[MAXN];
int tmp[2][MAXN], id[MAXN], k[MAXN];
int nxt[MAXN], lst[MAXN];
int radix_sort(int len){
	rep(i,1,len) id[i] = i, lst[i] = -1;
	for(int j=0,tot=0; true; ++j){
		if(len == 0) return tot;
		int* const _end = id+len+1;
		int *ptr[2] = {tmp[0],tmp[1]};
		for(int *i=id+1; i!=_end; ++i)
			if(int(num[*i].size()) != j)
				*(ptr[num[*i][j]] ++) = *i;
		rep(p,len=0,1) for(int *i=tmp[p]; i!=ptr[p]; ++i){
			id[++ len] = *i; if(!p) continue;
			nxt[tot+1] = lst[*i], k[lst[*i] = ++ tot] = j;
		}
	}
}

namespace zkw{
	int val[MAXN*3], id[MAXN*3];
	int siz[MAXN*3], ass, n;
	inline void pushUp(const int &o){
		siz[o] = siz[o<<1]+siz[o<<1|1];
		if(val[o<<1]+siz[o<<1|1] > val[o<<1|1])
			val[o] = val[o<<1]+siz[o<<1|1], id[o] = id[o<<1];
		else val[o] = val[o<<1|1], id[o] = id[o<<1|1];
	}
	void build(const int &_n){
		for(ass=1,n=_n; ass<n+2; ass<<=1);
		rep(i,1,n) id[ass+i] = i;
		memset(val+1,-1,(ass+n+1)<<2);
	}
	void insert(const int &x, const int &v){
		++ siz[x+ass], val[x+ass] = v;
		for(int i=x+ass; i>>=1; ) pushUp(i);
	}
	void erase(const int &x){
		-- siz[x+ass], val[x+ass] = -1;
		for(int i=x+ass; i>>=1; ) pushUp(i);
	}
	int query(int &rh){
		int l = ass, r = ass+n+1, rsiz = 0;
		rh = -1; int rid = 0, lh = -1, lid = 0;
		for(; (l^r)!=1; l>>=1,r>>=1){
			if(val[l^1] >= (lh += siz[l^1]))
				lh = val[l^1], lid = id[l^1];
			if(!(r&1)) continue; // do nothing
			if(val[r^1]+rsiz > rh) // strictly
				rh = val[r^1]+rsiz, rid = id[r^1];
			rsiz += siz[r^1]; // on the right
		}
		if(rh >= (lh += rsiz)) return rid;
		rh = lh; return lid; // better
	}
	bool empty(){
		int l = ass, r = ass+n+1;
		for(; (l^r)!=1; l>>=1,r>>=1){
			if(!(l&1) && siz[l^1]) return false;
			if((r&1) && siz[r^1]) return false;
		}
		return true; // nothing
	}
}

bool ans[MAXN<<1];
bool check(const int &want){
	if(zkw::empty()) return true; // great job
	int h0, t = zkw::query(h0);
	if(h0 != want) return h0 < want;
	const int now_n = zkw::n;
	zkw::n = t-1; // remove bigger ones
	if(~nxt[t]) zkw::insert(nxt[t],k[nxt[t]]);
	if(check(k[t]-1)){ // succeed
		memset(ans+k[t],true,h0-k[t]+1);
		return true; // get answer meanwhile
	}
	if(~nxt[t]) zkw::erase(nxt[t]);
	zkw::n = now_n; return false;
}
void solve(){
	while(!zkw::empty()){
		int h0, t = zkw::query(h0);
		if(check(h0)) continue; // done
		memset(ans+k[t]+1,true,h0-k[t]+1);
		zkw::n = t-1; // permanent modification	
	}
}

char str[MAXN];
int main(){
	int n = readint();
	for(int i=1,len; i<=n; ++i){
		scanf("%s",str), len = int(strlen(str));
		num[i].resize(len); rep0(j,0,len)
			num[i][j] = str[len-1-j]^48;
	}
	zkw::build(radix_sort(n));
	rep(i,1,n) zkw::insert(lst[i],k[lst[i]]);
	solve(); int len = (MAXN<<1)-1;
	while(!ans[len]) -- len; // leading zero
	drep(i,len,0) putchar(ans[i] ? '1' : '0');
	putchar('\n'); return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值