先看一道板题:BZOJ2654 tree(据说这题数据相当水,但我没去做)
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树。
题目保证有解
设f(x)表示恰有x个白点的情况下,最小生成树的代价。
很容易发现(???),f(x)的图像是凸的!(哈??)
我们可以尝试把这个图像用一些直线取求切点,由于其为凸函数,所以必然有一个直线能切到要求的自变量。
具体一点说:可以二分:每使用一个白点的额外代价cost,从而使得当cost为某一值的情况下,刚好在最优解中有need个白点被选出。此时,就可以直接把当前的最优解减去cost*need就是答案了。
另一道板题:【BZOJ5311】【CF321E】贞鱼
设f[x]表示前i条鱼坐车的代价,每次多一辆车就加入cost的代价。
再通过决策单调性,就能在
O
(
N
l
o
g
N
)
O(NlogN)
O(NlogN)内,算出对某个cost的最小总代价以及此时用的车数。然后就可以二分这个cost来刚好卡到k辆车。
#include<cstdio>
#include<cstring>
#include<algorithm>
#define SF scanf
#define PF printf
#define MAXN 4010
#define INF 0x7FFFFFFF
using namespace std;
const int MAXSIZE=1<<15;
char buf[MAXSIZE],*p1=buf,*p2=buf;
#define gc p1==p2&&(p2=(p1=buf)+fread(buf,1,MAXSIZE,stdin),p1==p2)?EOF:*p1++
void Read(int &x)
{
x=0;char ch=gc;
while(ch<'0'||ch>'9'){ch=gc;}
while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=gc;}
}
//void Read(ll &x){
// char c;
// while(c=getchar(),c!=EOF&&(c<'0'||c>'9'));
// x=c-'0';
// while(c=getchar(),c!=EOF&&c>='0'&&c<='9')
// x=x*10+c-'0';
//}
struct node{
int l,r,id;
node () {}
node (int l1,int r1,int id1):l(l1),r(r1),id(id1) {}
}q[MAXN];
int hd,tl,n;
int s[MAXN][MAXN];
pair<int,int> f[MAXN];
int calc(int x,int y){
return f[x].first+(s[y][y]+s[x][x]-s[x][y]*2ll)/2ll;
}
int find_mid(int x,int y,int l,int r){
int pos=l;
while(l<=r){
int mid=(l+r)>>1;
if(calc(x,mid)<=calc(y,mid)){
pos=mid;
l=mid+1;
}
else
r=mid-1;
}
return pos;
}
void solve(int cost){
f[0]=make_pair(0,0);
int hd=1,tl=1;
q[1]=node(1,n,0);
for(int i=1;i<=n;i++){
int j=q[hd].id;
f[i]=make_pair(f[j].first+(s[i][i]+s[j][j]-s[i][j]*2)/2ll+cost,f[j].second+1);
q[hd].l++;
while(hd<=tl&&q[hd].l>q[hd].r)
hd++;
if(hd>tl||calc(i,n)<calc(q[tl].id,n)){
while(hd<=tl&&calc(i,q[tl].l)<calc(q[tl].id,q[tl].l))
tl--;
if(hd<=tl){
int pos=find_mid(q[tl].id,i,q[tl].l,q[tl].r);
q[tl].r=pos;
q[++tl]=node(pos+1,n,i);
}
else
q[++tl]=node(i+1,n,i);
}
}
}
int k;
int main(){
SF("%d%d",&n,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++){
Read(s[i][j]);
// SF("%lld",&s[i][j]);
s[i][j]+=s[i-1][j]+s[i][j-1]-s[i-1][j-1];
}
int l=0,r=INF,ans;
while(l<=r){
int mid=1ll*(l+r)>>1ll;
solve(mid);
if(f[n].second<=k){
ans=mid;
r=mid-1;
}
else
l=mid+1;
}
solve(ans);
// PF("[%d,%d]",f[n].second,ans);
PF("%d\n",f[n].first-k*ans);//'k'*ans???f[n].second*ans->WA???
}
综上:当某个DP的限制刚好选M个,求最小/最大代价,且其答案满足单峰性,就可以通过二分每选一个增加的代价,来使得答案最优时,刚好也同时选M个的代价。
只不过。。这方法目前还有一个问题我无法理解:就是我代码倒数第二行注释的部分。
请知道的读者务必回复我。