有意思的可做dp题;细节有点多,值得多想想

题目描述

在逃亡者的面前有一个迷宫,这个迷宫由 nnn 个房间和 n−1n-1n−1 条双向走廊构成,每条走廊会链接不同的两个房间,所有的房间都可以通过走廊互相到达。换句话说,这是一棵树。
逃亡者会选择一个房间进入迷宫,走过若干条走廊并走出迷宫,但他永远不会走重复的走廊。
在第 iii 个房间里,有 FiF_iF​i​​ 个铁球,每当一个人经过这个房间时,他就会受到铁球的阻挡。逃亡者手里有VVV个磁铁,当他到达一个房间时,他可以选择丢下一个磁铁(也可以不丢),将与这个房间相邻的所有房间里的铁球吸引到这个房间。这个过程如下:

  1. 逃亡者进入房间。
  2. 逃亡者丢下磁铁。
  3. 逃亡者走出房间。
  4. 铁球被吸引到这个房间。

注意逃亡者只会受到这个房间原有的铁球的阻拦,而不会受到被吸引的铁球的阻挡。
在逃亡者走出迷宫后,追逐者将会沿着逃亡者走过的路径穿过迷宫,他会碰到这条路径上所有的铁球。
请帮助逃亡者选择一条路径,使得追逐者遇到的铁球数量减去逃亡者遇到的铁球数量最大化。

输入格式

第一行两个空格隔开的整数整数 nnn 和 VVV。
第二行 nnn 个空格隔开的整数表示 FiF_iF​i​​。
之后的 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的更多相关文章

  1. 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 ...

  2. 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就过了 ...

  3. 【刷题】LOJ 2480 「CEOI2017」One-Way Streets

    题目描述 给定一张 \(n\) 个点 \(m\) 条边的无向图,现在想要把这张图定向. 有 \(p\) 个限制条件,每个条件形如 \((xi,yi)\) ,表示在新的有向图当中,\(x_i\) 要能够 ...

  4. @loj - 2480@ 「CEOI2017」One-Way Streets

    目录 @description@ @solution@ @accepted code@ @details@ @description@ 给定一张 n 个点 m 条边的无向图,现在想要把这张图定向. 有 ...

  5. @loj - 2483@「CEOI2017」Building Bridges

    目录 @desription@ @solution@ @accepted code@ @details@ @another solution@ @another code@ @desription@ ...

  6. Loj #2192. 「SHOI2014」概率充电器

    Loj #2192. 「SHOI2014」概率充电器 题目描述 著名的电子产品品牌 SHOI 刚刚发布了引领世界潮流的下一代电子产品--概率充电器: 「采用全新纳米级加工技术,实现元件与导线能否通电完 ...

  7. Loj #3096. 「SNOI2019」数论

    Loj #3096. 「SNOI2019」数论 题目描述 给出正整数 \(P, Q, T\),大小为 \(n\) 的整数集 \(A\) 和大小为 \(m\) 的整数集 \(B\),请你求出: \[ \ ...

  8. Loj #3093. 「BJOI2019」光线

    Loj #3093. 「BJOI2019」光线 题目描述 当一束光打到一层玻璃上时,有一定比例的光会穿过这层玻璃,一定比例的光会被反射回去,剩下的光被玻璃吸收. 设对于任意 \(x\),有 \(x\t ...

  9. Loj #3089. 「BJOI2019」奥术神杖

    Loj #3089. 「BJOI2019」奥术神杖 题目描述 Bezorath 大陆抵抗地灾军团入侵的战争进入了僵持的阶段,世世代代生活在 Bezorath 这片大陆的精灵们开始寻找远古时代诸神遗留的 ...

随机推荐

  1. SpringBoot2.0 整合 RocketMQ ,实现请求异步处理

    一.RocketMQ 1.架构图片 2.角色分类 (1).Broker RocketMQ 的核心,接收 Producer 发过来的消息.处理 Consumer 的消费消息请求.消息的持 久化存储.服务 ...

  2. JSP技术概念

  3. android BottomNavigationView 底部显示3个以上的item

    你现在可以用app:labelVisibilityMode="[labeled, unlabeled, selected, auto] labeled 所有的标签都是可见的. unlabel ...

  4. [模板](luogu P3387)縮點

    前言:對於這週的咕咕咕表示好像沒什麼好表示的,完全沒有靈感a......寫東西真的好難啊......於是又玩了半天鬼泣4???還挺好玩的 來源:題解 题目背景 缩点+DP 题目描述 给定一个n个点m条 ...

  5. 2017 Multi-University Training Contest - Team 1 KazaQ's Socks

    Problem Description KazaQ wears socks everyday. At the beginning, he has n pairs of socks numbered f ...

  6. 转 PHP 正则表达式 以及案例

    2.Perl兼容的语法扩充 Perl兼容的正则表达式的模式类似于Perl中的语法,表达式必须包含在定界符中,除数字.字母.反斜线外的任何字符都可以作为定界符.例如,表达式’/^(?i)php[34]/ ...

  7. “玲珑杯”ACM比赛 Round #4 E -- array DP

    http://www.ifrog.cc/acm/problem/1050?contest=1006&no=4 DP[val]表示以val这个值结尾的等差数列有多少个 DP[val] += DP ...

  8. Unity Shader入门精要学习笔记 - 第8章 透明效果

    转载自 冯乐乐的 <Unity Shader入门精要> 透明是游戏中经常要使用的一种效果.在实时渲染中要实现透明效果,通常会在渲染模型时控制它的透明通道.当开启透明混合后,当一个物体被渲染 ...

  9. elastcisearch中文分词器各个版本

    地址 https://github.com/medcl/elasticsearch-analysis-ik/releases?after=v6.0.1

  10. debian使用apt安装时出现“更换介质,插入驱动器"/media/chrom/"再按回车键”的提示,无法从网络安装,解决?

    原文链接:https://www.zhihu.com/question/22132663 nano /etc/apt/sources.list把那出现的那行注释掉:含CD盘的一行:然后apt-get ...