Problem

uoj

题意大意:

一棵树,点权\(w_i\),每次玩家可以在树上行走,一条边需要\(1\)的时间,只能往儿子走。每次游戏需要从\(s\)到\(t\)。

玩家有一个总死亡次数,初始为\(0\)。如果走到\(i\)的时候,当前总的死亡次数小于\(w_i\),那么玩家就会立刻死亡并回到起点 \(s\),且该死亡过程不需要时间,多组询问从\(s\)到\(t\)的最短时间

\(n,m\leq 3\times 10^5,w_i\leq 10^9\)

Solution

不难发现每次死亡都互相独立,所以对于每次死亡都单独计算

我们设\(f[x][i]\)表示节点\(x\)往下走,已经死亡\(i-1\)次,死亡第\(i\)次所花费的最短时间,假如从\(s\)到\(t\)之间的最大点权为\(W\),则答案为\(\sum_{i=1}^Wf[s][i]+dis(s,t)\)

解释一下,如果路径上最大点权为\(W\),则从\(s\)到\(t\)的过程中一定恰好死\(W\)次(因为如果没死\(W\)次,则无法通过,一旦死亡\(W\)次,则接下来的点都可以通过且不会死亡),则答案记为\(\sum_{i=1}^Wf[s][i]\),最后还要来一次从\(s\)到\(t\)的畅通无阻的旅行,答案加上\(dis(s,t)\)

如何得到\(f[x][i]\)?可以简单\(Dp\),时间空间复杂度都为\(O(nW)\),我们可以拿到\(10\)分的好成绩,离散化一下可以多拿\(10\)分 (。・∀・)ノ゙

优化转移,发现\(f[x][i]\)可能有一大段是相同的,而且\(f[x][i]\)一定依照\(i\)严格递增,我们只需要记录转折点了

比如说对于\(f[x][i]\)我们记录了\(<f_1,f_4,f_{12},f_{40}>\),则对于\(i\in [5,12]\),\(f[x][i]=f_{12}\)

这样的话我们拿平衡树维护这个\(<f_i>\),答案可以维护

答案转移可以平衡树启发式合并,具体就是所有子树相应位置取\(\min\),然后整体加\(1\),询问完答案后,再将位置\([1,w_i]\)之间的值赋为\(0\)

我:“好了,我们开始打吧”

(一个小时后)

我:“这太恶心了,浑身难受,不想继续,我们还是用正解的方法吧”

同桌:“这怎么行呢,代码再长,忍忍就过去了,而且你就是要培养这种调试的能力”

我:“好吧”(开始硬着头皮开始打)

(20分钟后)

同桌:“啊,我不打了,这太恶心了,我还没打完主程序就有300行了,我们还是看正解吧”

我:“不行不行,你要坚持,怎么能放弃呢,代码再长,忍忍就过去了,而且就是要培养这种调试的能力嘛”

同桌:“算了算了,我放弃了,我们还是用正解做法吧”


如上,我们发现用上述方法会导致浑身蓝瘦

所以我们有种奇妙的代码优化,就是

队列启发式合并

具体怎么做呢,就是整体开一个长队列,按照\(dfs\)序分配队列空间,这样合并一棵子树的时候由于\(dfs\)序是连续的,所以在队列中的空间也是连续的,这样合并起来的话,继续用合并起来的空间,这就显得很巧妙了

合并两个队列的时候将短的队列加入长的队列,而队列的长度上限取决于这个子树内的最长链,如果我们采用长链剖分,则每条长链只会被遍历一次,时间复杂度是\(O(n)\)的,询问的时候二分查找第一个大于等于\(W\)的值,将前面的整体部分记上,再加上后面多出来的

如图,横坐标为\(depth\),纵坐标为\(w\),我们求的值就是下面的面积(其中队列里存的节点为染成蓝绿色的\(A,C,E,G\))(感谢@zjp_shadow提醒弱智博主修复)

整体复杂度\(O((n+m)\log n)\),具体做法详见代码

Code

