卧薪尝胆,厚积薄发。
DP凸优化
Date: Sun Mar 31 18:12:02 CST 2019 In Category: 总结

问题:

给出一些物品和选择物品的限制条件,要求刚好选 $m$ 个,最大化 $/$ 最小化权值和。
我们可以得到一个比较显然的 $DP$ 方程 $f[i][j]$ 表示前 $i$ 个刚好选 $j$ 个的最值,最后的答案就是 $f[n][m]$ ,设 $g[k]=f[n][k]$ ,那么 $g[k]$ 是一个凸函数,也就是说差分之后的数组是单调的。
并且还满足一个条件是如果没有第二维的限制也就是说那么 $DP$ 可以优化到

解决方案:

既然 $g[k]$ 是凸函数,那我们就把他画在坐标系下:
这个时候我们找到的最高点就是 $D$ 点,但是如果我们要求恰好选 $5$ 个的最大值,也就是 $F$ 点,那么就找不到,这个时候我们可以给所有点加上一个偏移量 $C$ ,也就是把点 $(x,y)$ 变成 $(x,y+xC)$ ,比如说 $C=1$ :
这个时候我们再找就可以找到 $F$ 点,再减掉 $C\times m$ 就是我们想要的答案。
那么这就给了我们一个思路,我们可以给每个物品的代价 $+C$ 然后 $DP$ ,最后再把 $f[n]$ 减掉 $C\times m$ 就行了,复杂度是 $O(n\log n)$ 的。

边界问题:

我们可能会得到一个尴尬的结果:当 $C=x$ 时, $cnt=m-1$ ,当 $C=x+1$ 时, $cnt=m+1$ ,这个时候我们只要保证每次计算的时候只保留最小的那个解,在二分到最后的时候如果极值点不为 $n$ 的话应比 $n$ 小。

#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstdio>
#include<cmath>
#include<cstring>
using namespace std;
int n,m,ned;
#define MAXM 100010
struct edge
{
int u,v,val,col;
}e[MAXM];
bool cmp_val(edge a,edge b){return (a.val == b.val ? a.col < b.col : a.val < b.val);}
#define MAXN 50010
int f[MAXN];
int find(int x){return (f[x] == x ? x : f[x] = find(f[x]));}
int ans;
int query(int k)
{
ans = 0;
for(int i = 1;i <= n;++i)f[i] = i;
for(int i = 1;i <= m;++i)
if(e[i].col == 0)
e[i].val += k;
sort(e + 1,e + 1 + m,cmp_val);
int res = 0;
for(int i = 1;i <= m;++i)
{
int p = find(e[i].u),q = find(e[i].v);
if(p == q)continue;
f[p] = q;
if(e[i].col == 0)++res;
ans += e[i].val;
}
for(int i = 1;i <= m;++i)
if(e[i].col == 0)
e[i].val -= k;
return res;
}
int main()
{
scanf("%d%d%d",&n,&m,&ned);
for(int i = 1;i <= m;++i)
{
scanf("%d%d%d%d",&e[i].u,&e[i].v,&e[i].val,&e[i].col);
++e[i].u;++e[i].v;
}
int l = -100,r = 100,mid;
while(l < r)
{
mid = ((l + r + 1) / 2);//cout << mid << endl;
if(query(mid) >= ned)l = mid;
else r = mid - 1;
}//cout << l << endl;
query(l);
cout << ans - ned * l << endl;
return 0;
}
In tag:
Copyright © 2020 wjh15101051
ღゝ◡╹)ノ♡