【动态规划】loj#2485. 「CEOI2017」Chase
有意思的可做dp题;细节有点多,值得多想想
题目描述
在逃亡者的面前有一个迷宫,这个迷宫由 nnn 个房间和 n−1n-1n−1 条双向走廊构成,每条走廊会链接不同的两个房间,所有的房间都可以通过走廊互相到达。换句话说,这是一棵树。
逃亡者会选择一个房间进入迷宫,走过若干条走廊并走出迷宫,但他永远不会走重复的走廊。
在第 iii 个房间里,有 FiF_iFi 个铁球,每当一个人经过这个房间时,他就会受到铁球的阻挡。逃亡者手里有VVV个磁铁,当他到达一个房间时,他可以选择丢下一个磁铁(也可以不丢),将与这个房间相邻的所有房间里的铁球吸引到这个房间。这个过程如下:
- 逃亡者进入房间。
- 逃亡者丢下磁铁。
- 逃亡者走出房间。
- 铁球被吸引到这个房间。
注意逃亡者只会受到这个房间原有的铁球的阻拦,而不会受到被吸引的铁球的阻挡。
在逃亡者走出迷宫后,追逐者将会沿着逃亡者走过的路径穿过迷宫,他会碰到这条路径上所有的铁球。
请帮助逃亡者选择一条路径,使得追逐者遇到的铁球数量减去逃亡者遇到的铁球数量最大化。
输入格式
第一行两个空格隔开的整数整数 nnn 和 VVV。
第二行 nnn 个空格隔开的整数表示 FiF_iFi。
之后的 n−1n-1n−1 行,每行两个空格隔开的整数 xxx 和 yyy,表示有一条走廊连接编号为 xxx 和编号为 yyy 的房间。
输出格式
输出一个整数表示最优情况下追逐者遇到的铁球数量减去逃亡者遇到的铁球数量。
样例
样例输入
12 2
2 3 3 8 1 5 6 7 8 3 5 4
2 1
2 7
3 4
4 7
7 6
5 6
6 8
6 9
7 10
10 11
10 12
样例输出
36
样例解释
有一个最优方案如下:
- 从 6 号房间进入迷宫并丢下第一个磁铁,他遇到了 5 个铁球,这个时候 6 号房间会有 27 个铁球,而 5 号,7 号,8 号,9 号房间都没有铁球。
- 走到 7 号房间丢下第二个磁铁并走出迷宫,他遇到了 0 个铁球,这个时候 7 号房间会有 41 个铁球,而 2 号,4 号,6 号,10 号房间会没有铁球。
在这个过程中,逃亡者会遇到 5 个铁球而追逐者会遇到 41 个铁球。
数据范围与提示
对于 100% 的数据,有 1≤n≤105;0≤V≤100;0≤Fi≤109
- 子任务 1(20%): 有 1≤n≤10
- 子任务 2(20%): 有 1≤n≤1000
- 子任务 3(30%): 保证存在一条从 1 号房间开始的最优路径;
- 子任务 4(30%): 无特殊限制。
题目分析
搜索
做法分析:
时间复杂度$O(n^22^n)$;期望得分20pts。
贪心
考虑若固定一条路径,应该怎样选取路径上的点使得答案最优。
首先追逐者遇到的铁球可以分成两部分:所有原先路径上的铁球;被吸引到路径上的铁球。而逃亡者遇到的铁球则是所有原先路径上的铁球,减去被吸引的铁球。
也就是说,两者遇到的铁球的差可以分成两部分:逃亡者避开的铁球+逃亡者从其他路径吸引来的铁球。
那么问题转化为:对于一颗树,定义一条路径的价值为路径上前$v$大的点权之和,要求一条以根为起点的最大价值路径。
这个东西相当于要求支持操作:查询前k大元素和;加入一个元素;删除任意一个元素。
用平衡树当然也是可以的,不过有一种堆的做法。
(最早做的时候,想到了贪心这步但是卡在查询路径前$k$大元素和这步了……一直在想可持久化堆?之类的奇怪东西)
众所周知删除堆有两种非常普遍的方法:1.强制弹出直到堆顶元素为删除元素,之后再依次弹回;2.在堆外标记元素被删除。第二种方法在权值小的时候很通用,不过权值一大且没法离散化时候就不行了。第三种是设置一个“删除堆”,存要删除的元素,当删除堆的堆顶和现在堆顶相同就舍弃现在堆顶。
于是就可以愉快地贪心了。
#include<bits/stdc++.h>
const int maxn = ; int n,v;
int p[maxn];
long long ans,sv[],sum;
bool vis[maxn];
std::priority_queue<long long> q,del;
int edgeTot,edges[maxn<<],nxt[maxn<<],head[maxn]; int read()
{
char ch = getchar();
int num = ;
bool fl = ;
for (; !isdigit(ch); ch = getchar())
if (ch=='-') fl = ;
for (; isdigit(ch); ch = getchar())
num = (num<<)+(num<<)+ch-;
if (fl) num = -num;
return num;
}
void addedge(int u, int v)
{
edges[++edgeTot] = v, nxt[edgeTot] = head[u], head[u] = edgeTot;
edges[++edgeTot] = u, nxt[edgeTot] = head[v], head[v] = edgeTot;
}
void clears(std::priority_queue<long long> &q)
{
std::priority_queue<long long> emt;
std::swap(q, emt);
}
void dfs(int x, int fa)
{
long long res = , cnt = ;
int tot = ;
for (int i=head[x]; i!=-; i=nxt[i])
if (!vis[edges[i]]) cnt += p[edges[i]];
q.push(cnt), vis[x] = , sum += cnt;
for (int i=head[x]; i!=-; i=nxt[i])
{
int to = edges[i];
if (!vis[to]){
dfs(to, x);
}
}
if (sum > ans){
while (tot<v&&q.size())
{
while (del.size()&&del.top()==q.top())
q.pop(), del.pop();
if (q.empty()) break;
sv[++tot] = q.top(), res += q.top();
q.pop();
}
ans = ans > res?ans:res;
for (int i=; i<=tot; i++) q.push(sv[i]);
}
sum -= cnt, vis[x] = , del.push(cnt);
}
int main()
{
memset(head, -, sizeof head);
n = read(), v = read();
for (int i=; i<=n; i++) p[i] = read();
for (int i=; i<n; i++) addedge(read(), read());
if (n <= )
for (int i=; i<=n; i++)
clears(q), clears(del), dfs(i, i);
else dfs(, );
printf("%lld\n",ans);
return ;
}
做法分析:
$n$次枚举起点,每次枚举起点后$n$次枚举终点。对于每一条路径$vlogv$更新答案。
由于用了优先队列,常数略大,需要卡常或者如上剪枝。
时间复杂度$O(n^2vlogv)$;期望得分70pts。
浅层的动态规划
枚举树根,令$f[i][j]$表示以$i$为根的子树内,以$i$为起点,选取$j$个点的一条链获得的最大价值。
由于枚举树根,因此所有情况都会被包括在内。
做法分析:
$n$次枚举树根,每次dp状态$O(nv)$,转移$O(1)$.
时间复杂度$O(n^2v)$;期望得分70pts。
深入的动态规划
上一个做法的瓶颈在于考虑的是整条路径,因此多次dp中重叠的信息较难合并,只能枚举树根。
事实上可以强制以$1$为根,规定“上”和“下”的顺序。于是就和求树的直径很类似地,合法的路径要么一条上;一条下;两条上下的组合起来。
用$up[x][i]$表示从$x$的子树中某个节点向上走到$x$,总共使用了$i$个节点的最大价值;相同的,$dx[x][i]$表示一条向下路径使用$i$个节点的最大价值。
现在的关键就在于转移时候的去重,不能使两条链有重复部分。
因此update部分是这个样子:
void update(int x, int y, int fa)
{
for (int i=; i<v; i++) getMax(ans, up[x][i]+dw[y][v-i]);
for (int i=; i<=v; i++)
getMax(up[x][i], max(up[y][i], up[y][i-]+sum[x]-p[y])),
getMax(dw[x][i], max(dw[y][i], dw[y][i-]+sum[x]-p[fa]));
}
还有需要注意的一点是,从$s$到$t$的路径价值和从$t$到$s$的价值是不一样的,所以每一次dfs还需要把子节点的顺序反过来再做一遍。
#include<bits/stdc++.h>
typedef long long ll;
const int maxn = ; int n,v;
int p[maxn];
ll ans,up[maxn][],dw[maxn][],sum[maxn];
std::vector<int> g[maxn]; #define BUF_SIZE 100000
#define OUT_SIZE 100000
inline char nc(){
static char buf[BUF_SIZE],*p1=buf+BUF_SIZE,*pend=buf+BUF_SIZE;
if (p1==pend){
p1=buf; pend=buf+fread(buf,,BUF_SIZE,stdin);
if (pend==p1)return -;
}
return *p1++;
}
int read()
{
char ch = nc();
int num = ;
bool fl = ;
for (; !isdigit(ch); ch = nc())
if (ch=='-') fl = ;
for (; isdigit(ch); ch = nc())
num = (num<<)+(num<<)+ch-;
if (fl) num = -num;
return num;
}
void getMax(ll &x, ll y){x=(x>y)?x:y;}
ll max(ll x, ll y){return x>y?x:y;}
void update(int x, int y, int fa)
{
for (int i=; i<v; i++) getMax(ans, up[x][i]+dw[y][v-i]);
for (int i=; i<=v; i++)
getMax(up[x][i], max(up[y][i], up[y][i-]+sum[x]-p[y])),
getMax(dw[x][i], max(dw[y][i], dw[y][i-]+sum[x]-p[fa]));
}
void dfs(int x, int fa)
{
for (int i=; i<=v; i++) up[x][i] = sum[x], dw[x][i] = sum[x]-p[fa];
for (auto &to:g[x])
if (to!=fa)
dfs(to, x), update(x, to, fa);
std::reverse(g[x].begin(), g[x].end());
for (int i=; i<=v; i++) up[x][i] = sum[x], dw[x][i] = sum[x]-p[fa];
for (auto &to:g[x])
if (to!=fa) update(x, to, fa);
getMax(ans, max(up[x][v], dw[x][v]));
}
int main()
{
n = read(), v = read();
for (int i=; i<=n; i++) p[i] = read();
for (int i=; i<n; i++)
{
int x = read(), y = read();
g[x].push_back(y), g[y].push_back(x);
sum[x] += p[y], sum[y] += p[x];
}
dfs(, );
printf("%lld\n",ans);
return ;
}
做法分析:
这里总的状态数是$O(nv)$的。对于$O(n)$个点来说,其每一个子节点的转移是$O(v)$的。每个点只会作为子节点被转移一次,因此复杂度是$O(nv)$的。
时间复杂度$O(nv)$;期望得分100。
END
【动态规划】loj#2485. 「CEOI2017」Chase的更多相关文章
- loj#2483. 「CEOI2017」Building Bridges 斜率优化 cdq分治
loj#2483. 「CEOI2017」Building Bridges 链接 https://loj.ac/problem/2483 思路 \[f[i]=f[j]+(h[i]-h[j])^2+(su ...
- loj#2483. 「CEOI2017」Building Bridges(dp cdq 凸包)
题意 题目链接 Sol \[f[i], f[j] + (h[i] - h[j])^2 + (w[i - 1] - w[j]))\] 然后直接套路斜率优化,发现\(k, x\)都不单调 写个cdq就过了 ...
- 【刷题】LOJ 2480 「CEOI2017」One-Way Streets
题目描述 给定一张 \(n\) 个点 \(m\) 条边的无向图,现在想要把这张图定向. 有 \(p\) 个限制条件,每个条件形如 \((xi,yi)\) ,表示在新的有向图当中,\(x_i\) 要能够 ...
- @loj - 2480@ 「CEOI2017」One-Way Streets
目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定一张 n 个点 m 条边的无向图,现在想要把这张图定向. 有 ...
- @loj - 2483@「CEOI2017」Building Bridges
目录 @desription@ @solution@ @accepted code@ @details@ @another solution@ @another code@ @desription@ ...
- Loj #2192. 「SHOI2014」概率充电器
Loj #2192. 「SHOI2014」概率充电器 题目描述 著名的电子产品品牌 SHOI 刚刚发布了引领世界潮流的下一代电子产品--概率充电器: 「采用全新纳米级加工技术,实现元件与导线能否通电完 ...
- Loj #3096. 「SNOI2019」数论
Loj #3096. 「SNOI2019」数论 题目描述 给出正整数 \(P, Q, T\),大小为 \(n\) 的整数集 \(A\) 和大小为 \(m\) 的整数集 \(B\),请你求出: \[ \ ...
- Loj #3093. 「BJOI2019」光线
Loj #3093. 「BJOI2019」光线 题目描述 当一束光打到一层玻璃上时,有一定比例的光会穿过这层玻璃,一定比例的光会被反射回去,剩下的光被玻璃吸收. 设对于任意 \(x\),有 \(x\t ...
- Loj #3089. 「BJOI2019」奥术神杖
Loj #3089. 「BJOI2019」奥术神杖 题目描述 Bezorath 大陆抵抗地灾军团入侵的战争进入了僵持的阶段,世世代代生活在 Bezorath 这片大陆的精灵们开始寻找远古时代诸神遗留的 ...
随机推荐
- VRTK3.3.0-004传送
直线传送: 一.无高度变换传送(VRTK_BasicTeleport) 1丶继续在VRScripts下创建空物体PlayArea,用来挂在传送相关脚本:创建Plane作为传送地面 2丶在PlayAre ...
- 区间质数查询 luoguP1865
原题 https://www.luogu.org/problemnew/show/P1865 本来get到了一个很好的判断素数的方法 O(玄学常数)https://www.luogu.org/blog ...
- python如何用pip安装模块
pip去python官网下载 我想写的是安装后怎么做,假设我们要安装pymysql模块 在python交互式模式中运行pip install pymysql 会抛出 语法错误,不知为何. 此时应该找到 ...
- 简单总结ConcurrentHashMap
一.HashTable hashTable是一个线程安全的容器,是线程安全版本的HashMap.但它的底层是和HashMap一样的,只是在方法上都加上了synchronized关键字. 这样子有什么后 ...
- git reflog查看所有操作记录
git reflog 可以查看所有分支的所有操作记录(包括(包括commit和reset的操作),包括已经被删除的commit记录,git log则不能察看已经删除了的commit记录 具体一个例子, ...
- 安装wamp时出现httpd.exe无法找到组件MSVCR100.dll的解决办法
很多朋友在安装wamp server时会出现以下错误,安装之后无法启动httpd.exe服务. --------------------------- httpd.exe - 无法找到组件 ----- ...
- Java中IO流文件读取、写入和复制
//构造文件File类 File f=new File(fileName); //判断是否为目录 f.isDirectory(); //获取目录下的文件名 String[] fileName=f.li ...
- P1791 线段覆盖
题目描述 已知数轴上0<N<10000条线段.每条线段按照端点Ai和Bi(Ai<>Bi,i=1..N)定义.端点坐标在(-999,999)内,坐标为整数.有些线段可能相交.编程 ...
- html5.0学习记录(一)——可拖动视频播放器
最近自己在重新学习html5新特性,了解到有视频标签和拖动标签,于是自己用这两个特性写了一个小demo,主要功能就是可以通过拖动视频来直接播放.效果图如下: 页面使用了<video>标签和 ...
- C语言abort函数
C语言编程入门教程,C语言库函数的abort函数的作用是异常终止一个进程,意味着abort后面的代码将不再执行. #include<stdio.h> #include<stdlib. ...