P4998 信号站

“信心赛”T1

如果只要求取一个点的话,直接套用蓝书里面的结论:取中位数即可。

但是这里要求\(k\)个点,怎么办?

可以定义一个函数为一个点的不合理值,那么可以发现这个函数是先下降后上升的。

换句话说:我们已经知道了中位数那里就是峰底,那么剩下的\(k-1\)个点就在这些点的旁边取即可。

我的思路也是这样啊为什么只有10pts

看了下其他dalao的做法,我才发现可以\(O(1)\)地更新答案。

维护两个变量numx和numy,表示比\(x\)大的人家个数和比\(y\)小的人家个数。

因为人家的坐标\(\leq 10^6\),所以直接不用离散化就能开下来,可以开一个数组表示一个点处有多少个人家。

给x和y赋两个初值,通过取答案的较小值来对它更新,\(x\)只负责向左扩展,\(y\)只负责向右扩展。

我的错误思路:用一个点来扩展,取它的两边较小值来继续扩展,如果两边相等的话就搞不了了。

同样,这道题有一个坑:信号站可以建在负半轴上面,所以数组开大一点然后平移一下就完事了。

代码:

#include<cstdio>
#include<algorithm>
#define ll long long
const int maxn = 2000005;
ll a[maxn], b[maxn];

int n, m;
int read()
{
    int ans = 0, s = 1;
    char ch = getchar();
    while(ch > '9' || ch < '0'){ if(ch == '-') s = -1; ch = getchar(); }
    while(ch >= '0' && ch <= '9') ans = ans * 10 + ch - '0', ch = getchar();
    return s * ans;
}
ll cal(int x)
{
    ll ret = 0;
    for(int i = 1; i <= n; i++) ret += abs(x - a[i]);
    return ret;
}
int main()
{
    n = read(), m = read();
    for(int i = 1; i <= n; i++)
    {
        a[i] = read();
        b[a[i] + 1000000]++;
    }
    std::nth_element(a + 1, a + (n + 1) / 2, a + n + 1);
    ll ans = 0;
    ll x = a[(n + 1) / 2], y = x + 1;
    ll ansx = cal(x), ansy = cal(y);
    ll numx = 0, numy = 0;
    for(int i = 1; i <= n; i++)
    {
        if(a[i] > x) numx++;
        if(a[i] < y) numy++;
    }
    while(m--)
    {
        if(ansx <= ansy)
        {
            ans += ansx;
            ansx = ansx + numx + b[x + 1000000] - (n - numx - b[x + 1000000]);
            numx += b[x + 1000000];
            x--;
        }
        else
        {
            ans += ansy;
            ansy = ansy + numy + b[y + 1000000] - (n - numy - b[y + 1000000]);
            numy += b[y + 1000000];
            y++;
        }
    }
    printf("%lld\n", ans);
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/Garen-Wang/p/9913001.html