EOJ 2018.2.5新生训练Week3 D.Game of Chairs

n个椅子,c种颜色排成一圈,间隔1m。随机选一种颜色,你要马上移动到这种颜色的椅子上(原本颜色相同则不动)。求走动距离的最小期望(输出最简分数)。
1 ≤ c ≤ n ≤ 1e6

据说暴力模拟+优化(n^2—>nlogn?)2.5s内可过……不过这里用了一些数学知识,复杂度降到O(n)。(其实是2n,不过eoj评测姬太快了,可以忽略)


首先肯定是常规的环拆链操作:复制一份放到后面去。这样就可以规定正方向为向右,从左往右扫描了。
要求出答案,关键在于求出要坐的这个位置。要求出这个位置,无疑需要求出位置 i 到各个颜色椅子的最短期望距离和 d(i) d ( i )
D(i,k) D ( i , k ) 为椅子 i 到颜色为 k 的椅子的最短期望距离。即:

d(i)=k=1cD(i,k) d ( i ) = ∑ k = 1 c D ( i , k )

我们发现,对每一个k, D(i,k) D ( i , k ) 是一个关于 i 的分段函数:

  1. i 在距离最近的颜色为k的椅子左边,则 i 每右移一次,离该椅子的距离-1,此时 D(i,k)=1 D ′ ( i , k ) = − 1
  2. 同理,i 在距离最近的颜色为k的椅子右边,则 i 每右移一次,离该椅子的距离+1,此时 D(i,k)=+1 D ′ ( i , k ) = + 1
  3. 于是在中间某个时刻,我们移动到了这张椅子上,此时 D(i,k)=0 D ′ ( i , k ) = 0 ,这里是函数的驻点。再求(伪)二阶导,由于一阶导在这个点从-1变成了+1,我们可以认为(伪)二阶导 D′′(i,k)=+2 D ″ ( i , k ) = + 2 。(这样设定二阶导是为了方便后面求一阶导和答案)

再考虑两个相邻的同色(k)椅子:当经过两者中点前,我们离左边椅子的距离小于离右边椅子的距离,反之亦然。也就是说,在经过两者中点时, D(i,k) D ′ ( i , k ) 由+1变为了-1(中间的椅子数为奇时,会在中点处变为0)。
因此,需要对ceil((i+j)/2)和floor((i+j)/2)这两个点(中间的椅子数为奇时,一个点)的二阶导分别减1。

综上,二阶导处理完毕。对二阶导求前缀和并且每项减c,得到一阶导。再对一阶导求前缀和(注意特判,第0个位置就是-c),得到每个位置的 d(i) d ( i ) ,最后取最小值,这题就终于做完了……

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int maxn = 2e6+5;
int n, c, a[maxn], nxt[maxn];
ll d[maxn];

ll gcd(ll a, ll b)
{
    return b ? gcd(b, a%b) : a;
}

int main()
{
    cin >> n >> c;
    for (int i = 1; i <= n; ++i)
        scanf("%d", a+i);
    for (int i = n+1; i <= (n<<1); ++i)
        a[i] = a[i-n];
    n <<= 1;
    for (int i = n; i >= 1; --i)
    {
        d[i] += 2;
        int &j = nxt[a[i]];
        if (j)
        {
            d[(i+j)>>1] -= 1;
            d[(i+j+1)>>1] -= 1;
        }
        j = i;
    }
    for (int i = 1; i <= n; ++i)
        d[i] += d[i-1];
    d[0] = -c;
    for (int i = 1; i <= n; ++i)
        d[i] += -c;
    ll sum = 0;
    for (int i = 1; i <= n; ++i)
    {
        int &j = nxt[a[i]];
        if (j)
        {
            sum += i;
            j = 0;
        }
    }
    ll ans = (ll)n * n;
    for (int i = 1; i <= n; ++i)
    {
        sum += d[i-1];
        if (sum < ans) ans = sum;
    }
    ll g = gcd(ans, c);
    ans /= g;
    c /= g;
    printf("%lld/%d\n", ans, c);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值