P14223 [ICPC 2024 Kunming I] 乐观向上

题目传送门

欢迎光顾我的博客

我们考虑如何从左往右的进行填数操作。当我们填到位置 \(i\) 时,\(p_{i}\) 这个数能被填进去需要满足的条件就是 \(p_{0} \oplus p_{1} \oplus \cdots \oplus p_{i-1} \neq p_{i}\)

先看看无解的情况。

我们经过一番思考后,发现无解的情况一定是填到最后一个数时填不进去,也就是 \(p_{0} \oplus p_{1} \oplus \cdots \oplus p_{n-2} = p_{n-1}\),即 \(p_{0} \oplus p_{1} \oplus \cdots \oplus p_{n-2} \oplus p_{n-1} = 0\)

这个也很好理解,因为如果从左往右填数时,只要不是第 \(n-1\) 位,其他位的备选项都有两个及以上,一个数如果填不进去总是会有另一个合法的数的。

然后就是有解的情况了。一种很直接的贪心思路就是,当填到第 \(i\) 位时,把那个最小的合法的数填进去。

但是这样就有一个问题:如果当前这个位置局部贪心填进去,是否会导致后续填数的时候无解?

当然是不会存在这种情况的,因为正如我们证明无解的情况一样,当你后续的备选项不止一个时,一个数不合适又会有另一个;当你后面只剩一个数时,也就是到了最后一位,直接填进去就好,因为这是有解的情况,最后一个数填进去一定合法。

而我们要求的是字典序最小,所以保障当前这一位最小是最重要的。综上,该种贪心策略是正确的。

然后就到了我们的乱搞时间

既然思路以及有了,那剩下的关键点就在于怎么找当前最小的合法的数了。

假设我们有一个数组 \(a\),下标为 \([1,n]\),我们让 \(a_{i}\) 初始等于 \(i-1\)

我们建一棵权值线段树,区间 \([l,r]\) 维护的是 \(a\) 数组对应区间的最小值。

若当前这个最小值合法,那么直接选走就行。

而当我们选择的最小数非法时,我们就去选次小值。具体来说,假设这个非法最小值是 \(num\),我们直接选 \(a\) 数组里 \([num+2,n]\) 这个区间的最小值。这个最小值一定是合法的。

(因为 \(num\) 这个数存在下标 \(num+1\) 处,现在我们不能选这个数了,所以下标还得加一)

每当我们选择一个数以后,就把 \(a\) 数组里这个数原本的位置置为 \(inf\),表示这个数被选走了。

这样我们的时间复杂度就是 \(O(n \log n)\) 的,虽然不是很优,但是它无脑啊。

代码:

#include<bits/stdc++.h>
#define int long long
#define ls (id<<1)
#define rs (id<<1|1)
using namespace std;

inline int read(){
	int x=0,f=1;char c=getchar();
	while(c<48){
		if(c=='-') f=-1;
		c=getchar();
	}
	while(c>47) x=(x<<1)+(x<<3)+(c^48),c=getchar();
	return x*f;
}

inline void write(int x){
	if(x<0) putchar('-'),x=-x;
	if(x<10) putchar(x+'0');
	else write(x/10),putchar(x%10+'0');
}

const int N=1e6+6;
const int inf=1e16;
int T,n,ans[N];
struct sw{
	int id,l,r,mn;
}tr[4*N];

inline void build(int id,int l,int r){
	tr[id].l=l,tr[id].r=r;
	if(l==r){
		tr[id].mn=l-1;
		return ;
	}
	int mid=(l+r)>>1;
	build(ls,l,mid);build(rs,mid+1,r);
	tr[id].mn=min(tr[ls].mn,tr[rs].mn);
}

inline void update(int id,int pos,int k){
	if(tr[id].l==pos&&tr[id].r==pos){
		tr[id].mn=k;
		return ;
	}
	int mid=(tr[id].l+tr[id].r)>>1;
	if(pos<=mid){
		update(ls,pos,k);
	}
	else{
		update(rs,pos,k);
	}
	tr[id].mn=min(tr[ls].mn,tr[rs].mn);
}

inline int query(int id,int l,int r){
	if(l<=tr[id].l&&tr[id].r<=r){
		return tr[id].mn;
	}
	int mid=(tr[id].l+tr[id].r)>>1,res=inf;
	if(l<=mid) res=min(res,query(ls,l,r));
	if(r>mid) res=min(res,query(rs,l,r));
	return res;
}

signed main(){
	T=read();
	while(T--){
		n=read();
		int dq=0;
		//dq:前面填过的所有数的区间异或值 
		build(1,1,n);
		int chk=0;
		//chk:0~n-1的连续异或值 
		for(int i=0;i<n;i++){
			chk^=i;
		}
		if(chk==0){//判断无解 
			printf("impossible\n");
			continue;
		}
		for(int i=0;i<n;i++){//填第i位 
			int num=query(1,1,n);//找最小值 
			if(num==dq){//当前最小值不合法,找次小值 
				int num2=query(1,num+2,n);
				dq^=num2;
				ans[i]=num2;
				update(1,num2+1,inf);
			}
			else{//直接填最小值 
				dq^=num;
				ans[i]=num;
				update(1,num+1,inf);
			}
		}
		for(int i=0;i<n;i++){
			printf("%lld ",ans[i]);
		}
		printf("\n");
	}
	return 0;
}
posted @ 2025-10-15 20:42  qwqSW  阅读(4)  评论(0)    收藏  举报