数学技巧巧夺最优解!
CashCollectFactory · · 题解
题目大意
有
数学解法
对于每个人的总杆数关于杆数限制的函数,是一个分段线性函数,如下图所示。
当限制超过他的最大杆数,直线是平的,前面的线段的斜率就是这个人有多少杆的得分超过了这一个限制。
由此性质,我们不难想出如下做法:
首先,将每个人自己的分数从大到小排序,用两重循环求出每个人的最佳排名,外层循环每次处理一个人,内层循环遍历其他所有人。
对于内层循环,我们首先将两个人的分数从大到小排列,在头部加一个
遍历完其他人后,首先可以知道在无限制的情况下的排名,再通过之前整理的排名变化情况,就可以得知在不同限制下的排名,从而得知最佳排名,于是本题就完成啦!
最后,算法的时间复杂度约为
具体的实现细节见下方代码:
代码时间(码丑勿喷,我颜良文丑)
#include<bits/stdc++.h>
using namespace std;
int main(){
int P,H;
while(cin >> P >> H) {
vector<vector<int>> S(P, vector<int>(H));
vector<int64_t> tot(P);
for(int i = 0; i < P; i++) {
for(int j = 0; j < H; j++) {
cin >> S[i][j];
tot[i] += S[i][j];
}
S[i].push_back(0); //设置一个最小的地板,在26行的循环递增条件用
sort(S[i].begin(), S[i].end(), greater<int>());
}
for(int i = 0; i < P; i++) {
vector<pair<int, int>> events; //pair中第一个int为取的lim,第二个表示取此lim时,排名需要如何变化
int cur = 0;
for (int j = 0; j < P; j++) {
int64_t itot = tot[i], jtot = tot[j], lim = 1000000000;
if (jtot <= itot) cur++;
for (int ih = 0, jh = 0; ih < H || jh < H; S[i][ih] > S[j][jh] ? ih++ : jh++) {
bool old = (jtot <= itot); //这里的条件比较巧妙,具体看下面
int v = max(S[i][ih], S[j][jh]); //不断取ij的scores中较大者为lim,如果有重复,当两个都到达小值时才会进入下一lim
itot -= (lim-v) * ih; jtot -= (lim-v) * jh; //对两者总score进行裁减
lim = v;
if (!old && jtot <= itot) {
/*排名要+1,在前一lim需要jtot>itot(=不行,因为排名算的是小于等于自己分数的个数),当前lim jtot <= itot
*直线过(lim,tot)点,斜率是h,分别列出ij两直线的方程式,求出交点横坐标就是lim+(itot-jtot)/(jh-ih), 同时计算出的是double,需要向下取整(转int已经执行了)
*判断条件相当于if(orig_jtot>orig_itot && (jtot<itot || jtot==itot))*/
events.emplace_back(lim+(itot-jtot)/(jh-ih), 1);
} else if (old && jtot > itot) {
/*排名要-1,在前一lim需要jtot<=itot,在当前lim jtot>itot,并且如果交点是整数,-1的效果要计入前一个点,所以会有jtot-itot-1
*判断条件相当于if((orig_jtot>orig_itot || orig_jtot==orig_itot) && jtot>itot)*/
events.emplace_back(lim+(jtot-itot-1)/(ih-jh), -1);
}
}
}
sort(events.begin(), events.end(), greater<pair<int, int>>());
int ret = cur;
for (auto const& e : events) {cur += e.second; ret = min(ret, cur);}
cout<<ret<<endl;
}
}
return 0;
}
补充一句,本代码需要使用 C++11 以上版本才可以正常编译,只因里面用了“auto”这个新东西~