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\) 就是几个散点。把每段的第一个点搞出来即可
-
转 \(0\) 次的答案可以暴力算出:\(g(0)=\sum d_i(0) = \sum \min\{ |a_i-i|,n-|a_i-i| \}\)
-
转 \(a_i-i+1 \pmod n\) 次到达最小值的下一个位置,\(g_i\) 由递减转为递增。因此把这个位置 \(+2\)
-
若 \(n\) 为奇数,转 \(a_i-i+1 + \lfloor n/2\rfloor \pmod n\) 次到达最大值,\(g_i\) 由递增转为常数。因此把这个位置 \(-1\)。下一个位置由常数转为递减,也要 \(-1\)
-
第一段即 \(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);
}

浙公网安备 33010602011771号