[CF802N][jzoj5378]闷声刷大题

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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值