带权二分
一种二分答案的套路,又叫做DP凸优化,wqs二分。
用来解决一类题目,要求某个要求出现K次,并且,可以很显然的发现,在改变相应权值的时候,对应出现的次数具有单调性。而且很显然,这种题一般满足一定的要求。而且一般权值为整数二分就可以,但是有的题需要实数二分...而且,边界条件通常很麻烦,调起来想摔电脑。
例题时间:
BZOJ2654: tree
题目大意:给你一个图,里面有白色边和黑色边,问恰好有k条边白色边的最小生成树
直接贪心法肯定是错误的,因此,我们考虑带权二分。
给定一个选择白色边的权值,之后把白色边的边权减去这个权值跑最小生成树,判断是否选了K个白色边。而这个权值通过二分找到。
附上代码:
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <queue>
using namespace std;
#define N 50005
int fa[N],n,m,K,cnt;
struct node
{
int from,to,op;
int val;
}e[N<<1];
int sum,num;
inline bool cmp(const node &a,const node &b)
{
if(a.val==b.val)
return a.op>b.op;
return a.val<b.val;
}
inline int find(int x)
{
if(x==fa[x])return x;
return fa[x]=find(fa[x]);
}
inline void add(int x,int y,int z,int op)
{
e[++cnt].to=y;
e[cnt].from=x;
e[cnt].val=z;
e[cnt].op=op;
return ;
}
void init()
{
for(int i=0;i<N;i++)
{
fa[i]=i;
}
return ;
}
inline int check(int x)
{
sum=0,num=0;
init();
for(int i=1;i<=m;i++)
{
e[i].val+=e[i].op*x;
}
sort(e+1,e+m+1,cmp);
int cnt1=0;
for(int i=1;i<=m;i++)
{
int x=e[i].from,y=e[i].to;
int fx=find(x),fy=find(y);
if(fx!=fy)
{
sum+=e[i].val;
num+=e[i].op;
fa[fx]=fy;
cnt1++;
}
if(cnt1==n-1)break;
}
for(int i=1;i<=m;i++)
{
e[i].val-=e[i].op*x;
}
if(num>=K)return 1;
return 0;
}
int main()
{
scanf("%d%d%d",&n,&m,&K);
for(int i=1;i<=m;i++)
{
int x,y,z,j;
scanf("%d%d%d%d",&x,&y,&z,&j);
add(x,y,z,j^1);
}
int l=-101,r=101;
int ans;
while(l<r)
{
int mid=(l+r)>>1;
if(check(mid))
{
l=mid+1;
ans=sum-K*mid;
}else
{
r=mid;
}
}
printf("%d\n",ans);
return 0;
}
BZOJ5311: 贞鱼
题目大意:给你n个点,每个点与点之间有权值,将n个点分成k份,每份是连续的,每份的代价是这份中任意两点的权值和,求最小代价。
二分分割一次的权值,之后在DP的时候转移一下即可。至于DP的东西去看https://www.cnblogs.com/Winniechen/p/9218864.html
附上代码:
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <cstdlib>
#include <cstring>
using namespace std;
#define N 4005
static char buf[1000000],*p1,*p2;
#define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1000000,stdin),p1==p2)?EOF:*p1++)
#define calc(x,y) (f[x]+((sum[y][y]+sum[x][x]-sum[y][x]-sum[x][y])>>1))
int rd()
{
register int x=0;register char c=nc();
while(c<'0'||c>'9')c=nc();
while(c>='0'&&c<='9')x=(((x<<2)+x)<<1)+c-'0',c=nc();
return x;
}
int sum[N][N],num[N],k,n,s[N][N];long long f[N];
struct node{int l,r,p;}q[N];
bool cmp(int i,int j,int k)
{
long long t1=calc(i,k),t2=calc(j,k);
if(t1==t2)return num[i]<=num[j];
return t1<t2;
}
int find(const node &t,int x)
{
int l=t.l,r=t.r+1;
while(l<r)
{
int m=(l+r)>>1;
if(cmp(x,t.p,m))r=m;
else l=m+1;
}
return l;
}
int check(int x)
{
memset(f,0x3f,sizeof(f));
f[0]=0;int h=0,t=0;q[t++]=(node){1,n,0};num[0]=0;
for(int i=1;i<=n;i++)
{
if(q[h].r<i&&h<t)h++;
f[i]=calc(q[h].p,i)+x;num[i]=num[q[h].p]+1;
if(cmp(i,q[t-1].p,n))
{
while(h<t&&cmp(i,q[t-1].p,q[t-1].l))t--;
if(h==t)q[t++]=(node){i+1,n,i};
else
{
int p=find(q[t-1],i);
q[t-1].r=p-1;
q[t++]=(node){p,n,i};
}
}
}
return num[n];
}
int main()
{
n=rd();k=rd();
for(register int i=1;i<=n;i++)
{
for(register int j=1;j<=n;j++)
{
sum[i][j]=sum[i][j-1]+rd();
}
}
for(register int i=1;i<=n;i++)
{
for(register int j=1;j<=n;j++)
{
sum[i][j]+=sum[i-1][j];
}
}
int l=0,r=1<<30;
while(l<r)
{
int m=(l+r)>>1;
if(check(m)>k)l=m+1;
else r=m;
}
check(l);
printf("%lld\n",f[n]-1ll*l*k);
}
BZOJ1812: [Ioi2005]riv
题目大意:没有题目大意,直接去看题面吧...
Description
Input
Output
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <iostream>
#include <cstdlib>
#include <queue>
#include <bitset>
using namespace std;
#define N 105
struct node
{
int to,next;
}e[N<<1];
int a[N],head[N],dis[N],v[N],cnt,n,m,siz[N],fa[N][N],sf[N],num[N][N],f[N][N],mid;
void add(int x,int y){e[cnt].to=y;e[cnt].next=head[x];head[x]=cnt++;}
void pre(int x)
{
fa[x][0]=x;
for(int i=head[x];i!=-1;i=e[i].next)
{
int to1=e[i].to;dis[to1]=dis[x]+v[to1];
for(int j=0;j<=sf[x];j++)fa[to1][++sf[to1]]=fa[x][j];
pre(to1);
}
}
void dfs(int x)
{
for(int i=0,t;i<=sf[x];i++)t=fa[x][i],f[x][i]=(dis[x]-dis[t])*a[x];
for(int i=head[x];i!=-1;i=e[i].next)
{
int to1=e[i].to;dfs(to1);
for(int j=0;j<=sf[x];j++)
{
if(f[to1][0]+mid<f[to1][j+1]||(f[to1][0]==f[to1][j+1]&&num[to1][0]+1<=num[to1][j+1]))
{
num[x][j]+=num[to1][0]+1;
f[x][j]+=f[to1][0]+mid;
}else f[x][j]+=f[to1][j+1],num[x][j]+=num[to1][j+1];
}
}
}
int check()
{
memset(f,0x3f,sizeof(f));memset(num,0,sizeof(num));dfs(0);
return num[0][0];
}
int main()
{
memset(head,-1,sizeof(head));
scanf("%d%d",&n,&m);
for(int i=1,x;i<=n;i++)scanf("%d%d%d",&a[i],&x,&v[i]),add(x,i);
int l=0,r=1<<21;pre(0);
while(l<r)
{
mid=(l+r)>>1;
if(check()>m)l=mid+1;
else r=mid;
}mid=l;check();
printf("%lld\n",f[0][0]-1ll*mid*m);
return 0;
}
BZOJ4609: [Wf2016]Branch Assignment
题目没有大意
直接求出dis[i]表示正向从i到b+1,和反向从b+1到i的权值和,之后我们发现,将dis[i]排序,之后可以得出,取连续的一段必定不会更劣(贪心可证,为了使更小的dis分配到更大的siz)之后通过决策单调性转移一下即可。
附上代码:
#include <cstdio>
#include <cmath>
#include <algorithm>
#include <iostream>
#include <queue>
#include <cstdlib>
#include <cstring>
#include <vector>
using namespace std;
#define N 5005
#define ll long long
#define calc(x,y) (f[x]+(sum[y]-sum[x])*(y-x-1))
__attribute__((optimize("-O3")))inline char nc() {
static char buf[100000],*p1,*p2;
return p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++;
}
__attribute__((optimize("-O3")))int rd() {
int x=0; char ch=nc();
while(ch<'0'||ch>'9') ch=nc();
while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+ch-'0',ch=nc();
return x;
}
struct node{int l,r,p;}q[N];vector<pair<int ,int > >G[2][N];
int n,b,s,r,num[N],vis[N];ll dis[N],a[N],sum[N],f[N];
void Dijkstra(bool op)
{
memset(dis,0x3f,sizeof(dis));dis[b+1]=0;memset(vis,0,sizeof(vis));
priority_queue<pair<ll,int> >q;q.push(make_pair(0,b+1));
while(!q.empty())
{
int x=q.top().second;q.pop();if(vis[x])continue;vis[x]=1;
for(int i=0;i<G[op][x].size();i++)
{
int to1=G[op][x][i].first,val=G[op][x][i].second;
if(dis[to1]>dis[x]+val)dis[to1]=dis[x]+val,q.push(make_pair(-dis[to1],to1));
}
}
for(int i=1;i<=b;i++)a[i]+=dis[i];
}
int find(const node &t,int x)
{
int l=t.l,r=t.r+1;
while(l<r)
{
int m=(l+r)>>1;
if(calc(t.p,m)<=calc(x,m))l=m+1;
else r=m;
}return l;
}
int check(ll x)
{
int h=0,t=0;q[t++]=(node){0,n,0};f[0]=0;num[0]=0;
for(int i=1;i<=n;i++)
{
while(h<t&&q[h].r<i)h++;
f[i]=calc(q[h].p,i)+x;num[i]=num[q[h].p]+1;
if(calc(i,n)<calc(q[t-1].p,n))
{
while(h<t&&calc(q[t-1].p,q[t-1].l)>calc(i,q[t-1].l))t--;
if(h==t)q[t++]=(node){i,n,i};
else
{
int x=find(q[t-1],i);q[t-1].r=x-1;q[t++]=(node){x,n,i};
}
}
}
return num[n];
}
int main()
{
n=rd();b=rd();s=rd();r=rd();
for(int i=1,x,y,z;i<=r;i++)x=rd(),y=rd(),z=rd(),G[0][x].push_back(make_pair(y,z)),G[1][y].push_back(make_pair(x,z));
Dijkstra(0);Dijkstra(1);n=b;sort(a+1,a+n+1);for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i];
ll l=0,r=1ll<<60;
while(l<r)
{
ll m=(l+r)>>1;
if(check(m)>s)l=m+1;
else r=m;
}check(l);
printf("%lld\n",f[n]-l*s);
}
大概还有什么林克卡特树之类的,就不多说了...