lyd读书笔记 0x05 排序(中)

这篇读书笔记探讨了中位数在货仓选址、均分纸牌问题中的应用,并通过BZOJ3032和POJ3784题目讲解了在线计算中位数的算法。中位数的选择能最小化距离总和,而在环形均分纸牌问题中,切开位置对应中位数时步数最小。
摘要由CSDN通过智能技术生成

中位数

货仓选址

ni=1|aix| ∑ 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 ,有M张纸牌。设 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]iive| | G [ i ] − i ⋅ i v e | ,其中 G[i] G [ i ] 是前缀和。然后就可以推出一个结论: d=Mi=1|iaveG[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似乎要迁移了的样子…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值