[硫化铂]等待

等待

题目概述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

题解

首先,我们 ∑ i = 1 n x i = ⊕ i = 1 n x i \sum_{i=1}^nx_i=\oplus_{i=1}^nx_i i=1nxi=i=1nxi这个条件,实际上是等价于每一位上有值的数最多一个。
如果出现多个的话,至少会有一个被消耗掉,而我们的异或操作不可能凭空产生值,所以唯有都不重复时才能满足条件。
那么我们现在相当于通过 + 1 +1 +1操作,使得每个 1 1 1的最多出现在一个位置。

对于单个数,我们怎么操作才是最优的呢?
我们应该会保留它前面的一些 1 1 1不变,然后后面某一个 0 0 0变成 1 1 1,之后就都是 0 0 0了。
我们加的目的就是为了让它后面的 1 1 1全部都转移到这个 0 0 0身上,之后如果我再加肯定是不优的了,除非我要将这个 1 1 1继续往前移动。
所以我们可以去枚举每个数做出改变的 1 1 1的位置,然后判定。但这不就是指数级复杂度了吗?
我们不如直接枚举每个位置的 1 1 1的分配,容易发现,我们将这个 1 1 1分配个还未被分配的这个 bit 位开始在后面最大的数一定是最优的。
因为在这种情况下,我覆盖较大数的方案一定也可以在后面覆盖小的那个数,所以选最大数一定不会劣与小的数。

所以对于一个给定的 0 / 1 0/1 0/1序列,我们判定它是否合法可以用贪心的方法。
假设我们已经枚举到了位置 i i i,前面的 0 / 1 0/1 0/1都完成了其的分配。
那么如果这个位置上序列中为 0 0 0,并且这位置上,有未被覆盖的数为 1 1 1,显然就无解了。
如果序列中为 1 1 1,并且有超过一个未被覆盖的是为 1 1 1,那也无解了。
如果只有一个,那么这个 1 1 1只能与该数这个位置的 1 1 1匹配,不能继续往后覆盖。
如果这位置上没有 1 1 1,那么这个 1 1 1就可以用去覆盖其它的数,我们从所有未被覆盖的数的后缀中选择最大的一个后缀覆盖,被覆盖的数就消失了,不会在之后产生贡献。

有了上面的判定方法,我们就很容易想到通过枚举每一位的选择情况来求出最小值。
对于每一位,我们就考虑将这一位赋为 0 0 0,后面的全为 1 1 1时,是否能有解,因为这已经是当前的最优情况了,如果无解,这位就只能为 1 1 1了。
当然,这样的做法是 O ( ( n + ∑ L ) 2 ) O\left((n+\sum L)^2\right) O((n+L)2)的,不太能过的样子。
我们当然要想去优化这个做法。

我们先考虑将所有数都只保留最高位,假设最高位的数列为 a ′ a' a,并将其从大到小,我们记 B = max ⁡ i = 1 n a i ′ + i − 1 B=\max_{i=1}^na'_i+i-1 B=maxi=1nai+i1
我们容易发现,我们之后序列的最高为 1 1 1位,要么是 B B B,要么是 B + 1 B+1 B+1
通过我们上面构造的取法可以知道,较小的数取的第一个 1 1 1是在较大的数的第一个 1 1 1右边,而当取到 a i ′ a'_i ai时前面已经去了 i − 1 i-1 i1个,所有取了它的 1 1 1后,我们前面取的第一个 1 1 1一定至少在 B B B处。
如果我们把整个 a ′ a' a都向左平移一位,那么它的解必定也是原来 a a a的一组解,由于这个 a ′ a' a是整体大于 a a a的,所以 a a a的解不可能劣于 a ′ a' a的最优解。
所以 a ′ a' a解的第一个 1 1 1,要么是 B B B,要么是 B + 1 B+1 B+1

我们记第一个 a i ′ + i − 1 a'_i+i-1 ai+i1取到 B B B的点为 k k k,那么在 i ∈ [ 1 , k ) i\in[1,k) i[1,k)间的 a i ′ a'_i ai,必定有 a i ′ + i − 1 < B a'_i+i-1<B ai+i1<B
也就是说,我们用 B − i + 1 B-i+1 Bi+1就可以覆盖整个数。
那么我们必定要用 [ B − k + 2 , B ] [B-k+2,B] [Bk+2,B]中的数,去覆盖这些数,而之后的 B − k + 1 B-k+1 Bk+1刚好就是 a k ′ a'_k ak的最高位。
所以我们枚举的其实是,我们使用 B − k + 1 B-k+1 Bk+1去去掉 a k ′ a'_k ak的最高位,还是用 B + 1 B+1 B+1去覆盖 a k ′ a'_k ak整个数。
我们不妨也用上面的尝试法,先去尝试选择 B − k + 1 B-k+1 Bk+1,看能不能构造出解。
事实上,我们总时间复杂度相当于我们一次成功尝试的复杂度与所有失败尝试的复杂度,而我们上面的 B B B,是很好通过一个平衡树维护的。
我们成功尝试的复杂度就是 O ( ( max ⁡ L + n ) log ⁡ n ) O\left((\max L+n)\log n\right) O((maxL+n)logn)的,但我们失败尝试的复杂度该怎么算。
我们发现,当我们选择 B − k + 1 B-k+1 Bk+1,往下迭代时,我们下面有三种情况:

  • 之后的 B ′ > B − k + 1 B'>B-k+1 B>Bk+1,显然无解。
  • 之后的 B ′ = B − k + 1 B'=B-k+1 B=Bk+1,显然我们只能继续迭代下去,只有一条路可走。
  • 之后的 B ′ < B − k + 1 B'<B-k+1 B<Bk+1,显然是一定有解的,也就是说我们的上一次操作不可能失败,失败的只可能是我们在这个点上新尝试的路径。

