P8364 题解

题面
题意是比较清楚的。这里提供一种比较好想的 DP 做法。
对于第一小问,我们求解每个政党的时候都假设它获得了所有未知的选票,然后模拟计算即可。
对于第二小问,我们首先注意到两个事实:

  • 如果初始票数小于总票数的 \(5\%\),那么这个政党不会有任何席位。
  • 如果这个政党被分配到 \(m\) 个席位,那么他也能被分配到 \(m+1\) 个席位,前提条件是 \(m\) 严格小于第一问的答案。

那么我们不难想到使用二分来搜出答案的最小值。下面着重说一下 \(\operatorname{check}\) 函数。
我们记 \(dp_{i,j}\) 来表示顺序前 \(i\) 个党派(不包括它本身)分配了 \(j\) 个席位需要的最小票数,那么 \(\operatorname{check}(idx,seats)=[dp_{-1,M-seats}\le V-sum]\),其中 \(-1\) 表示 \(dp\) 数组的最后一项, \(sum\) 表示初始时所有票数的和。
考虑从 \(dp_{i-1,j-k}\) 怎么转移到 \(dp_{i,j}\),那么考虑需要多分配的票数 \(extra=\frac{votes_{idx}\times k+seats}{seats+1}-votes_i\),为了避免票数相同但编号较小的情况,因此这里需要特判;为了避免不超过 \(5\%\),这里也需要特判。因此我们不难写出下面的(伪)代码:

extra = (votes[idx] * k + seats) / (seats + 1) - votes[i];
extra = max(extra + (votes[idx] * k % (seats + 1) == 0 and rk[i] > rk[idx] ans k), 0);
if (k and (votes[i] + extra) * 20 < V )
    extra += (V + 19 - 20 * (votes[i] + extra)) / 20;

dp[i-1][j-k]->dp[i][j]

考虑优化。

  • 对于空间的优化,我们显然可以使用滚动数组
  • 对于时间的优化,考虑到真正产生影响的不会超过 \(20\) 个政党,因此我们可以在最开始的时候对所有政党进行排序,最多只取前 \(20\) 个政党。另一方面,处于相同的原因,我们最多只会判断 \(20\) 个政党,其余的都没有席位。

优化以后的时间复杂度是 \(O(m^2\log m+n)\) 的,但有 \(20\) 倍常数。
代码如下:

#include <bits/stdc++.h>
using namespace std;
int V, N, M, nowVotes;
int vts[101];
vector<pair<int, int> > __;
int votes[101], dp[2][201], rk[101];
int sol[101];
int check(int idx, int seats) {
    int ii = 0;
    if (votes[idx] * 20 < V)
        return 1;
    for (int i = 0; i <= 200; i++)
        dp[0][i] = 0x3f3f3f3f;
    dp[0][0] = 0;
    for (int i = 0; i < min(N, 21); i++)
        if (i != idx) {
            ii ^= 1;
            for (int j = 0; j <= M; j++) {
                dp[ii][j] = 0x3f3f3f3f;
                for (int k = 0; k <= j; k++) {
                    int extra = (votes[idx] * k + seats) / (seats + 1) - votes[i];
                    extra = max(extra + (votes[idx] * k % (seats + 1) == 0 && rk[i] > rk[idx] && k), 0);
                    if (k && (votes[i] + extra) * 20 < V) {
                        extra += (V + 19 - 20 * (votes[i] + extra)) / 20;
                    }
                    dp[ii][j] = min(dp[ii ^ 1][j - k] + extra, dp[ii][j]);
                }
            }
        }
    return dp[ii][M - seats] <= V - nowVotes;
}
int main() {
    scanf("%d %d %d", &V, &N, &M);
    __.resize(N);
    for (int i = 0; i < N; i++) {
        scanf("%d", &votes[i]);
        __.push_back({-votes[i], i});
        nowVotes += votes[i];
    }
    sort(__.begin(), __.end());
    for (int i = 0; i < N; i++) {
        rk[i] = __[i].second;
        votes[i] = -__[i].first;
    }
    for (int i = 0; i < N; i++) {
        for (int j = 0; j < N; j++)
            vts[j] = 0;
        votes[i] += V - nowVotes;
        for (int j = 0; j < M; j++) {
            int max_a = 0, max_b = 1, maxParty = 0;
            for (int k = 0; k < N; k++)
                if (votes[k] * 20 >= V) {
                    if (votes[k] * max_b > max_a * (vts[k] + 1)) {
                        max_a = votes[k];
                        max_b = vts[k] + 1;
                        maxParty = k;
                    }
                    if (votes[k] * max_b == max_a * (vts[k] + 1) && rk[maxParty] > rk[k]) {
                        max_a = votes[k];
                        max_b = vts[k] + 1;
                        maxParty = k;
                    }
                }
            vts[maxParty]++;
        }
        sol[rk[i]] = vts[i];
        votes[i] -= V - nowVotes;
    }
    for (int i = 0; i < N; i++) {
        printf("%d ", sol[i]);
    }
    printf("\n");
    for (int i = 0; i < N; i++) {
        if (votes[i] * 20 < V) {
            sol[rk[i]] = 0;
            continue;
        }
        int ll = 0, rr = sol[rk[i]];
        while (ll < rr) {
            int mid = ll + ((rr - ll) >> 1);
            if (check(i, mid))
                rr = mid;
            else
                ll = mid + 1;
        }
        sol[rk[i]] = ll;
    }
    for (int i = 0; i < N; i++) {
        printf("%d ", sol[i]);
    }
    printf("\n");

    return 0;
}
posted @ 2023-12-31 21:35  Piggy343288  阅读(46)  评论(0)    收藏  举报