bzoj-2286 消耗战【虚树+倍增lca+单调栈】
2286: [Sdoi2011消耗战
Time Limit: 20 Sec Memory Limit: 512 MB
Submit: 1815 Solved: 645
[Submit][Status][Discuss]
Description
Input
第一行一个整数n,代表岛屿数量。
接下来n-1行,每行三个整数u,v,w,代表u号岛屿和v号岛屿由一条代价为c的桥梁直接相连,保证1<=u,v<=n且1<=c<=100000。
第n+1行,一个整数m,代表敌方机器能使用的次数。
接下来m行,每行一个整数ki,代表第i次后,有ki个岛屿资源丰富,接下来k个整数h1,h2,…hk,表示资源丰富岛屿的编号。
Output
输出有m行,分别代表每次任务的最小代价。
Sample Input
Sample Output
HINT
对于100%的数据,2<=n<=250000,m>=1,sigma(ki)<=500000,1<=ki<=n-1
f[father]+=fmin(g[son]?inf:f[son],(min_e(son,father));(g[i]标记是否是关键点)
每次查询,我们只需要遍历关键点与关键点之间的lca,其它点时可忽略的或可跳跃的。
那么就需要用到虚树的技巧了,虚树就是通过维护一个单调栈把树的关键点和它们之间的lca按照dfs序遍历一遍,遍历的过程中通过单调栈的调整来理清树的父亲和儿子之间的关系。
对于栈顶元素 p,栈次顶元素 q, 即将插入节点x 有如下关系:
1.lca是p.此时dfn(x)>dfn(p)=dfn(lca)
2.p和x分立在lca的两棵子树下.此时 dfn(x)>dfn(p)>dfn(ilca)
(1)如果dfn(q)>dfn(lca),可以直接连边q->p,然后退一次栈.
(2)如果dfn(q)=dfn(lca),说明q=lca,直接连边lca->p,把p退栈,此时子树已经构建完毕.
(3)如果dfn(q)<dfn(lca),说明lca被p与q夹在中间,此时连边lca->q,把p退栈,再把lca压入栈.此时子树构建完毕.
这里处理 min_e(p,q) p到q的路径中权值最小的边。需要用倍增lca或者树剖也是可以的。这个参见《挑战程序设计竞赛》吧,改改代码就可以了
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <vector>
#include <algorithm>
#define find_min(a,b) a>b?b:a
#define MAX_V 250008
#define MAX_LOG_V 21
#define INF 0x3f3f3f3f
#define inf (1ll<<40)
using namespace std;
typedef long long int ll;
struct edge{int to,cost;
};
vector<edge> G[MAX_V];
int par[MAX_LOG_V][MAX_V];//v节点向上走2^k步走到的节点
int mng[MAX_LOG_V][MAX_V];//v节点向上走2^k步中路过最小的边
int dfn[MAX_V];//每个点的dfs序标号
int dep[MAX_V];//深度
ll f[MAX_V];//树型dp
struct node{int h,dfn;
}hs[MAX_V];
bool g[MAX_V];//是否是关键点
int sta[MAX_V*2];// 模拟栈
int icnt;//栈顶 、id
int swap(int &x,int &y)
{//交换 x=x^y;y=x^y,x=x^y;
}
//当前点、父亲节点 、深度、连接父亲点的边权
void dfs(int v,int p,int d,int pre_e)
{//lca搜索预处理 par[0][v]=p,dep[v]=d,mng[0][v]=pre_e,dfn[v]=icnt++;for(int i=0;i<G[v].size();++i)if(G[v][i].to!=p) dfs(G[v][i].to,v,d+1,G[v][i].cost);
}
void init_tree()
{//预处理lca查询 icnt=0;//id初始化为0 dfs(1,-1,0,INF); for(int k=0;k+1<MAX_LOG_V;++k)for(int v=1;v<=MAX_V;++v){if(par[k][v]<0)//超过树根 par[k+1][v]=-1,mng[k+1][v]=INF;else{//能前进 2^(k+1)步 int u=par[k][v]; par[k+1][v]=par[k][u]; mng[k+1][v]=find_min(mng[k][v],mng[k][u]);}}
}
int lca(int u,int v)
{if(dep[u]>dep[v]) swap(u,v);//先到同一深度 for(int k=0;k<MAX_LOG_V;++k)if((dep[v]-dep[u])>>k & 1)v=par[k][v];if(u==v) return u;//同时向上 二分查询 for(int k=MAX_LOG_V-1;k>=0;--k)if(par[k][u]!=par[k][v])u=par[k][u],v=par[k][v];return par[0][u];
}
int min_e(int u,int v)
{int ilca=lca(u,v);int res=INF;//u->lca int mov;if(dep[ilca]<dep[u]){mov=dep[u]-dep[ilca];for(int k=0;k<MAX_LOG_V;++k)if(mov>>k &1)res=find_min(res,mng[k][u]),u=par[k][u];}//v->lca if(dep[ilca]<dep[v]){mov=dep[v]-dep[ilca];for(int k=0;k<MAX_LOG_V;++k)if(mov>>k &1)res=find_min(res,mng[k][v]),v=par[k][v];}return res;
}
void add_edge(int u,int v,int c)
{ G[u].push_back((edge){v,c}); G[v].push_back((edge){u,c});
}
void init()
{//初始化边数置0 for(int i=0;i<MAX_V;++i)G[i].clear();
}
int cmp(const void *a,const void *b)
{return ((node *)a)->dfn-((node *)b)->dfn;
}
ll fmin(ll a,ll b)
{return a>b?b:a;
}
void solve(int k)
{for(int i=1;i<=k;++i){int o=hs[i].h;hs[i].dfn=dfn[o];//同步搜索序id}qsort(hs+1,k,sizeof(hs[0]),cmp);int tp=0;sta[tp]=0;sta[++tp]=1;f[1]=0,g[1]=0;for(int i=1;i<=k;++i){int p=sta[tp],q=sta[tp-1],x=hs[i].h;int ilca=lca(p,x);while(dfn[p]>dfn[ilca]){if(dfn[q]<=dfn[ilca]){int tmp=fmin(g[tp]?inf:f[tp],(ll)min_e(p,ilca));sta[tp--]=0;if(ilca!=q)sta[++tp]=ilca,f[tp]=0,g[tp]=0;f[tp]+=tmp;break;}else{f[tp-1]+=fmin(g[tp]?inf:f[tp],(ll)min_e(p,q));sta[tp--]=0;}p=sta[tp],q=sta[tp-1];}if(sta[tp]!=x)sta[++tp]=x,f[tp]=0;g[tp]=1;}while(tp>1){int p=sta[tp],q=sta[tp-1];f[tp-1]+=fmin(g[tp]?inf:f[tp],(ll)min_e(p,q));sta[tp--]=0;}printf("%lld\n",f[tp--]);
}
int main()
{int n;while(~scanf("%d",&n)){init();int u,v,w;for(int i=0;i<n-1;++i){scanf("%d%d%d",&u,&v,&w);add_edge(u,v,w);}init_tree();int m,k,h;scanf("%d",&m);while(m--){scanf("%d",&k);for(int i=1;i<=k;++i)scanf("%d",&hs[i].h);solve(k);}}return 0;
}
bzoj-2286 消耗战【虚树+倍增lca+单调栈】相关推荐
- BZOJ 2286 消耗战 (虚树+树形DP)
给出一个n节点的无向树,每条边都有一个边权,给出m个询问, 每个询问询问ki个点,问切掉一些边后使得这些顶点无法与顶点1连接. 最少的边权和是多少. (n<=250000,sigma(ki)&l ...
- 【bzoj3879】SvT 后缀数组+倍增RMQ+单调栈
题目描述 (我并不想告诉你题目名字是什么鬼) 有一个长度为n的仅包含小写字母的字符串S,下标范围为[1,n]. 现在有若干组询问,对于每一个询问,我们给出若干个后缀(以其在S中出现的起始位置来表示), ...
- 洛谷_2495 [SDOI2011]消耗战(虚树)
消耗战 题目链接:https://www.luogu.com.cn/problem/P2495 题解: 对于单样例,可以考虑树形DP. 但此题是多实例,所以需要对树进行处理,每次询问有k+1(加上一号 ...
- P3233-[HNOI2014]世界树【虚树,倍增】
正题 题目链接:https://www.luogu.com.cn/problem/P3233 题目大意 nnn个点的一棵树,mmm次选出一些点作为关键点.每个树上的点会对最近的关键点做贡献,求每个关键 ...
- UOJ#271. 【清华集训2016】连通子树(虚树+倍增)
传送门 题解: 注意到每种颜色个数比较少,于是建出虚树后暴力背包,用倍增维护一下虚链上的DP值即可. 所以说你只需要5个倍增数组和一些卡常技巧加上无数的小细节就可以通过这道题了. 不说了我去睡觉了. ...
- 「Luogu2495」 [SDOI2011]消耗战 虚树
Luogu P2495 [SDOI2011]消耗战 problem Solution 苦思冥想稍作思考之后可以得到一个树形DP的方法: 令\(w(u,v)\)表示u,v之间的边的权值,\(f[u]\) ...
- 洛谷 P2495 [SDOI2011]消耗战 虚树
题目描述 在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达.现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望.已知 ...
- P2495 [SDOI2011]消耗战 虚树入门
一棵树,n个点m个操作.每条边有权值,每个操作给你k个点,问断开若干条边后使k个点与根不相连的最小边权和是多少. 有sigmaK<500000 易知一个裸的树dp需要复杂度,m次操作后总复杂度为 ...
- P2495 [SDOI2011]消耗战-虚树+树形dp
https://www.luogu.com.cn/problem/P2495 虚树:当我们在解决树形dp的问题的时候,题目中会给出一些询问,询问涉及的关键节点不多,并保证总的点数规模的时候,我们就可以 ...
- 洛谷 P4660 bzoj 1168 [ Baltic OI 2008 ] 手套 —— 分析+单调栈
题目:https://www.luogu.org/record/show?rid=12702916 https://www.lydsy.com/JudgeOnline/problem.php?id=1 ...
最新文章
- linux--memcache的安装和使用(转)
- java托盘图标变白在linux,为什么用java实现windows系统托盘图标总是不显示出来
- 学习笔记Hive(七)—— 自定义函数
- ASP.NET知识集
- 奔跑吧兄弟变成机器人是哪一期_奔跑吧预告,郑恺郭麒麟回归,而我却被女嘉宾的颜值吸引了...
- 七阶拉丁方阵_【C语言】输出N阶拉丁方阵并统计个数
- Java程序员已经饱和了,还有必要培训Java编程嘛
- Missing iOS Distribution signing identity for …
- 力学 —— 物体固有属性之惯性张量矩阵
- USSD设置呼叫转移功能
- Java类加载器深入探索
- 如何制定软件开发计划
- 中国移动发狠,给携转用户巨额优惠,反击中国电信
- 差距不止一星半点,Github星标85K的性能优化法则圣经
- php递归函数return问题
- centos tcpdump
- 【阅读】A Comprehensive Survey on Electronic Design Automation and Graph Neural Networks——EDA+GNN综述翻译
- LeetCode 826 Most Profit Assigning Work
- 互联网大佬们齐聚首,除了演讲还有啥?点击All in 全部热点!
- 学在浙大只有查看权限的课件下载