@uoj - 435@ 【集训队作业2018】Simple Tree
@description@
有一棵有根树,根为 1,点有点权。
现在有 m 次操作,操作有 3 种:
1 x y w,将 x 到 y 的路径上的点点权加上 w (其中 w=±1);
2 x y,询问在 x 到 y 的路径上有多少个点点权 >0;
3 x,询问在 x 的子树里的点有多少个点点权 >0。
输入格式
第一行三个数 n,m,T,表示树的结点个数,操作个数,和是否加密。
接下来 n−1 行,每行 2 个数 x y,表示结点 x 和 y 之间有一条边。
接下来一行 n 个数,第 i 个数表示结点 i 的初始点权。
接下来 m 行,每行格式见题目描述。
如果 T=1,则这 m 行读入的每个 x,y 都需要异或 last_ans 才能得到真实的输入,其中 last_ans 表示上一次询问操作的答案,如果不存在上一次询问操作则为 0。
输出格式
对于每个询问操作,输出一行表示答案。
样例一
input
5 5 0
1 2
1 3
3 4
3 5
1 0 0 0 0
2 2 5
3 3
1 2 5 1
2 2 5
3 3
output
1
0
4
2
限制与约定
对于所有数据,1≤n≤105,1≤m≤105,−10^9≤ 点权 ≤10^9。
@solution@
不妨先考虑链的情况。
对区间的值域进行修改与查询,除了分块以外,起码我是没有想到其他方法。
修改时整块 tag,散块暴力重构排序;查询时整块二分,散块暴力。
得到了一个 \(O(n\sqrt{n}\log n)\) 的算法。
至于上树。树分块?没听说过,啥东西呀。
我们可以通过树链剖分转成 log 个不相交的区间修改查询。
得到了一个连暴力都不如的 \(O(n\sqrt{n}\log^2 n)\) 的算法。
别慌,我们来尝试优化一下,能否去掉分块本身的那个 log。
一种空间消耗较大的方法使用桶。注意到当值域的绝对值 |ai| > m 时,多的部分是没有用的。于是我们可以把值域缩到 -m ~ m 之间。
然后就可以对于每个块开一个大小为 2*m 的桶。每次整块 ±1 时,整个块的答案的变化量可以 O(1) 算。
这道题可能要开 short int 才不会被卡空间。
另一种方法就是直接优化原先的做法。
排序可以将被修改的区间与没有被修改的区间先裂开,更改,再进行归并。就没有 log 的存在了。
整块修改时维护一个指针方便计算答案的变化量。为了做到 O(1) 移动指针,需要一次跳过值相同的所有数,再维护一下值相同的最左与最右。
这样就优化成了 \(O(n\sqrt{n}\log n)\)。不过还是有点玄,我们再优化一下。
注意到树链剖分将一次操作转成了 log 个不相交的区间操作,因此一次最多涉及 \(O(\sqrt{n})\) 个整块,而散块可能有 \(O(\log n)\) 个。
因此散块的复杂度比整块大。我们把块调小一点,变成 \(O(\sqrt{n\log n})\) 个块,就可以将复杂度有效均衡在 \(O(\sqrt{n\log n})\)。
@accepted code@
//用桶的方法 AC 的
#include<cstdio>
#include<algorithm>
using namespace std;
const int MAXN = 100002;
const int BLOCK = 105;
struct edge{
int to; edge *nxt;
}edges[2*MAXN + 5], *adj[MAXN + 5], *ecnt=&edges[0];
void addedge(int u, int v) {
edge *p = (++ecnt);
p->to = v, p->nxt = adj[u], adj[u] = p;
p = (++ecnt);
p->to = u, p->nxt = adj[v], adj[v] = p;
}
int siz[MAXN + 5], dep[MAXN + 5], hvy[MAXN + 5], fa[MAXN + 5];
void dfs1(int x, int f) {
fa[x] = f, siz[x] = 1, dep[x] = dep[f] + 1, hvy[x] = 0;
for(edge *p=adj[x];p;p=p->nxt) {
if( p->to == f ) continue;
dfs1(p->to, x);
siz[x] += siz[p->to];
if( siz[p->to] > siz[hvy[x]] )
hvy[x] = p->to;
}
}
int top[MAXN + 5], tid[MAXN + 5], dfn[MAXN + 5], dcnt;
void dfs2(int x, int tp) {
top[x] = tp, tid[x] = (++dcnt), dfn[dcnt] = x;
if( !hvy[x] ) return ;
dfs2(hvy[x], tp);
for(edge *p=adj[x];p;p=p->nxt)
if( p->to != fa[x] && p->to != hvy[x] )
dfs2(p->to, p->to);
}
int n, m, T;
int a[MAXN + 5], id[MAXN + 5];
int l[MAXN + 5], r[MAXN + 5], tg[MAXN + 5], res[MAXN + 5];
short int cnt[1000][2*MAXN + 5];
void insert(int k, int x) {cnt[x][k]++; if( k > tg[x] ) res[x]++;}
void erase(int k, int x) {cnt[x][k]--; if( k > tg[x]) res[x]--;}
void add(int x) {res[x] += cnt[x][tg[x]], tg[x]--;}
void remove(int x) {tg[x]++, res[x] -= cnt[x][tg[x]];}
void build() {
int bcnt = 0;
for(int i=1;i<=n;i++) {
if( (i - 1) % BLOCK == 0 )
bcnt++, l[bcnt] = i, tg[bcnt] = MAXN, res[bcnt] = 0;
r[bcnt] = i, insert(a[dfn[i]], id[i] = bcnt);
}
}
void update(int &x) {
if( x < 0 ) x = 0;
if( x > 2*MAXN ) x = 2*MAXN;
}
void Amodify(int le, int ri, int w) {
if( id[le] == id[ri] ) {
int p = id[le];
for(int i=le;i<=ri;i++) {
erase(a[dfn[i]], p);
a[dfn[i]] += w, update(a[dfn[i]]);
insert(a[dfn[i]], p);
}
}
else {
int p = id[le], q = id[ri];
for(int i=le;i<=r[p];i++) {
erase(a[dfn[i]], p);
a[dfn[i]] += w, update(a[dfn[i]]);
insert(a[dfn[i]], p);
}
for(int i=p+1;i<=q-1;i++)
( w == 1 ) ? add(i) : remove(i);
for(int i=l[q];i<=ri;i++) {
erase(a[dfn[i]], q);
a[dfn[i]] += w, update(a[dfn[i]]);
insert(a[dfn[i]], q);
}
}
}
int Aquery(int le, int ri) {
int ret = 0;
if( id[le] == id[ri] ) {
int p = id[le];
for(int i=le;i<=ri;i++)
ret += (a[dfn[i]] > tg[p]);
}
else {
int p = id[le], q = id[ri];
for(int i=le;i<=r[p];i++)
ret += (a[dfn[i]] > tg[p]);
for(int i=p+1;i<=q-1;i++)
ret += res[i];
for(int i=l[q];i<=ri;i++)
ret += (a[dfn[i]] > tg[q]);
}
return ret;
}
void modify(int x, int y, int w) {
while( top[x] != top[y] ) {
if( dep[top[x]] < dep[top[y]] ) swap(x, y);
Amodify(tid[top[x]], tid[x], w);
x = fa[top[x]];
}
if( dep[x] < dep[y] ) swap(x, y);
Amodify(tid[y], tid[x], w);
}
int query(int x, int y) {
int ret = 0;
while( top[x] != top[y] ) {
if( dep[top[x]] < dep[top[y]] ) swap(x, y);
ret += Aquery(tid[top[x]], tid[x]);
x = fa[top[x]];
}
if( dep[x] < dep[y] ) swap(x, y);
ret += Aquery(tid[y], tid[x]);
return ret;
}
int query(int x) {
return Aquery(tid[x], tid[x] + siz[x] - 1);
}
int main() {
scanf("%d%d%d", &n, &m, &T);
for(int i=1;i<n;i++) {
int x, y; scanf("%d%d", &x, &y);
addedge(x, y);
}
for(int i=1;i<=n;i++)
scanf("%d", &a[i]), a[i] += MAXN, update(a[i]);
dfs1(1, 0), dfs2(1, 1), build();
int last_ans = 0;
for(int i=1;i<=m;i++) {
int op; scanf("%d", &op);
if( op == 1 ) {
int x, y, w; scanf("%d%d%d", &x, &y, &w);
modify(x ^ last_ans, y ^ last_ans, w);
}
else if( op == 2 ) {
int x, y; scanf("%d%d", &x, &y);
last_ans = query(x ^ last_ans, y ^ last_ans);
printf("%d\n", last_ans), last_ans *= T;
}
else {
int x; scanf("%d", &x);
last_ans = query(x ^ last_ans);
printf("%d\n", last_ans), last_ans *= T;
}
}
}
@details@
写完桶的方法发现被 hack 数据卡内存。
然后一怒之下换成重构的方法,调了一下午终于过了(我树链剖分要是再搞混点编号与 dfs 序中的编号我就***)。
然后有人告诉我 short int 能过。
。。。
@uoj - 435@ 【集训队作业2018】Simple Tree的更多相关文章
- uoj #450[集训队作业2018]复读机
传送门 \(d=1\),那么任何时刻都可以\(k\)个复读机的一种,答案为\(k^n\) \(d>1\),可以枚举某个复读机的复读次数(必须是\(d\)的倍数),然后第\(i\)个复读时间为\( ...
- UOJ 422 [集训队作业2018] 小Z的礼物 min-max容斥 期望 轮廓线dp
LINK:小Z的礼物 太精髓了 我重学了一遍min-max容斥 重写了一遍按位或才写这道题的. 还是期望多少时间可以全部集齐. 相当于求出 \(E(max(S))\)表示最后一个出现的期望时间. 根据 ...
- UOJ #449. 【集训队作业2018】喂鸽子
UOJ #449. [集训队作业2018]喂鸽子 小Z是养鸽子的人.一天,小Z给鸽子们喂玉米吃.一共有n只鸽子,小Z每秒会等概率选择一只鸽子并给他一粒玉米.一只鸽子饱了当且仅当它吃了的玉米粒数量\(≥ ...
- 【UOJ#450】【集训队作业2018】复读机(生成函数,单位根反演)
[UOJ#450][集训队作业2018]复读机(生成函数,单位根反演) 题面 UOJ 题解 似乎是\(\mbox{Anson}\)爷的题. \(d=1\)的时候,随便怎么都行,答案就是\(k^n\). ...
- 【UOJ#422】【集训队作业2018】小Z的礼物(min-max容斥,轮廓线dp)
[UOJ#422][集训队作业2018]小Z的礼物(min-max容斥,轮廓线dp) 题面 UOJ 题解 毒瘤xzy,怎么能搬这种题当做WC模拟题QwQ 一开始开错题了,根本就不会做. 后来发现是每次 ...
- UOJ#418. 【集训队作业2018】三角形
#418. [集训队作业2018]三角形 和三角形没有关系 只要知道儿子放置的顺序,就可以直接模拟了 记录历史最大值 用一个pair(a,b):之后加上a个,期间最大值为增加b个 合并? A1+A2= ...
- UOJ#422. 【集训队作业2018】小Z的礼物
#422. [集训队作业2018]小Z的礼物 min-max容斥 转化为每个集合最早被染色的期望时间 如果有x个选择可以染色,那么期望时间就是((n-1)*m+(m-1)*n))/x 但是x会变,中途 ...
- UOJ#428. 【集训队作业2018】普通的计数题
#428. [集训队作业2018]普通的计数题 模型转化好题 所以变成统计有标号合法的树的个数. 合法限制: 1.根标号比子树都大 2.如果儿子全是叶子,数量B中有 3.如果存在一个儿子不是叶子,数量 ...
- uoj450 【集训队作业2018】复读机(生成函数,单位根反演)
uoj450 [集训队作业2018]复读机(生成函数,单位根反演) uoj 题解时间 首先直接搞出单个复读机的生成函数 $ \sum\limits_{ i = 0 }^{ k } [ d | i ] ...
- [UOJ422][集训队作业2018]小Z的礼物——轮廓线DP+min-max容斥
题目链接: [集训队作业2018]小Z的礼物 题目要求的就是最后一个喜欢的物品的期望得到时间. 根据$min-max$容斥可以知道$E(max(S))=\sum\limits_{T\subseteq ...
随机推荐
- 实现一个函数clone,使JavaScript中的5种主要的数据类型(包括Number、String、Object、Array、Boolean)进行值复制
实现一个函数clone,可以对JavaScript中的5种主要的数据类型(包括Number.String.Object.Array.Boolean)进行值复制. 1 /** 对象克隆 2 * 支持基本 ...
- Linux 下用Win共享
让win能访问到 linux 的smb 共享: linux 安装 samba: apt-get install samba #然后 vim /etc/samba/smb.conf #找到securit ...
- C++/CLI 创建WinForm程序
本文演示下用CLR创建一个简单的winform程序,IDE:VS2015 可以参考另一篇文章:http://blog.csdn.net/wcc27857285/article/details/7813 ...
- canvas旋转图片
canvas旋转图片 <!DOCTYPE html> <html lang="en"> <head> <meta charset=&quo ...
- Leetcode48. Rotate Image旋转图像
给定一个 n × n 的二维矩阵表示一个图像. 将图像顺时针旋转 90 度. 说明: 你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵.请不要使用另一个矩阵来旋转图像. 示例 1: 给定 m ...
- 阿里云DMS发布数据库网关服务: 打通网络限制 开启数据库统一管理的万能钥匙
概述 阿里云数据管理DMS在云端可提供专业的数据库服务,除对标本地数据库软件的基础功能外,还包含性能诊断.数据追踪.跨实例SQL查询(含异构数据库类型之间)等专业性功能,同时提供审计安全和企业级数据库 ...
- laravel-admin
laravel-admin 文档地址: http://laravel-admin.org/docs/#/zh/
- mytop安装,使用mytop监控MySQL性能 (总结)
mytop 是一个类似 Linux 下的 top 命令风格的 MySQL 监控工具,可以监控当前的连接用户和正在执行的命令. 1. 安装TermReadKey 下载地址: wget http ...
- LintCode刷题笔记--Longest Increasing Subsequence
标签: 动态规划 描述: Given a sequence of integers, find the longest increasing subsequence (LIS). You code s ...
- ASP.NET 自定义服务器控件
文章内容 本文通过创建一个最简单的服务器控件,演示开发服务器端控件的流程. 文章内容整理自MSDN的编程指南,原文地址在文章末尾的资源中. 本文创建一个简单的服务器控件,名为 RedLabel. ...