0x05 基本算法-排序

A题: Cinema

经典离散化例题,把电影的语言与字幕和观众懂的语言放进一个数组,然后离散化。

最后统计快乐人数。

const int N = 200006;
int n, m, a[N], x[N], y[N], cinema[N * 3], tot = 0, k, ans[N * 3];

int find(int f) { return lower_bound(cinema + 1, cinema + k + 1, f) - cinema; }

int main() {
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i], cinema[++tot] = a[i];
    cin >> m;
    for (int i = 1; i <= m; ++i) cin >> x[i], cinema[++tot] = x[i];
    for (int i = 1; i <= m; ++i) cin >> y[i], cinema[++tot] = y[i];

    sort(cinema + 1, cinema + tot + 1);
    k = unique(cinema + 1, cinema + tot + 1) - (cinema + 1);
    memset(ans, 0, sizeof(ans));
    for (int i = 1; i <= n; i++) ans[find(a[i])]++;
    int ans0 = 1, ans1 = 0, ans2 = 0;
    for (int i = 1; i <= m; i++) {
        int ansx = ans[find(x[i])], ansy = ans[find(y[i])];
        if (ansx > ans1 || (ansx == ans1 && ansy > ans2)) {
            ans0 = i;
            ans1 = ansx;
            ans2 = ansy;
        }
    }
    cout << ans0 << endl;
    return 0;
}

当然不用离散化也可以做。

简单使用 unordered_map 映射个数即可

const int N = 2e5 + 10;
int _, n, x, y, tmp, a[N];
unordered_map<int, int> mp;
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    for (cin >> _; _--;) cin >> tmp, mp[tmp]++;
    cin >> n;
    for (int i = 1; i <= n; ++i) cin >> a[i];
    for (int i = 1; i <= n; ++i) {
        int t;
        cin >> t;
        if (mp[a[i]] > x)
            tmp = i, x = mp[a[i]], y = mp[t];
        else if (mp[a[i]] == x && mp[t] > y)
            tmp = i, y = mp[t];
    }
    cout << tmp << "\n";
    return 0;
}

B题:货仓选址

排序一下,利用中位数的性质

int n, ans, sum;
int main() {
    ios_base::sync_with_stdio(false), cin.tie(0);
    cin >> n;
    vector<int> v(n);
    for (auto &t : v) cin >> t;
    sort(v.begin(), v.end());
    ans = v[n / 2];
    for (auto &t : v) sum += abs(t - ans);
    cout << sum << "\n";
    return 0;
}

C题:⭐七夕祭

这里借下 洛凌璃dalao的blog题解

题解

这个题其实是两个问题:

1.让摊点上下移动,使得每行的摊点一样多

2.左右移动摊点,使得每列的摊点一样多

两个问题是等价的,就讨论第一个

r[i]表示每行的摊点数

然后使得r[i]的一些摊点移动到r[i - 1]和r[i + 1], 类似于"均摊纸牌"

均摊纸牌

有M个人排一排,每个人分别有C[1]~C[M]张拍,每一步中,一个人可以将自己的一张手牌给相邻的人,求至少需要几步

显然, 纸牌总数T能被M整除有解,在有解的情况下, 考虑第一个人:

1.C[1] >= T/M, 第一个人要给第二个人C[1] - T/M张牌
2.C[1] < T/M, 第二个给第一个人T/M - C[1]张牌
本质就是使得第一人满足要求要|T/M - C[1]|步
那么满足第二人就要 |T/M - (C[2] - (T/M - C[1]))| = |2 * T/M - (C[1] + C[2])|步
满足第三人 |T/M - (C[3] - (T/M - (C[2] - (T/M - C[1]))))| = |3 * T/M - (C[1] + C[2] + C[3])|

到这里就可以发现,有一段是前缀和, 但再仔细化简以下可以发现

|3 * T/M - (C[1] + C[2] + C[3])|
=|(T/M - C[1]) + (T/M - C[2]) + (T/M - C[3])|
=|(C[1] - T/M) + (C[2] - T/M) + (C[3] - T/M)|

我们可以让A[i] = C[i] - T/M, S[i]为A[i]的前缀和,

那么对于"均摊纸牌"这道题的答案就是

∑ni=1∑i=1n|S[i]|

对于本题来说,无非是变成了环形问题

直接无脑dp就可以

我们随便选取一个人k最为断环的最后一名(即第一个人变为为k + 1),

则从这个人开始的持有的牌数(这行的摊点数), 前缀和为