显然,每次的失败最多是从某个点走到底,而由于尝试这个点时已经将之后的枚举长度压缩到某个 L k L_k Lk,所以之后最多删除 L k L_k Lk次,产生时间复杂度 O ( L k log ⁡ n ) O\left(L_k\log n\right) O(Lklogn)
如果这个点失败了,他就会被完全覆盖掉,消失了,之后不会产生贡献。
所以即使将每个点的失败贡献加起来,也就 O ( ∑ L log ⁡ n ) O\left(\sum L \log n\right) O(Llogn)
失败后我们就需要将原先尝试的路径复原,由于我们每次尝试也就是新建一个点加入平衡树内,只要将新建的点删掉,将原来分裂开的树合并即可。
显然走回来的这部分时间复杂度与走过去是一样的。

总时间复杂度 O ( ∑ L log ⁡ n ) O\left(\sum L\log n\right) O(Llogn)

源码

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<int,int> pii;
#define MAXN 300005
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s<'0'||s>'9'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*a*t%p;a=1ll*a*a%p;s>>=1;}return t;}
int n,len,siz[MAXN],maxx,nowlen,root;
vector<int>arr[MAXN],ord[MAXN][2],sum[MAXN],id[MAXN];
char str[MAXN];bool vis[MAXN],ans[MAXN<<1];
priority_queue<int,vector<int>,greater<int> >q;
struct ming{
	int lson,rson,rnd,val,lzy,maxx,id,siz;
	ming(){lson=rson=rnd=val=lzy=maxx=id=siz=0;}
};
bool cmp(int x,int y){
	if(siz[x]>nowlen&&siz[y]>nowlen)
		return id[x][nowlen]<id[y][nowlen];
	if(siz[x]>nowlen&&sum[x][nowlen]>sum[x][siz[y]-1])return 1;
	if(siz[y]>nowlen&&sum[y][nowlen]>sum[y][siz[x]-1])return 0;
	if(siz[x]<=nowlen&&siz[x]>siz[y])return 1;
	if(siz[y]<=nowlen&&siz[y]>siz[x])return 0;
	int t=min(siz[x],siz[y])-1;
	return id[x][t]<id[y][t];
}
class FHQ_Treap{
	private:
		ming tr[MAXN<<2];int tot;
		void work(int x,int w){if(!x)return ;tr[x].val+=w;tr[x].lzy+=w;tr[x].maxx+=w;}
		void pushdown(int rt){
			if(tr[rt].lzy){
				if(tr[rt].lson)work(tr[rt].lson,tr[rt].lzy);
				if(tr[rt].rson)work(tr[rt].rson,tr[rt].lzy);
				tr[rt].lzy=0;
			}
		}
		void pushup(int rt){
			tr[rt].siz=tr[tr[rt].lson].siz+tr[tr[rt].rson].siz+1;
			tr[rt].maxx=tr[rt].val;
			if(tr[rt].lson)tr[rt].maxx=max(tr[rt].maxx,tr[tr[rt].lson].maxx);
			if(tr[rt].rson)tr[rt].maxx=max(tr[rt].maxx,tr[tr[rt].rson].maxx);
		}
		int newnode(int v,int w){
			int rt=++tot;tr[rt].id=w;tr[rt].siz=1;
			tr[rt].val=tr[rt].maxx=v;tr[rt].rnd=rand();
			return rt;
		}
	public:
		int merge(int x,int y){
			if(!x||!y)return x+y;pushdown(x);pushdown(y);
			if(tr[x].rnd>tr[y].rnd){
				tr[x].rson=merge(tr[x].rson,y);
				pushup(x);return x;
			}
			tr[y].lson=merge(x,tr[y].lson);
			pushup(y);return y;
		}
		void splitSiz(int now,int k,int &x,int &y){
			if(!now){x=y=0;return ;}pushdown(now);
			if(tr[tr[now].lson].siz>=k)
				y=now,splitSiz(tr[y].lson,k,x,tr[y].lson),pushup(y);
			else x=now,splitSiz(tr[x].rson,k-tr[tr[x].lson].siz-1,tr[x].rson,y),pushup(x);
		}
		void splitVal(int now,int k,int &x,int &y){
			if(!now){x=y=0;return ;}pushdown(now);
			if(cmp(tr[now].id,k))
				x=now,splitVal(tr[x].rson,k,tr[x].rson,y),pushup(x);
			else y=now,splitVal(tr[y].lson,k,x,tr[y].lson),pushup(y);
		}
		int query(int rt,int k){
			if(!rt)return 0;pushdown(rt);
			if(tr[rt].lson&&tr[tr[rt].lson].maxx==k)return query(tr[rt].lson,k);
			if(tr[rt].val==k)return tr[tr[rt].lson].siz+1;
			return tr[tr[rt].lson].siz+1+query(tr[rt].rson,k);
		}
		void insert(int pos,int w){
			int x,y;splitVal(root,pos,x,y);work(y,1);
			root=merge(merge(x,newnode(w+tr[x].siz,pos)),y);
		}
		int ask(int rt){return tr[rt].maxx;}
		bool solve(bool typ){
			int tmp=ask(root),t=query(root,tmp);
			int x1,y1,x2,y2,x3,y3,x4,y4;splitSiz(root,t,x1,y1);
			work(y1,-tr[x1].siz);splitSiz(x1,t-1,x2,y2);
			int k=tmp-tr[x2].siz,laslen=nowlen,td=tr[y2].id;
			nowlen=k-1;int tp=-1,ct;
			for(int i=nowlen;i>=0;i--)if(arr[td][i]){tp=i;break;}
			if(tp>=0){
				splitVal(y1,td,x3,y3);work(y3,1);ct=tr[x3].siz;
				root=merge(merge(x3,newnode(tp+ct,td)),y3);
			} 
			else root=y1;
			if(!root){for(int i=tmp;i>=k;i--)ans[i]=1;return 1;}
			int tmax=ask(root);
			if(tmax<nowlen){solve(1);for(int i=tmp;i>=k;i--)ans[i]=1;return 1;}
			if(tmax==nowlen&&solve(0)){for(int i=tmp;i>=k;i--)ans[i]=1;return 1;}
			if(tp>=0){
				splitSiz(root,ct,x3,y3);
				splitSiz(y3,1,x4,y4);work(y4,-1);
				y1=merge(x3,y4);
			}
			work(y1,tr[x2].siz+tr[y2].siz);
			root=merge(merge(x2,y2),y1);nowlen=laslen;
			if(!typ)return 0;splitSiz(root,t,x1,y1);
			work(y1,-tr[x1].siz);root=y1;nowlen=k;
			if(root)solve(ask(root)<nowlen);
			for(int i=tmp+1;i>k;i--)ans[i]=1;
			return 1;
		}
}T;
int main(){
	freopen("wait.in","r",stdin);
	freopen("wait.out","w",stdout);
	read(n);
	for(int i=1;i<=n;i++){
		scanf("\n%s",str+1);len=(int)strlen(str+1);
		for(int j=len;j>0;j--)
			arr[i].pb(str[j]-'0'),str[j]=0;
		siz[i]=arr[i].size();
		sum[i].resize(siz[i]);id[i].resize(siz[i]);
		sum[i][0]=arr[i][0];
		for(int j=1;j<siz[i];j++)
			sum[i][j]=sum[i][j-1]+arr[i][j];
	}
	for(int i=1;i<=n;i++)maxx=max(maxx,siz[i]);
	for(int i=1;i<=n;i++)ord[0][arr[i][0]].pb(i);
	for(int i=1;i<maxx;i++){
		int siz0=ord[i-1][0].size();
		for(int j=0;j<siz0;j++){
			int x=ord[i-1][0][j];
			if(siz[x]>i)ord[i][arr[x][i]].pb(x);
		}
		int siz1=ord[i-1][1].size();
		for(int j=0;j<siz1;j++){
			int x=ord[i-1][1][j];
			if(siz[x]>i)ord[i][arr[x][i]].pb(x);
		}
	}
	for(int i=0;i<maxx;i++){
		int siz1=ord[i][1].size(),idx=0;
		for(int j=siz1-1;j>=0;j--)
			id[ord[i][1][j]][i]=++idx;
		int siz0=ord[i][0].size();
		for(int j=siz0-1;j>=0;j--)
			id[ord[i][0][j]][i]=++idx;
	}
	nowlen=maxx+n-1;
	for(int i=1;i<=n;i++)T.insert(i,siz[i]-1);
	T.solve(1);bool flag=0;
	for(int i=maxx+n-1;i>=0;i--)
		if(ans[i])putchar('1'),flag=1;
		else if(flag)putchar('0');
	puts("");
	return 0;
}

谢谢!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值