CEOI 2009 Day1 A - boxes
题目链接
题目大意
交互题。
一个环上有 \(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\) 减一,你可以选取一个 \(j\in[1,m],k\in\mathbb Z\),使得 \(a_j+k\),\(a_{j+1}-k\)(下标循环定义)。
我们每次选取最大的值 \(a_i\), 将其减小到 \(100\),然后使 \(a_{i-1}\) 相应地增加。由于最终空隙数量 \(=n/2=100m\),所以 \(a_i\geq 100\),此时相当于最大值的位置左移 \(1\) 到 \(a_{i-1}\) 上了,可以发现这样一个过程会一直进行,可以发现在 \(100\) 轮后,所有的 \(a_i\) 都会被更新一遍,而想要 \(a_i\) 从 \(100\) 减少到 \(0\) 至少需要 \(100\) 轮,所以可以保证任意时刻 \(\min\{a_i\}\geq 0\),从而这么做是正确的。
然后就做完了,时间复杂度是 \(O(操作数)=O(n\sqrt 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;
}