CEOI 2009 Day1 A - boxes

题目链接

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;
}
posted @ 2021-09-10 16:08  Neal_lee  阅读(72)  评论(0编辑  收藏  举报