题目链接
题目大意
交互题。
一个环上有 (n) 个位置,开始你要放 (frac{n}{4}) 个盒子上去,接下来系统再放 (frac{n}{4}) 个盒子,每次系统给出 (b_i),要求在第 (b_i) 个插入的盒子后面插入一个盒子,由于后面可能没有空,你需要通过若干次调整腾出空来,每次调整可以把一个盒子推到别的地方,但是需要推的路径上没有别的盒子。调整后指定一个在 (b_i) 和下一个盒子之间的位置插入新的盒子。
要求每次插入盒子前,你做的调整次数 (leq 200) 。
(n=20000)
思路
首先可以注意到不能有很多的盒子贴在一起,若贴在一起的盒子超过 (400) 个,那么系统要求在中间插一个盒子就完蛋了,于是考虑分组,要求贴在一起的盒子数量不超过定值 (B),总共分 (frac{n}{2B}) 组。
题目有一个极端情况,即一直在第一个盒子后面插入新盒子,所以要想办法把插在那个组里的盒子分流到其它组中,可以这么做:当一个组盒子数量超过 (B) 时,把边上的一个盒子扔到别的组里,如果那个组也超 (B) 个了,就按照方向继续扔盒子。在组内部从两个方向腾出一个位置总共需要调整 (B) 次,在扔盒子直到遇到第一个放完后不超过 (B) 个的组的过程中总共需要调整 (frac{n}{2B}) 次,所以必然可以选取一个合适的方向使得调整的次数 (leq (B+frac{n}{2B})/2),取 (B=sqrt{frac{n}{2}}=100),则这里可以调整不超过 (100) 次。
观察调整的变化情况,以向右(顺时针)扔为例,可以发现相当于从当前组到第一个 (leq B) 组之间的组都左移了一格,由于第一个 (leq B) 的那个组左边多了一个盒子,所以只有当前组和右边相邻组之间的间距减小了,现在还剩 (100) 次调整次数,可以将一整个组进行平移,考虑用这个操作使得组与组之间不贴在一起。
开始的时候我们按照 (50) 个一组,等间距地放在环上,设两组之间空隙的长度为 (a_i),那么我们初始有一个长度为 (m=100),(a_i) 全为 (150) 的的序列 (a),每次有一个 (a_i) 减一,你可以选取一个 (jin[1,m],kinmathbb Z),使得 (a_j+k),(a_{j+1}-k)(下标循环定义)。
我们每次选取最大的值 (a_i), 将其减小到 (100),然后使 (a_{i-1}) 相应地增加。由于最终空隙数量 (=n/2=100m),所以 (a_igeq 100),此时相当于最大值的位置左移 (1) 到 (a_{i-1}) 上了,可以发现这样一个过程会一直进行,可以发现在 (100) 轮后,所有的 (a_i) 都会被更新一遍,而想要 (a_i) 从 (100) 减少到 (0) 至少需要 (100) 轮,所以可以保证任意时刻 (min{a_i}geq 0),从而这么做是正确的。
然后就做完了,时间复杂度是 (O(操作数)=O(nsqrt n)) 的。
可以发现实际上我们一次操作的调整次数上限为 ((B+frac{n}{2B})/2+B=frac{3B}{2}+frac{n}{4B}),根据不等式,当 (B=58) 时操作数上限为 (174) 次,是最优的情况。
Code
// https://cses.fi/179/submit/A
#include<iostream>
#define rep(i,a,b) for(int i = (a); i <= (b); i++)
#define per(i,b,a) for(int i = (b); i >= (a); i--)
#define N 20021
#define M 110
#define L 100
using namespace std;
int n, m;
int a[N], loc[N], cnt, forbid;
int l[M], r[M], cat[N];
void put(int pos, int c, bool print){
if(print) cout<<"I "<<pos<<endl, cout.flush();
a[pos] = ++cnt, loc[cnt] = pos, cat[cnt] = c;
}
void move(int id, int pos, int c){
if(!id) return;
if(id != forbid) cout<<"M "<<id<<" "<<pos<<endl, cout.flush();
a[loc[id]] = 0, a[pos] = id, loc[id] = pos, cat[id] = c;
}
int pre(int i){ return (i+m-2)%m+1; }
int nxt(int i){ return i%m+1; }
int bck(int i){ return (i+n-2)%n+1; }
int frt(int i){ return i%n+1; }
int dis(int i, int j){ return (j+n-i)%n+1; }
int main(){
ios::sync_with_stdio(false);
cin>>n;
m = n/2/L;
rep(i,1,m){
rep(j,1,L/2) put((i-1)*2*L+j, i, 1);
l[i] = (i-1)*2*L+1, r[i] = (i-1)*2*L+L/2;
}
rep(_,1,n/4){
int b; cin>>b;
int p = loc[b], c = cat[b];
int ldis = dis(l[c], p), rdis = dis(p, r[c]) - 1;
for(int i = pre(c); dis(l[i],r[i]) == L; i = pre(i)) ldis++;
for(int i = nxt(c); dis(l[i],r[i]) == L; i = nxt(i)) rdis++;
forbid = cnt+1;
if(rdis <= ldis){
for(int i = r[c]; i != p; i = bck(i)) move(a[i], frt(i), c);
put(p+1, c, 0), r[c] = frt(r[c]);
for(int i = c; dis(l[i],r[i]) > L; i = nxt(i))
move(a[r[i]], bck(l[nxt(i)]), nxt(i)), r[i] = bck(r[i]), l[nxt(i)] = bck(l[nxt(i)]);
} else{
for(int i = l[c]; i != frt(p); i = frt(i)) move(a[i], bck(i), c);
put(p, c, 0), l[c] = bck(l[c]);
for(int i = c; dis(l[i],r[i]) > L; i = pre(i))
move(a[l[i]], frt(r[pre(i)]), pre(i)), l[i] = frt(l[i]), r[pre(i)] = frt(r[pre(i)]);
}
int mx = 0, pc = 0;
rep(i,1,m) if(mx < dis(r[i],l[nxt(i)])-2)
mx = dis(r[i],l[nxt(i)])-2, pc = i;
int st = (l[nxt(pc)]+n - L-2) % n + 1;
int i = r[pc], j = st;
while(i != bck(l[pc])) move(a[i], j, pc), i = bck(i), j = bck(j);
r[pc] = st, l[pc] = frt(j);
cout<<"I "<<loc[cnt]<<endl;
}
return 0;
}