abc268 E - Chinese Restaurant (Three-Star Version)

题意:

一个带转盘的餐桌,\(n\) 个人,\(n\) 盘菜。人逆时针标号为 \(0,\cdots ,n-1\),菜的标号 \(a_i\) 是一个 \(0\sim n-1\) 的排列。若标号为 \(i\) 的人要旋转(方向随意) \(x\) 下才能使标号为 \(i\) 的菜到他面前,则这个人的沮丧值为 \(x\)

现在你可以旋转任意下,问最小的沮丧值总和

\(3\le n\le 2e5\)

思路:

\(i\) 个人的沮丧值是一个关于旋转次数的分段单调函数 \(g_i\)。最小值 \(0\) 在旋转到 \(i=a_i\) 时取得;当 \(n\) 为偶数时,转到最小值后再转 \(\frac n2\) 下就达到最大值位置;当 \(n\) 为奇数时,再转 \(\lfloor \frac n2\rfloor\)\(\lceil \frac n2 \rceil\) 都是最大值

我们要求 \(n\) 个分段单调函数的和!

\(g_i\) 的差分 \(c_i\) 是个分段函数,每段内取常数;\(c_i\) 的差分 \(d_i\) 就是几个散点。把每段的第一个点搞出来即可

  1. \(0\) 次的答案可以暴力算出:\(g(0)=\sum d_i(0) = \sum \min\{ |a_i-i|,n-|a_i-i| \}\)

  2. \(a_i-i+1 \pmod n\) 次到达最小值的下一个位置,\(g_i\) 由递减转为递增。因此把这个位置 \(+2\)

  3. \(n\) 为奇数,转 \(a_i-i+1 + \lfloor n/2\rfloor \pmod n\) 次到达最大值,\(g_i\) 由递增转为常数。因此把这个位置 \(-1\)。下一个位置由常数转为递减,也要 \(-1\)

  4. 第一段即 \(d[1]\) 咋办?(貌似是由 \(a_i-i\pmod n\)\(n/2\) 的大小关系决定的,但我没搞懂qaq)不就是转一次的答案么,我直接暴力算!

注意转 \(0\) 次和转 \(1\) 次的答案是直接算出来的,不应再被修改。因此上述 \(2,3\) 号修改应避开 \(0,1\) 两个位置

最后对 \(d[]\) 做两次前缀和还原为原来的函数

const signed N = 2e5 + 5;
int n; ll d[N];
int M(int x) {
    return (x % n + n) % n;
}
void add(int p, int x) { //不修改d[0]和d[1]
    if(p > 1) d[p] += x;
}
void sol() {
    cin >> n;
    for(int i = 0; i < n; i++) {
        int a; cin >> a;
        int mov0 = min(M(a - i), n - M(a - i)); //暴力算0次的
        int mov1 = min(M(a - i - 1), n - M(a - i - 1)); //暴力算1次的
        d[0] += mov0;
        d[1] += mov1 - mov0;
        add(M(a - i + 1), 2); //搞出递增段
        add(M(a - i + 1 + n / 2), -1); //搞出n为奇时最上面平的那段
        add(M(a - i + 1 + (n + 1) / 2), -1); //搞出递减段
    }
    for(int i = 2; i < n; i++)
        d[i] += d[i - 1];
    for(int i = 1; i < n; i++)
        d[i] += d[i - 1];
    cout << *min_element(d, d + n);
}
posted @ 2023-01-08 21:03  Bellala  阅读(58)  评论(0)    收藏  举报