洛谷传送门
这题的主要算法我确实想不出来……不愧是IOI的题。
首先假如把一轮游戏的(a_i)从小到大排序,那么奖励数额(x_i=sum_{i=frac{n}{2}+1} ^ {n}a_i - sum _ {i = 1} ^ {frac{n}{2}}a_i).
然后我们要求的就是(sum_{i = 1} ^ {k}x_i)的最大值。
正解确实很难想,先想只有一轮的情况:我们令第(i)种颜色的数额最大的抽奖券为(R_i),最小的为(L_i),那么(x)一定是任意的(frac{n}{2})个(R_i)减去另外(frac{n}{2})个(L_j)。
我们可以先把(n)个(R_i)全部拿出来,然后再用(frac{n}{2})个(L_j)替换掉对应的(R_j)。而一次替换对答案的贡献是(-L_j-R_j)。所以把这个贡献从大到小排一个序,取前(frac{n}{2})个,(k=1)的情况就做出来了。
对于多轮有的情况,我们可以仿照(l=1)的情况:先取(nk)个(R_i),然后用(frac{nk}{2})个(L_i)替换掉一些(R_i)。不过要多考虑的一点是,(L_i)应该替换掉哪一个(R_i)呢?为了保持最优解,就需要用最小的(L)替换最小的(R),第二小的(L),替换第二小的(R)……因此就可以用一个优先队列来维护:如果一个颜色第(t)小的数额从队首出来了,就把他的贡献加到答案里,并把(t+1)小的数额放进队列里。
这样进行(frac{nk}{2})次后,就能知道(k)轮选了哪些卡片。
所以接下来我们要解决这些卡片该怎么分配。
我们一轮一轮的考虑:记录每一个颜色用了(L_j)的数目(cntL_i),那么取前(frac{n}{2})大的(cntL_i),这些颜色选(L_i),剩下(frac{n}{2})个颜色选(R_i),这样就保证了后面的轮数里不会出现一轮里一种颜色选了多张牌的情况。
代码写起来不是很难,但一定要保持头脑清醒,否则会忽视很多细节。
#include<cstdio>
#include<iostream>
#include<cmath>
#include<algorithm>
#include<vector>
#include<queue>
using namespace std;
#define In inline
typedef long long ll;
const int maxn = 1505;
typedef vector<int> Ivec;
extern "C" void allocate_tickets(std::vector<std::vector<int> > s);
int n, m;
struct Node
{
ll val; int id, col;
In bool operator < (const Node& oth)const
{
return val > oth.val;
}
};
priority_queue<Node> q;
int ansL[maxn], cntR[maxn], pos[maxn];
extern "C" In bool cmp(int a, int b) {return ansL[a] > ansL[b];}
extern "C" long long find_maximum(int K, vector<Ivec> a)
{
n = a.size(), m = a[0].size();
for(int i = 0; i < n; ++i) q.push((Node){a[i][0] + a[i][m - K], 0, i});
int cnt = n * K / 2;
for(int i = 1; i <= cnt; ++i)
{
Node tp = q.top(); q.pop();
int id = tp.id + 1, col = tp.col;
if(id == K) ansL[col] = K;
else q.push((Node){a[col][id] + a[col][m - K + id], id, col});
}
while(!q.empty()) ansL[q.top().col] = q.top().id, q.pop();
ll ans = 0;
vector<Ivec> g(n, Ivec(m, -1));
for(int i = 0; i < n; ++i) pos[i] = i;
for(int k = 0; k < K; ++k)
{
sort(pos, pos + n, cmp);
// sort(pos, pos + n, [&](int a, int b) {return ansL[a] > ansL[b];}); //洛谷的交互题评测机不让用lambda表达式……
for(int i = 0; i < n; ++i)
{
int p = pos[i];
if(i < (n >> 1)) g[p][--ansL[p]] = k, ans -= a[p][ansL[p]];
else g[p][m - (++cntR[p])] = k, ans += a[p][m - cntR[p]];
}
}
allocate_tickets(g);
return ans;
}