带权二分

带权二分

一种二分答案的套路,又叫做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

几乎整个Byteland王国都被森林和河流所覆盖。小点的河汇聚到一起,形成了稍大点的河。就这样,所有的河水都汇聚并流进了一条大河,最后这条大河流进了大海。这条大河的入海口处有一个村庄——名叫Bytetown 在Byteland国,有n个伐木的村庄,这些村庄都座落在河边。目前在Bytetown,有一个巨大的伐木场,它处理着全国砍下的所有木料。木料被砍下后,顺着河流而被运到Bytetown的伐木场。Byteland的国王决定,为了减少运输木料的费用,再额外地建造k个伐木场。这k个伐木场将被建在其他村庄里。这些伐木场建造后,木料就不用都被送到Bytetown了,它们可以在 运输过程中第一个碰到的新伐木场被处理。显然,如果伐木场座落的那个村子就不用再付运送木料的费用了。它们可以直接被本村的伐木场处理。 注意:所有的河流都不会分叉,也就是说,每一个村子,顺流而下都只有一条路——到bytetown。 国王的大臣计算出了每个村子每年要产多少木料,你的任务是决定在哪些村子建设伐木场能获得最小的运费。其中运费的计算方法为:每一块木料每千米1分钱。 编一个程序: 1.从文件读入村子的个数,另外要建设的伐木场的数目,每年每个村子产的木料的块数以及河流的描述。 2.计算最小的运费并输出。

Input

第一行 包括两个数 n(2<=n<=100),k(1<=k<=50,且 k<=n)。n为村庄数,k为要建的伐木场的数目。除了bytetown外,每个村子依次被命名为1,2,3……n,bytetown被命名为0。 接下来n行,每行包涵3个整数 wi——每年i村子产的木料的块数 (0<=wi<=10000) vi——离i村子下游最近的村子(或bytetown)(0<=vi<=n) di——vi到i的距离(km)。(1<=di<=10000) 保证每年所有的木料流到bytetown的运费不超过2000,000,000分 50%的数据中n不超过20。

Output

输出最小花费,精确到分。

这道题其实并不用带权二分的,直接树形DP,N^2*K(N^2*K^2)都可以过...带权二分跑不过N^2*K...二分建立镇守府的权值,状态f[i][j]表示i的子树中,所有节点最多到第j层父亲的最小代价,之后再记录一个建立了多少个镇守府即可。

附上代码:

#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);
}

  

大概还有什么林克卡特树之类的,就不多说了...

原文地址:https://www.cnblogs.com/Winniechen/p/10034423.html

时间: 2024-09-28 16:02:24

带权二分的相关文章

Codeforces.739E.Gosha is hunting(DP 带权二分)

题目链接 \(Description\) 有n只精灵,两种精灵球,每种球能捕捉到第i只精灵的概率已知.求用A个低级球和B个高级球能捕捉到精灵数的最大期望. \(Solution\) 设f[i][a][b]表示前i只用了a个低级球,b个高级球的最大期望.转移时四种情况显然.复杂度\(\mathcal O(nAB)\). 随着某种球可使用数的增多,f应是凸函数,即增长越来越慢.而且两种球都满足这个性质. 于是可以wqs二分套wqs二分了..没有个数限制取个max记一下个数就可以了.复杂度\(\mat

P4383 [八省联考2018]林克卡特树lct 树形DP+凸优化/带权二分

$ \color{#0066ff}{ 题目描述 }$ 小L 最近沉迷于塞尔达传说:荒野之息(The Legend of Zelda: Breath of The Wild)无法自拔,他尤其喜欢游戏中的迷你挑战. 游戏中有一个叫做"LCT" 的挑战,它的规则是这样子的:现在有一个N 个点的 树(Tree),每条边有一个整数边权vi ,若vi >= 0,表示走这条边会获得vi 的收益:若vi < 0 ,则表示走这条边需要支付- vi 的过路费.小L 需要控制主角Link 切掉(

飘逸的python - 带权随机算法及在抽奖中的应用

带权随机在游戏开发中重度使用,各种抽奖和爆装备等. 运营根据需要来配置各个物品出现的概率. 今天要说的这个带权随机算法思想很简单,就是"把所有物品根据其权重构成一个个区间,权重大的区间大.可以想象成一个饼图.  然后,扔骰子,看落在哪个区间," 举个栗子,有个年终抽奖,物品是iphone/ipad/itouch. 主办方配置的权重是[('iphone', 10), ('ipad', 40), ('itouch', 50)]. 用一行代码即可说明其思想,即random.choice(['

BZOJ4025 二分图 分治 并查集 二分图 并查集按秩合并 带权并查集

原文链接http://www.cnblogs.com/zhouzhendong/p/8683831.html 题目传送门 - BZOJ4025 题意 有$n$个点,有$m$条边.有$T$个时间段.其中第$i$条边连接节点$x_i,y_i$,并且在$start_i$时刻出现,在$end_i$时刻消失.问每一个时刻的图是不是二分图. $n\leq 10^5,m\leq 2\times 10^5,T\leq 10^5$ 题解 真是一道好题. 做这题我才发现我从来没写过按秩合并的并查集QAQ. 先考虑按

中位数及带权中位数题集

codevs 3625 1 #include <bits/stdc++.h> 2 using namespace std; 3 int x[11111], y[11111]; 4 int main() 5 { 6 int n; scanf("%d", &n); 7 for(int i = 1; i <= n; i++) scanf("%d %d", &x[i], &y[i]); 8 ///先处理y 9 sort(y + 1,

KM(Kuhn-Munkres)算法求带权二分图的最佳匹配

KM(Kuhn-Munkres)算法求带权二分图的最佳匹配 相关概念 这个算法个人觉得一开始时有点难以理解它的一些概念,特别是新定义出来的,因为不知道是干嘛用的.但是,在了解了算法的执行过程和原理后,这些概念的意义和背后的作用就渐渐的显示出来了.因此,先暂时把相关概念列出来,看看,有个大概印象就好,等到了解了算法的流程后,在看原理中会有这些概念,那个时候回来细看就好了. 完备匹配:定义 设G=<V1,V2,E>为二部图,|V1|≤|V2|,M为G中一个最大匹配,且|M|=|V1|,则称M为V1

Java数据结构——带权图

带权图的最小生成树——Prim算法和Kruskal算法 带权图的最短路径算法——Dijkstra算法 package graph; // path.java // demonstrates shortest path with weighted, directed graphs 带权图的最短路径算法 // to run this program: C>java PathApp ////////////////////////////////////////////////////////////

并查集模板 &amp;&amp; 带权并查集模板

不带权: 1 int f[50050]; 2 void init(void) 3 { 4 for(int i=1;i<=n;i++) 5 f[i]=i; 6 } 7 int fd(int x) 8 { 9 return f[x]==x?x:fd[x]=fd(f[x]); 10 } 11 int uion(int x,int y) 12 { 13 int fa=fd(x),fb=fd(y); 14 if(fa!=fb)f[fa]=fb; 15 } 带权: 1 int f[K],rl[K]; 2 3

hdu3038(带权并查集)

题目链接: http://acm.split.hdu.edu.cn/showproblem.php?pid=3038 题意: n表示有一个长度为n的数组, 接下来有m行形如x, y, d的输入, 表示从第x,个元素到第y个元素的和为d(包括x, 和y), 问m行输入里面有几个是错误的(第一个输入是正确的); 思路: 很显然带权并查集咯,我们可以用距离的概念代替和的概念比较好理解一点,d表示x到y的和即x到y的距离; 可以用rank[x]表示x到其父亲节点的距离,  将正确的距离关系合并到并查集中