#include <bits/stdc++.h>
typedef long long ll; template <typename _tp> inline void read(_tp&x){
char ch=getchar(),ob=0;x=0;
while(ch!='-'&&!isdigit(ch))ch=getchar();if(ch=='-')ob=1,ch=getchar();
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();if(ob)x=-x;
} template <typename _tp> inline void cmax(_tp&A,_tp B) {A = A > B ? A : B;}
template <typename _tp> inline _tp max(_tp A,_tp B) {return A > B ? A : B;} const int N=301000;
struct Edge{int v,nxt;}a[N+N];
int head[N],Head[N],dep[N];
int he[N],ta[N],dfn[N],son[N];
int Q_t[N],w[N],n,m,dfc=1,_; ll Ans[N],sm[N]; inline void add(int*arr,int u,int v){a[++_].v = v, a[_].nxt = arr[u], arr[u] = _;} struct vLCA{
int anc[N][20],ans_F[N][20],len[N];
inline vLCA(){memset(len,0,sizeof len); len[0] = -1;}
int query(int x,int y){
int res(0);
for(int i=19;~i;--i)
if(dep[anc[x][i]] > dep[y])
cmax(res,ans_F[x][i]), x = anc[x][i];
return res;
}
void dfs(int x){
for(int i=1;i<20;++i){
anc[x][i] = anc[anc[x][i-1]][i-1];
ans_F[x][i] = max(ans_F[x][i-1], ans_F[anc[x][i-1]][i-1]);
}
for(int i=head[x];i;i=a[i].nxt){
anc[a[i].v][0] = x;
ans_F[a[i].v][0] = w[x];
dep[a[i].v] = dep[x] + 1;
dfs(a[i].v);
if(len[a[i].v] > len[son[x]])
son[x] = a[i].v;
}
len[x] = len[son[x]] + 1;
}
}lca; struct node{
int dep,w;
inline node(){}
inline node(const int&Dep,const int&W):dep(Dep),w(W){}
}p[N],tmp[N]; void ins(int x, node e) {
while(he[x] <= ta[x] and p[he[x]].w <= e.w)++he[x];
if(he[x] > ta[x] or p[he[x]].dep > e.dep){
sm[he[x]-1] = 0;
if(he[x] <= ta[x]) sm[he[x]-1] = (ll)(p[he[x]].w - e.w) * p[he[x]].dep + sm[he[x]];
p[--he[x]] = e;
}
} void merge(int x,int y){
int tp=0;
while(he[x] <= ta[x] and p[he[x]].dep < p[ta[y]].dep)
tmp[++tp] = p[he[x]++];
while(tp and he[y] <= ta[y])
if(p[ta[y]].dep > tmp[tp].dep) ins(x,p[ta[y]--]);
else ins(x, tmp[tp--]);
while(tp) ins(x, tmp[tp--]);
while(he[y] <= ta[y]) ins(x,p[ta[y]--]);
} void solve(int x,int id){
int y = Q_t[id], op; ll res = 0ll;
int l = he[x], r = ta[x], mid, w = lca.query(y,x);
while(l<r){
mid = l + r>> 1;
if(p[mid].w < w) l = mid + 1;
else r = mid;
}
op = bool(p[he[x]].w <= w);
if(op) res = sm[he[x]] - sm[l] + (ll)p[he[x]].w * p[he[x]].dep;
res += (ll)p[l].dep * (w-(op?p[l].w:0)) - (ll)dep[x]*w;
Ans[id] = res + dep[y] - dep[x];
} void dfs(int x){
dfn[x] = ++dfc;
if(son[x]) dfs(son[x]), he[x] = he[son[x]], ta[x] = ta[son[x]];
else he[x] = dfc, ta[x] = dfc - 1;
for(int i=head[x];i;i=a[i].nxt)
if(a[i].v != son[x])
dfs(a[i].v), merge(x,a[i].v);
for(int i=Head[x];i;i=a[i].nxt)
solve(x,a[i].v);
ins(x,node(dep[x],w[x]));
} void input();
void print();
int main(){
input(); lca.dfs(1);
dfs(1); print();
return 0;
} void print(){
for(int i=1;i<=m;++i)
printf("%lld\n",Ans[i]);
} void input(){
read(n), dep[1] = 1; int x;
for(int i=1;i<=n;++i) read(w[i]);
for(int i=2;i<=n;++i) read(x), add(head,x,i);
read(m);
for(int i=1;i<=m;++i){
read(x), read(Q_t[i]);
add(Head,x,i);
}
}

