CEOI 2009 Day1 A

题目链接

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) 减一,你可以选取一个 (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;
}
原文地址:https://www.cnblogs.com/Neal-lee/p/15251751.html