Description
给出两个长度为n的数列a和b,你要做k次匹配,每次选择匹配的a[i],b[j]必须满足i<=j
且每个a和b只能被匹配一次。匹配一次的代价为a[i]+b[j],求最小代价。
k<=n<=1.5*1e5
Solution
非题解法,作比赛时灵稽一动的产物,复杂度多一个log似乎能卡过去(原谅我数大)
当你在想部分分的Dp时,你会不会觉得这个k非常的烦人?
无论你怎么设状态,你都无法逃过k这一维,当然费用流也要跑k次。
如果我们设F[i]表示当做i次匹配时的最小代价,可以发现F是个凸函数,也就是斜率不断增大(或不减)
那么我们可以通过二分一个斜率c,然后把整个函数图像转一下,求最低点的答案。
如果最低点就是k的话那么我们就求出了F[k]!
这个听栋栋讲似乎叫做凸优化,是一个应用很广的算法
这个东西对应到题目里就相当于你每次做一次匹配,就把代价加个c
最后求没有限制的最小代价,这个可以用一个贪心来求
为了防止整数二分出现一些很猎奇的情况,我们可以对每次最优解加上一个特殊限制,比如匹配次数最小之类的。。。。
然后就可以卡卡常。。。反正我写了辣鸡STL的辣鸡set被卡成暴力分QwQ
Code
#include <set>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;
typedef long long ll;
const int N=1.5*1e5+5,M=405,inf=2*1e9;
struct note{
int v,bz;
friend bool operator < (note x,note y) {
return x.v<y.v;
}
}d[N];
set<note> A;
ll ans,f[2][M][M];
int n,K,sz,a[N],b[N];
int read() {
char ch;
for(ch=getchar();ch<'0'||ch>'9';ch=getchar());
int x=ch-'0';
for(ch=getchar();ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x;
}
void up(int x) {
while (x&&d[x/2].v>d[x].v) swap(d[x/2],d[x]),x/=2;
}
void down(int x) {
while(x*2<=sz) {
x=x*2;
if(x+1<=sz&&d[x+1].v<d[x].v) x++;
if (d[x/2].v>d[x].v) swap(d[x/2],d[x]);
else break;
}
}
int calc(int mid) {
int tot=0;ans=sz=0;
fo(i,1,n) {
note x;x.v=a[i];x.bz=1;
d[++sz]=x;up(sz);
note top=d[1];
//A.insert(x);
//note top=*A.begin();
ll now=(ll)top.v+(ll)b[i]+(ll)mid;
if (now<0) {
tot+=top.bz;
//A.erase(A.begin());
x.v=-now+top.v;x.bz=0;
//A.insert(x);
d[1]=x;down(1);
} else now=0;
ans+=now;
}
fo(i,1,sz) d[i].v=d[i].bz=0;
return tot;
}
int main() {
freopen("orz.in","r",stdin);
freopen("orz.out","w",stdout);
n=read();K=read();
int val=0;bool ok=1;
fo(i,1,n) {
a[i]=read();
if (!val) val=a[i];
else if (val!=a[i]) ok=0;
}
fo(i,1,n) b[i]=read();
if (ok) {
sort(b+1,b+n+1);
ll ans=(ll)val*K;
fo(i,1,K) ans+=b[i];
printf("%lld\n",ans);
return 0;
}
int l=-inf,r=0,mid;
while (l<r) {
mid=(l+r)/2;
int cnt=calc(mid);
if (cnt==K) break;
if (cnt<K) r=mid;
else l=mid;
}
printf("%lld\n",ans-(ll)K*mid);
}