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;
}