题解 P6831 【[IOI 2020 Day1] Carnival Tickets】

s_r_f

2020-09-19 19:27:16

Solution

安利:[IOI2020题解-洛谷博客](https://www.luogu.com.cn/blog/s-r-f/ioi2020-ti-xie) [IOI2020题解-cnblogs](https://www.cnblogs.com/s-r-f/p/13697373.html) --- [题目链接-LOJ](https://loj.ac/problem/3366) [题目链接-洛谷](https://www.luogu.com.cn/problem/P6831) 首先我们考虑如何求出最大值。 不难发现这个抽奖的过程中,工作人员必然会取中位数来最小化当前轮次的奖励。 因为n是偶数,不难发现我们能获得的奖励为: 设当前轮次使用的奖券上的数值为 $a_{1,2...n}$ 并且已经从小到大排序,那么 $\large ans=\sum\limits_{\frac{n}{2}+1\leq i\leq n} a_i -\sum\limits_{1\leq i\leq \frac{n}{2}} a_i$ 也就是说,有 $\large \frac{n}{2}$ 个数对答案的贡献为 $a_i$ ,另 $\large \frac{n}{2}$ 个数对答案的贡献为 $-a_i$ , **并且所有负贡献的数字都不大于正贡献的数字。** 可以发现,经过 $k$ 次过程获得的最大值,相当于我们在 $n$ 种颜色的奖券上的数字中,每种颜色选取 $k$ 个数字,并在其中选 $\large \frac{nk}{2}$ 个数字,使其对答案的贡献为正数 ,令另外一半的数字的贡献为负数 ,然后求和即为答案。 不难发现如果我们获得了这个取数问题的最优解,那么**必然存在一种方案把这 $nk$ 个数分成 $k$ 组使得每组中有 $n$ 个颜色两两不同的数,一半取负贡献,一半取正贡献,并且负贡献的数都不大于正贡献的数字。即有对应原问题的合法方案。** 证明:如果不存在合法方案,那么对于任何一种方案,必然存在同一组数字中有一个取负贡献的数大于一个取正贡献的数。这时候交换一下它们的符号就可以获得一个更优的解,所以如果取得了最优解的取数方案,必然有一个对应了原问题的合法方案。 取数问题的贪心:先默认都取负数,然后再贪心的做 $\large \frac{nk}{2}$ 次把负数换成正数的操作即可。 然后我们考虑如何构造方案。 递归构造,考虑从当前的 $nk$ 个数字中取出 $n$ 个颜色两两不同的成为一组,保证它合法后再对剩下的 $n(k-1)$ 个数字继续构造。 从每种颜色中取出一个最大的正数和一个最小的负数并将正数和负数分别排序,枚举正数和负数之间的边界,$two-pointers$ 的同时维护可用的正/负数数目即可。 因为正数和负数分别取到了最大和最小,所以在整个取数问题有对应方案的时候这个子问题必然能找到一组解,可以继续递归。 由于递归的时候**保证了选数方案一直是当前的最优解**,所以一直都能保证当前的选数方案有一组对应原问题的解。 $\Theta(nm\log n)$ code(LOJ上通过): ```cpp #include "tickets.h" #include <bits/stdc++.h> #define LL long long using namespace std; const int N = 1505,M = 1505; int n,m,k,ans[N][M],val[N][M]; int z[N][M]; LL AANS; namespace CALCZ{ int vv[N],nl[N],nr[N]; struct Node{ int id,v; bool operator < (const Node w) const{ return v < w.v; } }tmp; priority_queue<Node>H; inline void calcz(){ int i,j,ll = n*k/2; for (i = 0; i < n; ++i) for (j = 0; j < m; ++j) z[i][j] = 0; for (i = 0; i < n; ++i) for (j = 0; j < k; ++j) z[i][j] = -1; for (i = 0; i < n; ++i){ nl[i] = k-1,nr[i] = m-1; vv[i] = val[i][nl[i]] + val[i][nr[i]]; tmp.id = i,tmp.v = vv[i],H.push(tmp); } while (ll--){ tmp = H.top(); H.pop(); i = tmp.id; z[i][nl[i]] = 0,z[i][nr[i]] = 1; --nl[i],--nr[i]; if (nl[i] >= 0){ vv[i] = val[i][nl[i]] + val[i][nr[i]]; tmp.id = i,tmp.v = vv[i],H.push(tmp); } } for (i = 0; i < n; ++i) for (j = 0; j < m; ++j) AANS += z[i][j] * val[i][j]; } } inline void solve_k_1(){ int i,j; for (i = 0; i < n; ++i) for (j = 0; j < m; ++j) if (z[i][j]) ans[i][j] = 0; vector<vector<int> >a; vector<int>ret; ret.resize(m),a.clear(); for (i = 0; i < n; ++i){ for (j = 0; j < m; ++j) ret[j] = ans[i][j]; a.push_back(ret); } allocate_tickets(a); } namespace Try{ int nl[N],nr[N]; struct Node{ int id,v; inline bool operator < (const Node w) const{ return v < w.v; } }tz[N],tf[N]; int lz,lf; int okz[N],okf[N]; int cntz,cntf,cntzf; inline void addf(int x){ x = tf[x].id; if (okf[x]) return; ++cntf,okf[x] = 1; if (okz[x]) ++cntzf; } inline void delz(int x){ x = tz[x].id; if (!okz[x]) return; --cntz,okz[x] = 0; if (!okf[x]) --cntzf; } inline void work(){ int i; static int Time = -1; ++Time; cntf = cntz = 0; for (lz = lf = i = 0; i < n; ++i){ okz[i] = 0,okf[i] = 0; if (nl[i] >= 0 && z[i][nl[i]] == -1){ ++lf; tf[lf].id = i,tf[lf].v = val[i][nl[i]]; } if (nr[i] >= 0 && z[i][nr[i]] == 1){ ++lz; tz[lz].id = i,tz[lz].v = val[i][nr[i]]; okz[i] = 1,++cntz; } } sort(tf+1,tf+lf+1); sort(tz+1,tz+lz+1); int ll = 1; for (i = 1; i <= lf; ++i){ addf(i); while (ll <= lz && tz[ll].v < tf[i].v) delz(ll),++ll; if (cntz + cntf - cntzf >= n && cntz >= n/2 && cntf >= n/2) break; } int nz = n/2,nf = n/2; for (i = 0; i < n && (nz || nf); ++i){ if (!okz[i] && !okf[i]) continue; if (!okz[i]){ if (!nf) continue; --nf; --cntf; ans[i][nl[i]] = Time,--nl[i]; continue; } if (!okf[i]){ if (!nz) continue; --nz; --cntz; ans[i][nr[i]] = Time,--nr[i]; continue; } if (nz && cntz <= nz){ --nz; ans[i][nr[i]] = Time,--nr[i]; --cntz,--cntf; continue; } --nf; ans[i][nl[i]] = Time,--nl[i]; --cntz,--cntf; continue; } } inline void MAIN(){ for (int i = 0; i < n; ++i){ nr[i] = m-1,nl[i] = 0; while (z[i][nl[i]+1] == -1) ++nl[i]; } while (k--) work(); } } LL find_maximum(int k,vector<vector<int> >x) { ::k=k; int i,j; n = x.size(),m = x[0].size(); for (i = 0; i < n; ++i) for (j = 0; j < m; ++j) ans[i][j] = -1,val[i][j] = x[i][j]; CALCZ::calcz(); if (k == 1){ solve_k_1(); return AANS; } Try::MAIN(); vector<vector<int> >a; vector<int>ret; ret.resize(m),a.clear(); for (i = 0; i < n; ++i){ for (j = 0; j < m; ++j) ret[j] = ans[i][j]; a.push_back(ret); } allocate_tickets(a); return AANS; } ```