中位数
货仓选址
求 ∑ni=1|ai−x| ∑ i = 1 n | a i − x | 的最小值。
如果x<中位数,x后面的数的增加量大于前面的数的减少量
反之亦然。
所以x为中位数。
ljt神犇:
将
(i,ai)
(
i
,
a
i
)
投影到坐标平面上
然后用直线
x=k
x
=
k
去扫,可以想象到在上下点分布平均时距离总和最小。
均分纸牌
luoguP1031的问题非常简单,做法也很清晰。首先维护一个平均值ave,然后依次从右面移过去。
然后我们模拟一遍即可。举个栗子,3 5 1 7 平均数是4,那么先变成4 4 1 7然后变成4 4 4 4
有个问题,譬如1 1 5 9这样的话,变成的是4 -2 5 9,会不会出锅?并不会,因为我们可以发现,-2相当于调整了一下顺序,先把5的移过来再进行下一步操作。
所以直接模拟一下即可。
//一年前写的代码2333
#include<iostream>
using namespace std;
int main(){
int i;
cin>>i;
int pai[i],s=0;
for(int p=0;p<i;++p){
cin>>pai[p];
s+=pai[p];
}
int avg=s/i;
int f=0;
for(int p=0;p<i;++p){
int j=avg-pai[p];
if(j!=0){
pai[p+1]-=j;
pai[p]=avg;
++f;
}
}
cout<<f;
}
但lyd并没有局限于这一模拟过程。假如我们将状态进行抽象,我们会得到一些普遍性的结论。
假设总和为
T
T
,有张纸牌。设
ave=TM
a
v
e
=
T
M
。对于第i个人来说,如果
C[i]<ave
C
[
i
]
<
a
v
e
他拿的应该是
C[i]+ave
C
[
i
]
+
a
v
e
张,后一个拿
C[i+1]−ave+C[i]
C
[
i
+
1
]
−
a
v
e
+
C
[
i
]
,否则,他拿
C[i]−ave
C
[
i
]
−
a
v
e
张,而后一个拿
C[i+1]+C[i]−ave
C
[
i
+
1
]
+
C
[
i
]
−
a
v
e
张。
但是这样并不方便维护,我们考虑整体和隔离的思想。将前i个看做一个整体,显然前i个内部的均分是不会改变其整体结构的,因而对于该体系来说,想要达到平均数结构,就必须与下一个体系交换足够的纸牌,而交换数量就是
|G[i]−i⋅ive|
|
G
[
i
]
−
i
⋅
i
v
e
|
,其中
G[i]
G
[
i
]
是前缀和。然后就可以推出一个结论:
d=∑Mi=1|i⋅ave−G[i]|
d
=
∑
i
=
1
M
|
i
⋅
a
v
e
−
G
[
i
]
|
,也就是将每次体系更新的贡献加起来。
如果让每个人的数量都减去
ave
a
v
e
,结果就可以经过简单的数学推导进一步化简:
d=∑Mi=1|S[i]|
d
=
∑
i
=
1
M
|
S
[
i
]
|
,其中
S[i]
S
[
i
]
是新数组的前缀和。这就是均分纸牌问题的通用公式。
现在考虑一种变形:如果这里的纸牌是环形的呢?
对于环形问题,首先考虑切开。假定我们切开的东西是
A[k+1],A[k+2],...,A[M],A[1],...,A[k]
A
[
k
+
1
]
,
A
[
k
+
2
]
,
.
.
.
,
A
[
M
]
,
A
[
1
]
,
.
.
.
,
A
[
k
]
,那么其前缀和也会有所变化,即
S[k+1]−S[k],S[k+2]−S[k],...,S[M]−S[k],.S[1]+S[M]−S[k],...,S[M]
S
[
k
+
1
]
−
S
[
k
]
,
S
[
k
+
2
]
−
S
[
k
]
,
.
.
.
,
S
[
M
]
−
S
[
k
]
,
.
S
[
1
]
+
S
[
M
]
−
S
[
k
]
,
.
.
.
,
S
[
M
]
由于均分之后,
S[M]=0
S
[
M
]
=
0
恒成立,所以前缀和的变化仅仅是减去
S[k]
S
[
k
]
。那么,我们要求的就是哪个取值上最短,换言之,求什么时候
∑Mi=1|S[i]−S[k]|
∑
i
=
1
M
|
S
[
i
]
−
S
[
k
]
|
取到最小。
等等,这个形式有点熟悉的样子。。这不就是货仓选择得到的中位数么!!!
因此,我们就知道一个结论:对于环形均分纸牌问题来说,最小步数在
S[k]
S
[
k
]
为中位数所对应的
k
k
<script type="math/tex" id="MathJax-Element-24">k</script>处切开取到最小值。
BZOJ3032 七夕祭
http://begin.lydsy.com/JudgeOnline/problem.php?id=4711
这里可以提交。。
在两个方向上跑一遍环形均分纸牌即可。
#include <iostream>
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <cstring>
using namespace std;
#define N 100003
#define LL long long
LL n, m, T;
LL row[N], col[N], s[N];
LL avr, avc, ans;
inline void read(LL& x) {
x = 0; char c = getchar();
while(!isdigit(c)) c = getchar();
while(isdigit(c)) x = x * 10 + c - '0', c = getchar();
}
int main() {
read(n), read(m), read(T);
LL x, y;
for(int i = 1; i <= T; ++i) {
read(x), read(y);
row[x]++, col[y]++;
avr++; avc++;
}
if(avr % n == 0 && avc % m == 0) printf("both ");
else if(avr % n == 0) printf("row ");
else if(avc % m == 0) printf("column ");
else return printf("impossible\n"), 0;
if(avr % n == 0) {
avr /= n;
for(int i = 1; i <= n; ++i) row[i] -= avr, s[i] = s[i - 1] + row[i];
sort(s + 1, s + n + 1);
LL k = s[n / 2 + 1];
for(int i = 1; i <= n; ++i) ans += abs(s[i] - k);
}
memset(s, 0, sizeof s);
if(avc % m == 0) {
avc /= m;
for(int i = 1; i <= m; ++i) col[i] -= avc, s[i] = s[i - 1] + col[i];
sort(s + 1, s + m + 1);
LL k = s[m / 2 + 1];
for(int i = 1; i <= m; ++i) ans += abs(s[i] - k);
}
cout<<ans<<endl;
return 0;
}
POJ3784 Running Median
总之就是用一个大根堆和一个小根堆,非常巧妙。
#include <iostream>
#include <cstdio>
#include <queue>
using namespace std;
priority_queue<int> big;
priority_queue<int, vector<int>, greater<int> > small;
int main() {
//freopen("test.txt", "w", stdout);
int T;
scanf("%d", &T);
int id, m, cnt, a, mid;
while(T--) {
while(!big.empty()) big.pop();
while(!small.empty()) small.pop();
scanf("%d%d", &id, &m);
cnt = 1, mid = -0x3fffff;
cout<<id<<" "<<(m + 1 >> 1)<<endl;
for(int i = 1; i <= m; ++i) {
scanf("%d", &a);
if(big.empty()) {
big.push(a);
mid = a;
cout<<a<<" ";
continue;
}
if(a < mid) big.push(a);
else small.push(a);
if(((int)big.size() - (int)small.size()) > 1) {
//cout<<big.size()<<" "<<small.size()<<" "<<big.size() - small.size()<<endl;
int t = big.top();
big.pop();
small.push(t);
} else if(((int)small.size() - (int)big.size()) > 1) {
int t = small.top();
small.pop();
big.push(t);
}
if(i & 1) {
++cnt;
if(big.size() > small.size()) mid = big.top();
else mid = small.top();
if(cnt % 10 == 0) cout<<mid<<endl;
else cout<<mid<<" ";
} else mid = (small.top() + big.top()) / 2;
}
if(cnt % 10 != 0) cout<<endl;
}
return 0;
}
mmp下次再不注释文件我tm剁手
blog似乎要迁移了的样子…