人生第一道蓝交互。
观察数据范围,可以考虑数据点分治,分别解决各个 Subtask。
Subtask 1 & 2
由于颜色块连续,当 query(l,r)==1 时必有 \([l,r]\) 的小球颜色全部相同。
于是双指针扫一遍即可,\(l\) 为当前颜色段起点,\(r\) 一直向右扫到 query(l,r)>1。
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\) 种颜色的情况。
可以维护每种颜色最后出现的位置,按照最后出现的顺序排序,然后分讨即可,细节比较多。
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]\) 开始逐渐收紧区间就能找出同色球的位置。
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\),进而求出每个球的颜色。
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)\),目前最优解。
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;}
}
完整代码