人生第一道蓝交互。
观察数据范围,可以考虑数据点分治,分别解决各个 Subtask。
Subtask 1 & 2
由于颜色块连续,当 query(l,r)==1 时必有 \([l,r]\) 的小球颜色全部相同。
于是双指针扫一遍即可,\(l\) 为当前颜色段起点,\(r\) 一直向右扫到 query(l,r)>1。
cpp
namespace Subtask12{
void solve(){
int l=1,r=2,cnt=1;
while(l<=n){
while(r<=n&&query(l,r)==1) ++r;
rep(i,l,r) ans[i]=cnt;
++cnt,l=r;
}
cout<<"! ";
rept(i,1,n) cout<<ans[i]<<' ';
cout<<endl;
}
}
Subtask 3 & 4
发现颜色数量不会超过 \(4\) 种。这里只需要考虑 \(4\) 种颜色的情况。
可以维护每种颜色最后出现的位置,按照最后出现的顺序排序,然后分讨即可,细节比较多。
cpp
namespace Subtask34{
void solve(){
int lst[5]={},ord[5];
// lst[i]:颜色i上一次出现的位置
rept(i,1,4) ord[i]=i;
rept(i,1,n){
sort(ord+1,ord+5,[lst](int x,int y)->bool{return lst[x]>lst[y];});
int a=ord[1],b=ord[2],c=ord[3],d=ord[4];
if(!lst[a]) ans[i]=1;
else if(!lst[b]){
if(query(lst[a],i)==1) ans[i]=a;
else ans[i]=b;
}else if(!lst[c]){
if(query(lst[b],i)==3) ans[i]=c;
else if(query(lst[a],i)==1) ans[i]=a;
else ans[i]=b;
}else{
// 可以通过二分减少询问次数
if(query(lst[b],i)==2){
if(query(lst[a],i)==1) ans[i]=a;
else ans[i]=b;
}else{
if(query(lst[c],i)==3) ans[i]=c;
else ans[i]=d;
}
}
lst[ans[i]]=i;
}
cout<<"! ";
rept(i,1,n) cout<<ans[i]<<' ';
cout<<endl;
}
}
Subtask 5
先判断总颜色数量。
若为 \(N\),做法显然。
若为 \(N-1\),则有且仅有一对同色的球。
我们注意到如果 query(l,r)==r-l,则这对球一定在 \([l,r]\) 中。
因此从 \([1,N]\) 开始逐渐收紧区间就能找出同色球的位置。
cpp
namespace Subtask5{
void solve(){
int k=query(1,n);
if(k==n){
cout<<'!';
rept(i,1,n) cout<<' '<<i;
cout<<endl;
return;
}
int l=1,r=n,cnt=1;
while(l<=r&&query(l,r)==r-l) ++l;
--l;
while(l<=r&&query(l,r)==r-l) --r;
++r;
cout<<"! ";
rept(i,1,n){
if(i==l||i==r) cout<<n<<' ';
else cout<<cnt++<<' ';
}
cout<<endl;
}
}
Subtask 6 & 7
注意到,如果 query(l,r)==query(l+1,r),则 \([l+1,r]\) 中一定有与 \(l\) 同色的球。
有了这个,我们就能二分出每个球右边第一个与它同色的球的位置 \(nxt_i\),进而求出每个球的颜色。
cpp
namespace Subtask67{
int nxt[N];
// nxt[i]:i右边第一个与i同色的球位置
void solve(){
int cnt=1,p;
rept(i,1,n){
int l=i+1,r=n+1,mid;
while(l<r){
mid=l+r>>1;
if(query(i,mid)==query(i+1,mid)) r=mid;
else l=mid+1;
}
nxt[i]=l;
}
rept(i,1,n){
if(!ans[i]){
for(int j=i;j<=n;j=nxt[j]) ans[j]=cnt;
++cnt;
}
}
cout<<"! ";
rept(i,1,n) cout<<ans[i]<<' ';
cout<<endl;
}
}
然而这样的询问次数大约是 \(2N\log_2 N\approx 20000\),过不了 Subtask 7。需要想办法去掉一次 query。
发现 query(i,mid) 不好省掉,考虑省掉 query(i+1,mid)。
仔细观察 query(i+1,mid),发现这个东西等于 \(mid-i-\sum_{k=i+1}^{mid}[nxt_k\le mid]\)。
于是从 \(N\) 到 \(1\) 倒着计算 \(nxt_i\),用树状数组维护和式里面的部分即可。
询问次数 \(N\log_2 N\),时间复杂度 \(O(N \log^2 N)\),目前最优解。
cpp
namespace Subtask67{
int nxt[N],s[N];
void add(int p,int x){
while(p<=n){
s[p]+=x;
p+=p&-p;
}
}
int ask(int p){
int res=0;
while(p){
res+=s[p];
p&=p-1;
}
return res;
}
void solve(){
pert(i,n,1){
int l=i+1,r=n+1,mid;
while(l<r){
mid=l+r>>1;
if(query(i,mid)==mid-i-(ask(mid)-ask(i))) r=mid;
else l=mid+1;
}
nxt[i]=l;
if(nxt[i]<=n) add(nxt[i],1);
}
int cnt=1;
rept(i,1,n){
if(!ans[i]){
for(int j=i;j<=n;j=nxt[j]) ans[j]=cnt;
++cnt;
}
}
cout<<"! ";
rept(i,1,n) cout<<ans[i]<<' ';
cout<<endl;
}
}