WQS二分 学习笔记 + 例题([BZOJ2654]Tree、[联考2018]林克卡特树)
目录
- 问题类型
- WQS二分思路
- 形象地说
- 板题
- 题意 & 解法
- CODE
- 省选题
- 题意 & 解法
- CODE
问题类型
形如“恰好取 k 个……时的最优答案(并非具体方案)”的问题。
- 保证存在合法方案。
WQS二分思路
我们先把限制去掉,这时候最终的方案肯定不一定取了 k 个该物品,
假设最优方案该物品个数更小,我们可以每取一个该物品就加一份额外贡献(宏观调控!)来使最终方案的该物品个数增加,若最优方案该物品个数比 k 大,我们可以每取一个该物品就算上一份额外花费来使最终方案的该物品个数减少。
总的来说,就是每取一个该物品就多加一个权值 x,该权值可以为正负,可以发现最优方案中该物品的个数关于 x 一定是单调的,那么就可以二分一个 x,然后在无限制条件下求出最优答案以及最优方案中该物品的个数,使个数刚好大于等于(或小于等于) k。
由于保证存在合法方案,此时相当于得到了刚好选 k 个该物品算上额外权值 x 时的最优答案,这时并不一定我们的最优方案恰好 k 个,但是我们的最优答案和恰好 k 个时的答案是一样的,那么再减去 k*x 就是我们要的答案。
于是我们最终的复杂度就是 O(无限制情况下的复杂度*log)
形象地说
你是一个商店的老板,商店里每种物品有一定的利润,每个月卖出去的物品总数一定,不同种商品销量之间的内在联系千奇百怪,你找不到规律。处于各种考虑,现在你想知道某种商品恰好卖出 k 个时盈利值的最小值,你只能实践。
顾客们很聪明,他们会在满足各种限制的情况下每个月让你的钱包里的钱最少。
如果这种商品正常情况下超过了 k 个,那好办,每买一个该种商品多收一份手续费 ¥x 就行了,手续费进入你的腰包,但是肯定不能算在盈利值里,这样该商品销量会减少。
如果这种商品正常情况下不到 k 个,你可以“行贿”,每买一个该种物品你偷偷给点好处 ¥x ,但是这也不能算在盈利值里,这样该商品销量会增加。
这样一来,你二分一个 x 值,查看盈利值,然后减去你预计的行贿/手续费总数,就可以试验最少的月数得到你想知道的值。
WQS二分就是类似的思路。
板题
题意 & 解法
[BZOJ2654]Tree:
给你一个无向带权连通图,每条边是黑色或白色。让你求一棵最小权的恰好有need条白色边的生成树,输出此时的总权值,题目保证有解。
通过额外给白边加一份权值 x,调控白边的数量,求出白边数量刚好大于等于need时的最小生成树权值,然后减去 x*need,复杂度 O ( n log 2 n ) O(n\log^2n) O(nlog2n)。
细节:
- 求最小生成树时,权值相等的边优先选白边。
CODE
#include<cstdio>
#include<cstring>
#include<vector>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 100005
#define DB double
#define LL long long
#define ENDL putchar('\n')
#define lowbit(x) (-(x) & (x))
LL read() {LL f = 1,x = 0;char s = getchar();while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}return f * x;
}
const int MOD = 1000000007;
int n,m,i,j,s,o,k,nd;
int U[MAXN],V[MAXN],W[MAXN],cl[MAXN];
int fa[MAXN],b[MAXN];
int findf(int x) {return x==fa[x] ? x:(fa[x] = findf(fa[x]));}
void unionSet(int a,int b) {fa[findf(a)] = findf(b);}
int ADD; LL ANS = 0;
bool cmp(int a,int b) {if(W[a]-(1-cl[a])*ADD == W[b]-(1-cl[b])*ADD) return cl[a] < cl[b];return W[a]-(1-cl[a])*ADD < W[b]-(1-cl[b])*ADD;
}
int check(int md) {ADD = md; ANS = 0;for(int i = 1;i <= n;i ++) fa[i] = i;for(int i = 1;i <= m;i ++) b[i] = i;sort(b + 1,b + 1 + m,cmp);int ct = 0;for(int i = 1;i <= m;i ++) {int s = U[b[i]],o = V[b[i]];if(findf(s) != findf(o)) {unionSet(s,o);ANS += W[b[i]] - (1-cl[b[i]]) * ADD;if(cl[b[i]] == 0) ct ++;}}return ct;
}
int main() {n = read();m = read();nd = read();for(int i = 1;i <= m;i ++) {U[i] = read()+1; V[i] = read()+1;W[i] = read(); cl[i] = read();}int l = -100,r = 100,mid;while(l < r) {mid = ((l + r + 20000) >> 1) - 10000;if(check(mid) >= nd) r = mid;else l = mid + 1;}check(l);printf("%lld\n",ANS + nd*1ll*l);return 0;
}
省选题
题意 & 解法
[八/九省联考2018]林克卡特树
题意很好转化:一棵带正负边权的无根树,恰好选 k+1 条不相交的路径(单点可以算路径),求总的路径权值和最大值。
我们给每条路径一个“报酬” x (可正负),然后跑 dp,令 d p [ i ] [ 0 / 1 / 2 ] dp[i][0/1/2] dp[i][0/1/2] 分别表示点 i i i 不在路径中、点 i i i 为一条路径的上端点、点 i i i 为一条路径的 l c a lca lca 时子树 i i i 的最大权值和满足最大权值时的最多路径条数:
令 j j j 为此时遍历到的新的 i i i 的儿子,等式右边都是上一个儿子结束时的值
- d p [ i ] [ 0 ] ( n e w ) = m a x { d p [ i ] [ 0 ] , d p [ i ] [ 0 ] + d p [ j ] [ 0 / 1 / 2 ] } dp[i][0](new) = max\{dp[i][0],dp[i][0]+dp[j][0/1/2] \} dp[i][0](new)=max{dp[i][0],dp[i][0]+dp[j][0/1/2]}
- d p [ i ] [ 1 ] ( n e w ) = m a x { d p [ i ] [ 1 ] , d p [ i ] [ 1 ] + d p [ j ] [ 0 / 1 / 2 ] , d p [ i ] [ 0 ] + d p [ j ] [ 1 ] + w i , j , d p [ i ] [ 0 ] + d p [ j ] [ 0 / 1 / 2 ] + x } dp[i][1](new) = max\{dp[i][1],dp[i][1]+dp[j][0/1/2],dp[i][0]+dp[j][1]+w_{i,j},dp[i][0]+dp[j][0/1/2]+x \} dp[i][1](new)=max{dp[i][1],dp[i][1]+dp[j][0/1/2],dp[i][0]+dp[j][1]+wi,j,dp[i][0]+dp[j][0/1/2]+x}
(不变、从前面的儿子连过来、从 j 连过来、自己单独成路径) - d p [ i ] [ 2 ] ( n e w ) = m a x { d p [ i ] [ 2 ] , d p [ i ] [ 2 ] + d p [ j ] [ 0 / 1 / 2 ] , d p [ i ] [ 1 ] + d p [ j ] [ 1 ] + w i , j − x } dp[i][2](new) = max\{dp[i][2],dp[i][2]+dp[j][0/1/2],dp[i][1]+dp[j][1]+w_{i,j}-x \} dp[i][2](new)=max{dp[i][2],dp[i][2]+dp[j][0/1/2],dp[i][1]+dp[j][1]+wi,j−x}
(不变、整条路径在先前的子树、从旧儿子连向新儿子)
同时维护最多路径条数,这个就比较简单了,不展开。
最后得到路径条数刚好大于等于 k+1 时的 m a x { d p [ r o o t ] [ 0 / 1 / 2 ] } max\{dp[root][0/1/2]\} max{dp[root][0/1/2]} ,把它减去 (k+1)*x 就是答案了。
CODE
#include<vector>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 300005
#define DB double
#define LL long long
#define ENDL putchar('\n')
LL read() {LL f=1,x=0;char s = getchar();while(s < '0' || s > '9') {if(s=='-')f = -f;s = getchar();}while(s >= '0' && s <= '9') {x=x*10+(s-'0');s = getchar();}return f * x;
}
int n,m,i,j,s,o,k;
struct it{LL nm,ct;it(){nm=ct=0;}it(LL N,LL C){nm=N;ct=C;}
};
it bing(it a,it b) {if(a.nm == b.nm) return a.ct > b.ct ? a:b;return a.nm > b.nm ? a:b;
}
LL aw;
vector<it> g[MAXN];
it dp[MAXN][3];
void dfs(int x,int fa) {dp[x][1] = dp[x][2] = it((LL)-1e18,0ll);dp[x][1] = bing(dp[x][1],it(aw,1));dp[x][0] = it(0,0);for(int i = 0;i < (int)g[x].size();i ++) {int y = g[x][i].nm;if(y != fa) {LL W = g[x][i].ct;dfs(y,x);it dp0 = dp[x][0],dp1 = dp[x][1],dp2 = dp[x][2];it dpy = bing(dp[y][0],bing(dp[y][1],dp[y][2]));dp[x][0] = bing(dp[x][0],it(dp0.nm+dpy.nm,dp0.ct+dpy.ct));dp[x][1] = bing(dp[x][1],bing(it(dp1.nm+dpy.nm,dp1.ct+dpy.ct),it(dp0.nm+dp[y][1].nm+W,dp0.ct+dp[y][1].ct)));dp[x][1] = bing(dp[x][1],it(dp0.nm+dpy.nm+aw,dp0.ct+dpy.ct+1));dp[x][2] = bing(dp[x][2],it(dp2.nm+dpy.nm,dp2.ct+dpy.ct));dp[x][2] = bing(dp[x][2],it(dp1.nm+dp[y][1].nm+W-aw,dp1.ct+dp[y][1].ct-1));}}return ;
}
it check(LL ad) {aw = ad;dfs(1,0);return bing(bing(dp[1][0],dp[1][1]),dp[1][2]);
}
int main() {n = read();m = read();for(int i = 1;i < n;i ++) {s = read();o = read();k = read();g[s].push_back(it(o,k));g[o].push_back(it(s,k));}LL l = -1e13,r = 1e13,mid;while(l < r) {mid = ((l + r + (LL)2e17) >> 1) - (LL)1e17;it as = check(mid);if(as.ct >= m+1) r = mid;else l = mid+1;}it as = check(l);printf("%lld\n",as.nm - (m+1)*1ll*l);return 0;
}
WQS二分 学习笔记 + 例题([BZOJ2654]Tree、[联考2018]林克卡特树)相关推荐
- P4383 [八省联考2018]林克卡特树(树形dp+wqs二分)
[八省联考2018]林克卡特树 题目大意:给定一棵有负权边的树,现在必须恰好删去 k k k条边,并加上恰好 k k k条权值为 0 0 0的边,要求最大化它的直径长度. 首先考虑删去 K K K条边 ...
- luogu4383 bzoj5252[八省联考2018]林克卡特树lct
** [八省联考2018]林克卡特树lct** luogu bzoj 分析 很神仙的一道wqs二分.是真的不会切>-< 如果已经切完了,最优秀的方案就是每个联通块搞直径然后连起来一定是最优 ...
- LuoguP4383 [八省联考2018]林克卡特树lct
LuoguP4383 [八省联考2018]林克卡特树lct https://www.luogu.org/problemnew/show/P4383 分析: 题意等价于选择\(K\)条点不相交的链,使得 ...
- [八省联考2018]林克卡特树
林克卡特树 题解 挺简单的一道题. 原题断 k k k条边连 k k k条边权为 0 0 0的边相当于寻去 k + 1 k+1 k+1条不相交链出来,将它们连上得到的结果. 所以我们要从原树中选取 k ...
- [学习笔记]dp凸优化/wqs二分[八省联考2018]林克卡特树lct
废话 很早就想学wqs二分,结果拖了好久.因为以前是看了几遍都没有懂..(太菜了 后来因为计划里凸优化的题(比如CF321E Ciel and Gondolas,CF739E Gosha is hun ...
- 洛谷P4383 [八省联考2018]林克卡特树lct(DP凸优化/wqs二分)
题目描述 小L 最近沉迷于塞尔达传说:荒野之息(The Legend of Zelda: Breath of The Wild)无法自拔,他尤其喜欢游戏中的迷你挑战. 游戏中有一个叫做"LC ...
- P4383 [八省联考 2018] 林克卡特树(wqs二分、树形dp)
解析 它还真的不难. 乐. 这题没做出来有些谔谔. 外层wqs二分显而易见,里面不知道为啥我总觉得这个题可以贪心. 然后一直试图在原树直径上下功夫,一筹莫展. 看到题解"dp"两个 ...
- 洛谷P4383 [八省联考2018]林克卡特树
题目描述 题解 题目可以转化一下,就是要在原树中选出 k+1k+1k+1 条不相交的链使得其权值和最大. 考虑暴力 dp\text{dp}dp : f[u][i][0/1/2]f[u][i][0/1/ ...
- [八省联考 2018] 林克卡特树 题解
这道题我前前后后做了一年,共过了 4 4 4 遍,每次都有的新的理解:这次我认为自己理解透了,于是就写了一篇题解. 这道题是我入坑看到的第一道黑题(当时很萌,不知道黑题是什么,看到这题感觉很好玩),另 ...
最新文章
- python内置库之学习ctypes库(二)
- python学起来难不难-自学python数据分析之路难不难走?
- 区块链组织-超级账本(Hyperledger)的简介
- 结构化查询语言包含哪些方面?
- java中let_Java Doclet
- java中如何获取当前文件的物理路径?
- 一篇RxJava友好的文章(二)
- 查看oracle磁盘组空间,shell脚本检查oracle中的ASM磁盘组空间并发送邮件
- 怎样设置和检测浏览器语言
- CVE-2015-5254(ActiveMQ 反序列化漏洞)复现
- 小学数学研究性学习设计方案
- sprd 11 隐藏桌面apk图标
- 无法删除文件夹的“只读”属性
- 关于Xshell无法连接VM中的openEuler的解决思路
- hex文件格式剖析,以及hex与bin文件互相转换
- 图像的采样与量化像素的空间关系图像文件的类型—数字图像处理
- 论文分区和影响因子 IF 查询
- 6. 聚类算法之K-Means
- 快看!千亿蓝海一触即发,lazada越南致富风口逢卖必爆
- Echart地图组件的使用
热门文章
- 沙尘暴来袭!看不见的空气污染更致命,快来花园般的家深呼吸~
- 用HTML5构建一个流程图绘制工具
- 获取滚动条宽度(Element-UI之三)
- RuntimeError: reciprocal is not implemented for type torch.cuda.LongTensor
- day02_java基础加强(jdk新特性 javaa设计模式 反射)
- 日志结构化,SQL来查询
- 智慧物流:ZETag云标签如何做到快递包裹防拆防盗?
- HTTP缓存机制--客户端缓存
- Linux 学习之修改文件权限
- SQL远程连接数据库查询数据,远程调用存储过程