[SDOI2011]消耗战(虚树)
题意:给定一棵树,割断每一条边都有代价,每次询问会给定一些点,求用最少的代价使所有给定点都和1号节点不连通
暴力\(DP\)
我们先考虑暴力怎么做
设\(dp[u]\)为以\(u\)为根的子树中,割掉所有给定点的最小代价
转移的时候要分两种情况:
1.若u不是给定点,则\(dp[u] = min(u\)到根节点的所有边的最小边长,割掉所有含有给定点的子树)
\(ps:\)上述给定子树不一定与u直接相连
2.若u是给定点,显然他必须与1号点分离,所以\(dp[u]=u\)到根节点的所有边的最小边长
复杂度\(O(nm)\),显然对于\(m>=1\)这种数据是过不了的
下面给出暴力DP代码(吸氧之后有50分):
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define re register
#define debug printf("Now is Line : %d\n",__LINE__)
#define file(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
//#define int long long
#define inf 1234567890
#define mod 1000000007
il int read()
{
re int x = 0, f = 1; re char c = getchar();
while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
#define maxn 2333//我也不知道问什么数组开大会从RE 50分->TLE 20分
struct edge
{
int v, w, next;
}e[maxn << 1];
int n, m, head[maxn], cnt, is[maxn], dp[maxn];
il void add(int u, int v, int w)
{
e[++ cnt] = (edge){v, w, head[u]};
head[u] = cnt;
}
il void dfs(int u, int fr)
{
int temp = 0;
for(re int i = head[u]; i; i = e[i].next)
{
int v = e[i].v;
if(v == fr) continue;
if(is[v]) {temp += e[i].w; continue;}
dp[v] = min(dp[u], e[i].w);
dfs(v, u);
temp += dp[v];
}
dp[u] = min(dp[u], temp);
}
int main()
{
//file(a);
n = read();
for(re int i = 1; i < n; ++ i)
{
int u = read(), v = read(), w = read();
add(u, v, w), add(v, u, w);
}
int T = read();
while(T --)
{
memset(is, 0, sizeof(is));
m = read(), dp[1] = inf;
for(re int i = 1; i <= m; ++ i) is[read()] = 1;
dfs(1, 0);
printf("%d\n", dp[1]);
}
return 0;
}
那么剩下五十分我们要怎么得呢?
我们发现\(\sum k[i]\)是非常小的,那我们是不是可以在k上做文(luan)章(gao)呢?
我们发现,有很多点在树上我们是没有用到的,所以我们可以考虑重构一棵新的树,这棵树就叫做————虚树
虚树优化\(DP\)
虚树的思想是只保留有用的点(在这道题目里面显然是标记点和lca),然后重新构建一棵树,从而使节点大大减少,优化复杂度
那么我们要怎么构建虚树呢?
首先对于每一棵树,我们都可以用dfs序表示出来,所以对于每一次询问,我们把所有的标记点按照dfs序排序,然后压进一个栈中,然后连边即可
具体方法:
我们先对所有标记点按照\(dfs\)序排序,并依次入栈
我们考虑入栈操作:
假设我们现在要加入的元素为x,第二个元素为y,栈顶为\(s\),\(l = lca(x, s)\)
因为我们是按\(dfs\)加入,所以\(dfn[s]<dfn[x],dfn[l]<dfn[x]\)
如果l=s,也就是说s是x的祖先,那么s到1号点显然有边需要割掉,所以x对答案并不产生影响,直接忽略
所以x,s肯定在l的两边(\(dfn[l]<dfn[x]\))
然后我们进行分类讨论
\(if(dfn[y] > dfn[l])\) 则说明y在l与x之间,连边\(y -> x\),并将x弹出
\(if(dfn[y] < dfn[l])\) 则说明l在x与y之间,所以连边\(l -> x\),并且令x出栈,l入栈
\(if(dfn[y] = dfn[l])\) 则说明y=l,连边 \(l -> x\)
建树完成以后,每一个节点都是有用点,即满足暴力DP中的含有给定点的子树,所以每一条边都是可以割掉的,直接转移即可
时间复杂度\(O(klogk)\)
代码如下
#include<bits/stdc++.h>
using namespace std;
#define il inline
#define re register
#define debug printf("Now is Line : %d\n",__LINE__)
#define file(a) freopen(#a".in","r",stdin);freopen(#a".out","w",stdout)
#define int long long
#define inf 123456789000000000
#define mod 1000000007
il int read()
{
re int x = 0, f = 1; re char c = getchar();
while(c < '0' || c > '9') { if(c == '-') f = -1; c = getchar();}
while(c >= '0' && c <= '9') x = x * 10 + c - 48, c = getchar();
return x * f;
}
#define maxn 250005
struct edge
{
int v, w, next;
}e[maxn << 1];
int n, m, head[maxn], cnt, is[maxn], mi[maxn], dfn[maxn], col, t;
int size[maxn], fa[maxn], top[maxn], son[maxn], dep[maxn], s[maxn];
vector<int>v[maxn];
il void add(int u, int v, int w)
{
e[++ cnt] = (edge){v, w, head[u]};
head[u] = cnt;
}
il bool cmp(int a, int b){return dfn[a] < dfn[b];}
il void dfs1(int u, int fr)
{
size[u] = 1, fa[u] = fr, dep[u] = dep[fr] + 1;
for(re int i = head[u]; i; i = e[i].next)
{
int v = e[i].v;
if(v == fr) continue;
mi[v] = min(mi[u], e[i].w);
dfs1(v, u);
if(size[son[u]] < size[v]) son[u] = v;
}
}
il void dfs2(int u, int fr)
{
top[u] = fr, dfn[u] = ++ col;
if(!son[u]) return;
dfs2(son[u], fr);
for(re int i = head[u]; i; i = e[i].next)
{
int v = e[i].v;
if(v != fa[u] && v != son[u]) dfs2(v, v);
}
}
il int lca(int a, int b)
{
while(top[a] != top[b]) dep[top[a]] > dep[top[b]] ? a = fa[top[a]] : b = fa[top[b]];
return dep[a] < dep[b] ? a : b;
}
il void push(int x)
{
if(t == 1) {s[++ t] = x;return;}
int l = lca(x, s[t]);
if(l == s[t]) return;
while(t > 1 && dfn[s[t - 1]] >= dfn[l]) v[s[t - 1]].push_back(s[t]), --t;
if(s[t] != l) v[l].push_back(s[t]), s[t] = l;
s[++ t] = x;
}
il int dp(int u)
{
if(v[u].size() == 0) return mi[u];
int temp = 0;
for(re int i = 0; i < v[u].size(); ++ i) temp += dp(v[u][i]);
v[u].clear();
return min(mi[u], temp);
}
signed main()
{
file(a);
n = read();
for(re int i = 1; i < n; ++ i)
{
int u = read(), v = read(), w = read();
add(u, v, w), add(v, u, w);
}
mi[1] = inf, dfs1(1, 0), dfs2(1, 1);
int T = read();
while(T --)
{
m = read();
for(re int i = 1; i <= m; ++ i) is[i] = read();
sort(is + 1, is + m + 1, cmp);
s[t = 1] = 1;
for(re int i = 1; i <= m; ++ i) push(is[i]);
while(t > 0) v[s[t - 1]].push_back(s[t]), --t;
printf("%lld\n", dp(1));
}
return 0;
}
[SDOI2011]消耗战(虚树)的更多相关文章
- bzoj 2286: [Sdoi2011]消耗战 虚树+树dp
2286: [Sdoi2011]消耗战 Time Limit: 20 Sec Memory Limit: 512 MB[Submit][Status][Discuss] Description 在一 ...
- [BZOJ2286][SDOI2011]消耗战(虚树DP)
2286: [Sdoi2011]消耗战 Time Limit: 20 Sec Memory Limit: 512 MBSubmit: 4998 Solved: 1867[Submit][Statu ...
- 【BZOJ2286】[Sdoi2011]消耗战 虚树
[BZOJ2286][Sdoi2011]消耗战 Description 在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达.现在,我军已经侦查到敌军的总部在编号为1的 ...
- bzoj 2286 [Sdoi2011]消耗战 虚树+dp
题目大意:多次给出关键点,求切断边使所有关键点与1断开的最小费用 分析:每次造出虚树,dp[i]表示将i和i子树与父亲断开费用 对于父亲x,儿子y ①y为关键点:\(dp[x]\)+=\(dismn( ...
- 【BZOJ】2286: [Sdoi2011]消耗战 虚树+DP
[题意]给定n个点的带边权树,每次询问给定ki个特殊点,求隔离点1和特殊点的最小代价.n<=250000,Σki<=500000. [算法]虚树+DP [题解]考虑普通树上的dp,设f[x ...
- [SDOI2011]消耗战(虚树+树形动规)
虚树dp 虚树的主要思想: 不遍历没用的的节点以及没用的子树,从而使复杂度降低到\(\sum\limits k\)(k为询问的节点的总数). 所以怎么办: 只把询问节点和其LCA放入询问的数组中. 1 ...
- bzoj2286: [Sdoi2011]消耗战 虚树
在一场战争中,战场由n个岛屿和n-1个桥梁组成,保证每两个岛屿间有且仅有一条路径可达.现在,我军已经侦查到敌军的总部在编号为1的岛屿,而且他们已经没有足够多的能源维系战斗,我军胜利在望.已知在其他k个 ...
- bzoj 2286(洛谷 2495) [Sdoi2011]消耗战——虚树
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=2286 https://www.luogu.org/problemnew/show/P2495 ...
- BZOJ 2286: [Sdoi2011]消耗战 虚树 树形dp 动态规划 dfs序
https://www.lydsy.com/JudgeOnline/problem.php?id=2286 wa了两次因为lca犯了zz错误 这道题如果不多次询问的话就是裸dp. 一棵树上多次询问,且 ...
- BZOJ.2286.[SDOI2011]消耗战(虚树 树形DP)
题目链接 BZOJ 洛谷P2495 树形DP,对于每棵子树要么逐个删除其中要删除的边,要么直接断连向父节点的边. 如果当前点需要删除,那么直接断不需要再管子树. 复杂度O(m*n). 对于两个要删除的 ...
随机推荐
- C++二分查找算法演示源码
如下内容段是关于C++二分查找算法演示的内容. #include <cstdio>{ int l = 0, r = n-1; int mid; while (l <= r){ mid ...
- ASP.NET中弹出消息框的几种常见方法
在ASP.NET网站开发中,经常需要使用到alert消息框,尤其是在提交网页的时候,往往需要在服务器端对数据进行检验,并给出提示或警告. 这里,仅介绍几种不同的实现方法. 1.众所周知的方法是采用如下 ...
- c/c++ linux epoll系列2 利用epoll_wait查看是否可以送信
linux epoll系列2 利用epoll_wait查看是否可以送信 write函数本来是非阻塞函数,但是当缓存区被写满后,再往缓存区里写的时候,就必须等待缓存区再次变成可写,所以这是write就变 ...
- c/c++ linux 进程间通信系列6,使用消息队列(message queue)
linux 进程间通信系列6,使用消息队列(message queue) 概念:消息排队,先进先出(FIFO),消息一旦出队,就从队列里消失了. 1,创建消息队列(message queue) 2,写 ...
- Cs231n-assignment 2作业笔记
assignment 2 assignment2讲解参见: https://blog.csdn.net/BigDataDigest/article/details/79286510 http://ww ...
- CentOS7 Docker私有仓库搭建及删除镜像 【转】
文章来源:centos7 Docker私有仓库搭建及删除镜像 如果不想用私有镜像库,你可以用docker的库 https://hub.docker.com 环境准备 环境:两个装有Docker 17. ...
- WPF中自定义标题栏时窗体最大化处理之WindowChrome
注意: 本文方法基础是WindowChrome,而WindowChrome在.NET Framework 4.5之后才集成发布的.见:WindowChrome Class 在.NET Framewor ...
- VMware虚拟机上网络连接解决方案
VMware虚拟机上网络连接解决方案 作者:凯鲁嘎吉 - 博客园http://www.cnblogs.com/kailugaji/ 从虚拟机上连接外部网络,需要设置以下几个地方. 1.服务 (1)打开 ...
- 转 Angular2优质学习资源收集
文档博客书籍类 官方网站: https://angular.io 中文站点: https://angular.cn Victor的blog(Victor是Angular路由模块的作者): https: ...
- R语言学习——矩阵
> #矩阵是一个二维数组,每个元素都拥有相同的模式(数值型.字符型或者逻辑型).通过matrix()创建,一般使用格式为:mymatrix<-matrix(vector,nrow=numb ...