题目传送门
欢迎光顾我的博客
我们考虑如何从左往右的进行填数操作。当我们填到位置 \(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(nlogn)\) 的,虽然不是很优,但是它无脑啊。
代码:
P14223
#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;
}