A[k + 1]   S[k + 1] - S[k]
A[k + 2]   S[k + 2] - S[k]
...
A[M]       S[M] - S[k]
A[1]       S[M] - S[k] + S[1]
A[2]       S[M] - S[k] + S[2]
...
A[k]       S[M] - S[k] + S[k]

我们发现S[M] = 0, 所以答案为

|S[k + 1] - S[k]| + ... + |S[M] - S[k]| + |s[M] - S[k] + S[1]| + ... + |S[M] - S[K] + S[k]|

=|S[k + 1] - S[k]| + ... + |S[M] - S[k]| + |-S[k] + S[1]| + ... + |-S[k] + S[k]|

=∑ni=1∑i=1n |S[i] - S[k]|

答案已经很明显了,像不像"仓货选址"?

仓货选址

一条轴上有N家店,每家店的坐标为D[1]~D[N],选择一家点为仓库向其他商店发货,求选哪家店,运送距离最短

不就是∑ni=1∑i=1n |D[i] - D[k]| 为答案吗?

当然是选中位数了啦,

设k左边有P家店,右边有Q家店

如果P<Q,那必然将k右移, ans - Q + P,答案明显变小了

Q>P,同理,故选择中位数

所以本题的答案就已经近在眼前了, 前缀和,求中位数

using ll = long long;
const int maxn = 1e5 + 5;
int n, m, k;
int c[maxn], r[maxn], s[maxn];

ll work(int a[], int n) {
    for (int i = 1; i <= n; ++i) s[i] = s[i - 1] + a[i] - k / n;
    sort(s + 1, s + 1 + n);
    ll ans = 0;
    for (int i = 1; i <= n; ++i) ans += abs(s[i] - s[(n >> 1) + 1]);
    return ans;
}

int main() {
    cin >> n >> m >> k;
    for (int i = 1; i <= k; ++i) {
        int a, b;
        cin >> a >> b, ++c[b], ++r[a];
    }
    if (k % n + k % m == 0)
        cout << "both " << work(c, m) + work(r, n);
    else if (k % n == 0)
        cout << "row " << work(r, n);
    else if (k % m == 0)
        cout << "column " << work(c, m);
    else
        cout << "impossible";
    return 0;
}

【例题】Running Median (对顶堆)

题意:动态的维护中位数的问题,依次读入一个整数,每当总个数为奇数时输出此时序列的中位数

使用对顶堆的在线做法。

为了实时找到中位数,我们可以建议两个二叉堆:一个小根堆、一个大根堆。在依次读入数字的过程设当前序列长度为 \(M\),我们始终保持:

  1. 序列中从小到大排名为 \(1\) ~ $ M/2 $ 的整数存储在大根堆中;
  2. 序列中从小到大排名为 \(M/2 + 1\) ~ \(M\) 的整数存储在小根堆中。

任何时候如果某一个堆中元素过多则是打破了平衡需要取出该堆的堆顶插入到另一个堆。这样一来序列的中位数就是小根堆的堆顶

关于新数 \(X\) 插入:如果 \(X\) 比中位数小,则插入大根堆,否则就插入小根堆。之后再维护平衡即可

上述算法就是 “对顶堆” 算法

这个代码会爆空间,但很好的实现的上述过程

priority_queue<int> q1, q2;
void solve() {
    while (q1.size()) q1.pop();
    while (q2.size()) q2.pop();

    int num, n;
    cin >> num >> n;
    cout << num << " " << (n + 1) / 2 << "\n";
    int a;
    cin >> a;
    cout << a << " ";
    q2.push(-a);
    int cnt = 1;
    for (int i = 2; i <= n; ++i) {
        cin >> a;
        if (a < -q2.top()) q1.push(a);
        else
            q2.push(-a);
        int s = q1.size();
        if (s > i / 2) {
            q2.push(-q1.top());
            q1.pop();
        }
        if (s < i / 2) {
            q1.push(-q2.top());
            q2.pop();
        }
        if (i & 1) {
            cout << -q2.top() << " ";
            if (++cnt % 10 == 0) cout << endl;
        }
    }
    cout << "\n";
}

AC 代码

vector<short> a;
void solve() {
    a.clear();
    int k, n;
    cin >> k >> n;
    cout << k << " " << (n + 1) / 2 << "\n";
    for (int i = 1; i <= n; ++i) {
        short x;
        cin >> x;
        a.insert(upper_bound(a.begin(), a.end(), x), x);
        if (i & 1) cout << a[(i - 1) / 2] << " ";
        if (i % 20 == 0) cout << "\n";
    }
    cout << "\n";
}
posted @ 2021-01-31 16:33  RioTian  阅读(121)  评论(0编辑  收藏  举报