【DP】BZOJ5311&CF321E 贞鱼 & WQS二分(DP凸优化)总结

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34454069/article/details/85717891

先看一道板题: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) 内,算出对某个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个的代价。

只不过。。这方法目前还有一个问题我无法理解:就是我代码倒数第二行注释的部分。
请知道的读者务必回复我。

猜你喜欢

转载自blog.csdn.net/qq_34454069/article/details/85717891