题解-UOJ284 快乐游戏鸡的更多相关文章

  1. Python菜鸟快乐游戏编程_pygame(6)

    Python菜鸟快乐游戏编程_pygame(博主录制,2K分辨率,超高清) https://study.163.com/course/courseMain.htm?courseId=100618802 ...

  2. Python菜鸟快乐游戏编程_pygame(5)

    Python菜鸟快乐游戏编程_pygame(博主录制,2K分辨率,超高清) https://study.163.com/course/courseMain.htm?courseId=100618802 ...

  3. Python菜鸟快乐游戏编程_pygame(4)

    Python菜鸟快乐游戏编程_pygame(博主录制,2K分辨率,超高清) https://study.163.com/course/courseMain.htm?courseId=100618802 ...

  4. Python菜鸟快乐游戏编程_pygame(3)

    Python菜鸟快乐游戏编程_pygame(博主录制,2K分辨率,超高清) https://study.163.com/course/courseMain.htm?courseId=100618802 ...

  5. Python菜鸟快乐游戏编程_pygame(2)

    Python菜鸟快乐游戏编程_pygame(博主录制,2K分辨率,超高清) https://study.163.com/course/courseMain.htm?courseId=100618802 ...

  6. Python菜鸟快乐游戏编程_pygame(1)

    Python菜鸟快乐游戏编程_pygame(博主录制,2K分辨率,超高清) https://study.163.com/course/courseMain.htm?courseId=100618802 ...

  7. 【题解】JXOI2018游戏(组合数)

    [题解]JXOI2018游戏(组合数) 题目大意 对于\([l,r]\)中的数,你有一种操作,就是删除一个数及其所有倍数.问你删除所有数的所有方案的步数之和. 由于这里是简化题意,有一个东西没有提到: ...

  8. JS制作蔡徐坤打篮球小游戏(鸡你太美?)

    一.前提: 和我之前写的 QT小球游戏 差不多(指的是实现方法). 感谢大佬的 Github:https://github.com/kasuganosoras/cxk-ball 外加游戏网页:http ...

  9. 题解 P2089 【烤鸡】

    看到这个题一共也就pow(3,10)=59049次循环,那不就暴力了嘛! 虽然说正解是动归和搜索, 但是搜索和暴力枚举的差距真心不大(不好好学习qwq). 看到楼上又说到 答案需要数据存储的问题, 这 ...

随机推荐

  1. Mybatis笔记二:接口式编程

    目录 旧方法的弊端 接口式编程 接口式编程的好处 接口式编程的增删改查 旧方法的弊端 在Mybatis笔记一中,我们使用命名空间+id的方式实现了Mybatis的执行,不过这里的命名空间是我们随便写的 ...

  2. 3.建造者模式(Builder)

    Builder模式的缘起:    假设创建游戏中的一个房屋House设施,该房屋的构建由几部分组成,且各个部分富于变化.如果使用最直观的设计方法,每一个房屋部分的变化,都将导致房屋构建的重新修正... ...

  3. Linux记录-配置无密码登录

    1.互信的机器都执行 ssh-keygen -t rsa cat ~/.ssh/id_rsa.pub >> /home/hdfs/.ssh/authorized_keys chmod 60 ...

  4. jQuery使用(六):DOM操作之元素包裹、克隆DOM与data的综合应用

    包裹 wrap() wrapInner() wrapAll() unwrap() clone() 数据缓存机制 data 文档处理(包裹) 1.1.wrap()--将所匹配的元素用其他元素结构化标签包 ...

  5. [leetcode-108,109] 将有序数组转换为二叉搜索树

    109. 有序链表转换二叉搜索树 Given a singly linked list where elements are sorted in ascending order, convert it ...

  6. Git与GitHub学习笔记(一)如何删除github里面的文件夹?

    按照以下步骤即可(本地删除) 1. git pull you git url2. git checkout 3. rm -r dirName4. git add --all5. git commit  ...

  7. zookeeper的安装与使用

    zookeeper的安装与使用: Zookeeper简介 1.可以作为集群的管理工具使用. 2.可以集中管理配置文件. Zookeeper是一个高效的分布式协调服务,可以提供配置信息管理.命名.分布式 ...

  8. idea 创建运行web项目时,报错: Can not issue executeUpdate() for SELECTs解决方案

    最近在做一个Web课程设计的时候遇到了如下的问题. java.sql.SQLException: java.lang.RuntimeException: java.sql.SQLException: ...

  9. 四十一、Linux 线程——线程同步之条件变量

    41.1 概念 41.1.1 条件变量的介绍 互斥锁的缺点是它只有两种状态:锁定和非锁定 条件变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足 条件变量内部是一个等待队列,放置等待 ...

  10. php时间转换

    UNIX时间戳和格式化日期是我们常打交道的两个时间表示形式,Unix时间戳存储.处理方便,但是不直观,格式化日期直观,但是处理起来不如Unix时间戳那么自如,所以有的时候需要互相转换,下面给出互相转换 ...