[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). 对于两个要删除的 ...
随机推荐
- nginx + flask + uwsgi + centos + python3 搭建web项目
1. python3之前已经搭建好,安装flask,使用 pip3 intall flask,这个比较简单,就不过多介绍 2.我在 /usr/local/nginx/html3 (html3是我新建 ...
- 前后端分离djangorestframework——ContentType组件表
ContentType ContentType其实django自带的,但是平时的话很少会用到,所以还是放在Djangorestframework这个部分 作用: 在实际的开发中,由于数据库量级大,所以 ...
- Linux系统下virtuoso数据库安装与使用
最近在调研关联数据的一些东西,需要用到rdf数据库,所以接触了virtuoso数据库.安装的坑其实并不多,之前在windows 10上安过一次.这次在ubuntu 18.04上安装一下,其他的linu ...
- C#字符串转二进制、二进制转字符串
最近公司要做一个操作日志的模块,如果将操作日志以字符串的形式存到后台数据库,非常浪费内存,不可取,特意写了字符串与二进制相互转换的函数. 1.字符串转二进制 private string String ...
- Python字典、集合之高山流水
字典dict字典是由大括号{键:值}组成.字典是无序的.字典的键必须是不可变数据类型.不能使用列表作为键,但可以使用元祖作为字典的键.例如: dict_ = {"test":&qu ...
- 英语背单词app
乐词 √ 真人发音 词根词缀 小组计划及时复习 真人例句 墨墨 单词量测试做的特别好 扇贝 哈哈哈,没用过 百词斩 同样25个单词,我在乐词中背了20分钟,在百词斩中需要60分钟. 原因在于 要记单词 ...
- PHAR系列之导言
由于之前都是在现成的PHP框架下写web项目,一般都只专注于框架内部的使用跟优化.但是对于一些PHP很有特色的功能跟特性很少过多的深入了解.最近做项目调研的时候偶尔注意到一个PHP中一个比较常见的概念 ...
- MySql 学习之路-高级2
目录: 1.约束 2.ALTER TABLE 3.VIEW 1.约束 说明:SQL约束用于规定表中的数据规则,如果存在违反约束的数据行为,行为会被约束终止,约束可以在建表是规定,也可以在建表后规定,通 ...
- [原创]GDB调试指南-断点设置
前言 上篇<GDB调试指南-启动调试>我们讲到了GDB启动调试的多种方式,分别应用于多种场景.今天我们来介绍一下断点设置的多种方式. 为何要设置断点 在介绍之前,我们首先需要了解,为什么需 ...
- easyui中datagrid+layout布局
1.掌握layout布局 首先,layout布局的具体使用可参考官网http://www.jeasyui.net/plugins/162.html layout布局分为东南西北中五个区域,如图我们将其 ...