IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 数据结构与算法 -> [ROI2018]无进位加法 -> 正文阅读

[数据结构与算法][ROI2018]无进位加法

题目

题目背景
接上文。一块从天而降的巨石砸在 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 Li??3×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 bi??ai? 呗!盍自讨苦吃,让 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+1?v,则 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 n?Li?,其已被忽略。

上面的做法显然不够快。我们知道,在大小比较时,最高位起到决定性作用。于是我们设 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} 2ki??ai?<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(i?1+ki?) 。因为用掉前 ( i ? 1 ) (i{-}1) (i?1) 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(i?1+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 t?1+kt?=h0? 。由于处理 a t a_t at? 之前至少有 ( t ? 1 ) (t{-}1) (t?1) 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'} kt??1=h0??kt?,最多递归 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[Li?log(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;
}
  数据结构与算法 最新文章
【力扣106】 从中序与后续遍历序列构造二叉
leetcode 322 零钱兑换
哈希的应用:海量数据处理
动态规划|最短Hamilton路径
华为机试_HJ41 称砝码【中等】【menset】【
【C与数据结构】——寒假提高每日练习Day1
基础算法——堆排序
2023王道数据结构线性表--单链表课后习题部
LeetCode 之 反转链表的一部分
【题解】lintcode必刷50题<有效的括号序列
上一篇文章      下一篇文章      查看所有文章
加:2022-04-01 00:19:45  更:2022-04-01 00:20:50 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 9:41:42-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码