题目描述

辣鸡$ljh\ NOI$之后就退役了,然后就滚去学文化课了。
他每天都被$katarina$大神虐,仗着自己学过一些姿势就给$katarina$大神出了一道题。
有一棵$n$个节点的以$1$号节点为根的树,每个节点上有一个小桶,节点$u$上的小桶可以容纳个小球,$ljh$每次可以给一个节点到根路径上的所有节点的小桶内放一个小球,如果这个节点的小桶满了则不能放进这个节点,在放完所有小球之后就企图去刁难$katarina$大神,让$katarina$大神回答每个节点的小桶内的小球有多少种颜色。
然而$katarina$大神一眼就秒掉了,还说这就是一道傻逼模板题。
现在$katarina$大神想考考即将参加$NOIP2019$的你能不能回答上辣鸡$ljh$的问题。


输入格式

第一行,一个整数$n$,树上节点的数量。
接下来$n-1$行,每行两个整数$u,v$,表示在$u,v$之间有一条边。
接下来一行$n$个整数,$k_1~k_n$表示每个节点上的小桶数量。
下一行是一个整数$m$,表示$ljh$进行的操作数量。
接下来$m$行,每行两个整数$x,c$,分别表示进行操作的节点和小球颜色。
下一行是一个整数$Q$,表示你需要回答的询问数。
接下来$Q$行,每行一个整数$x$,表示一个询问。


输出格式

对于每个询问输出一行表示这个询问的答案。


样例

样例输入1:

5
1 2
2 3
3 4
2 5
2 1 1 1 1
2
2 1
4 2
3
1
3
5

样例输出1:

2
1
0

样例输入2:

10
3 10
2 5
3 2
2 6
1 9
8 7
7 4
3 8
3 1
15 47 23 22 9 16 45 39 21 13
10
10 7
9 3
5 1
5 2
9 4
10 9
2 4
10 1
2 6
7 9
3
1
2
3

样例输出2:

7
4
6


数据范围与提示

对于$5\%$的数据,$n\leqslant 10,m\leqslant 10,k_i\leqslant 10$。

对于$30\%$的数据,$n\leqslant {10}^3,m\leqslant {10}^3,k_i\leqslant {10}^3$。

对于另外$40\%$的数据,$n\leqslant {10}^5,m\leqslant {10}^5,k_i={10}^5$。

对于$100\%$的数据,$n\leqslant {10}^5,m\leqslant {10}^5,k_i\leqslant {10}^5$。


题解

$5\%$算法:

到现在我都没想出来。

$30\%$算法:

暴力修改,$\Theta(1)$查找答案。

时间复杂度:$\Theta(n^2)$。

期望得分:$30$分。

$40\%$算法:

注意$k_i={10}^5$,也就是说,每一个桶肯定不会装超,问题也就简单了许多,转化成了[BZOJ3307]:雨天的尾巴这道题。

区别在于,这道题是维护总和,而那道题是维护最大值,而且这道题还不用LCA。

时间复杂度:$\Theta(n\log n)$。

期望得分:$40$分。

总得分:$70$分。

$100\%$算法:

对于每一个结点都用一棵线段树维护,下标为时间,存储这个时间子树中是否已经加入了小球和贡献(注意如果之前已经加入了这个小球则贡献为0)。

在线段树上二分查找答案就好了。

但是,显然这样还不够,无论是时间还是空间。

所以我们使用线段树启发式合并,利用启发式合并的方式处理出当前子树中的所有操作,同时构建出新的线段树。

我们还可以先预处理出重儿子,这样我们就可以更少的改变当前线段树里的值。

时间复杂度:$\Theta(n\log^2n)$。

期望得分:$100$分。


代码时刻

$30\%$算法:

#include<bits/stdc++.h>
using namespace std;
struct rec
{
int nxt;
int to;
}e[200001];
int N,M,Q;
int head[100001],cnt;
int k[100001];
int fa[100001];
bool Map[1001][1001];
int ans[100001];
void add(int x,int y)
{
e[++cnt].nxt=head[x];
e[cnt].to=y;
head[x]=cnt;
}
void pre_dfs(int x)//预处理
{
for(int i=head[x];i;i=e[i].nxt)
if(e[i].to!=fa[x])
{
fa[e[i].to]=x;
pre_dfs(e[i].to);
}
}
void ufs(int x,int c)//疯狂往上找,暴力修改
{
while(1)
{
if(!x)break;
if(!Map[x][c]&&k[x]>0)ans[x]++;
Map[x][c]=1;
k[x]--;
x=fa[x];
}
}
int main()
{
scanf("%d",&N);
for(int i=1;i<N;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
for(int i=1;i<=N;i++)
scanf("%d",&k[i]);
scanf("%d",&M);
if(N<=1000&&M<=1000)
{
pre_dfs(1);
while(M--)
{
int x,c;
scanf("%d%d",&x,&c);
ufs(x,c);
}
scanf("%d",&Q);
while(Q--)
{
int x;
scanf("%d",&x);
printf("%d\n",ans[x]);
}
}
return 0;
}

$40\%$算法:

#include<bits/stdc++.h>
using namespace std;
struct rec
{
int nxt;
int to;
}e[200001];
struct node
{
int x;
int c;
}q[100001];
int N,M,Q;
int head[100001],cnt;
int root[100001],trsum[10000001],ls[10000001],rs[10000001],tot;
int k[100001];
int dfsv[100001];
bool vis[100001];
int sum;
int ans[100001];
bool cmp(node a,node b){return a.c<b.c;}
void add(int x,int y)
{
e[++cnt].nxt=head[x];
e[cnt].to=y;
head[x]=cnt;
}
void insert(int &x,int k,int l,int r)
{
if(!x)x=++tot;
if(l==r)
{
trsum[x]=1;
return;
}
int mid=(l+r)>>1;
if(k<=mid)insert(ls[x],k,l,mid);
else insert(rs[x],k,mid+1,r);
trsum[x]=trsum[ls[x]]+trsum[rs[x]];
}
int merge(int x,int fl,int l,int r)//合并
{
if(!x||!fl)return x+fl;
if(l==r)
{
trsum[x]=trsum[x]||trsum[fl];
return x;
}
int mid=(l+r)>>1;
ls[x]=merge(ls[x],ls[fl],l,mid);
rs[x]=merge(rs[x],rs[fl],mid+1,r);
trsum[x]=trsum[ls[x]]+trsum[rs[x]];
return x;
}
void dfs(int x)
{
vis[x]=1;
for(int i=head[x];i;i=e[i].nxt)
if(!vis[e[i].to])
{
dfs(e[i].to);
root[x]=merge(root[x],root[e[i].to],1,M);
}
ans[x]=trsum[root[x]];
}
int main()
{
scanf("%d",&N);
for(int i=1;i<N;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
for(int i=1;i<=N;i++)
scanf("%d",&k[i]);
scanf("%d",&M);
for(int i=1;i<=M;i++)
scanf("%d%d",&q[i].x,&q[i].c);
sort(q+1,q+M+1,cmp);
for(int i=1;i<=M;i++)
{
if(q[i].c!=q[i-1].c)sum++;//算是一个预处理吧,思路有些清奇
insert(root[q[i].x],sum,1,M);
}
dfs(1);
scanf("%d",&Q);
while(Q--)
{
int x;
scanf("%d",&x);
printf("%d\n",ans[x]);
}
return 0;
}

$100\%$算法:

#include<bits/stdc++.h>
#define L(x) x<<1
#define R(x) x<<1|1
using namespace std;
struct rec
{
int nxt;
int to;
}e[200001];
struct node
{
int x;
int c;
}q[100001];
int N,M,Q;
int head[100001],cnt;
int t[100001];
map<int,int> col;//会有负的权值,所以用map
int sum;
bool vis[100001];
int son[100001];
int tr[400001],lz[400001],tle[400001],size[400001];
int ans[100001];
vector<pair<int,int> >super[100001];//用vector存储每一个点内球的颜色和时间,二维数组会MLE
void add(int x,int y)
{
e[++cnt].nxt=head[x];
e[cnt].to=y;
head[x]=cnt;
}
void super_memset(int x)//清空
{
tr[1]=size[1]=0;
lz[1]=1;
for(int i=0;i<super[x].size();i++)tle[super[x][i].first]=0;
}
void pushup(int x)
{
tr[x]=tr[L(x)]+tr[R(x)];
size[x]=size[L(x)]+size[R(x)];
}
void pushdown(int x)
{
if(!lz[x])return;
size[L(x)]=size[R(x)]=tr[L(x)]=tr[R(x)]=lz[x]=0;
lz[L(x)]=lz[R(x)]=1;
}
void change(int x,int l,int r,int k,int v,int w)
{
tr[x]+=v;
size[x]+=w;
if(l==r)return;
pushdown(x);
int mid=(l+r)>>1;
if(k<=mid)change(L(x),l,mid,k,v,w);
else change(R(x),mid+1,r,k,v,w);
pushup(x);
}
void add(int x)
{
for(int i=0;i<super[x].size();i++)
{
pair<int,int> flag=super[x][i];
if(!tle[flag.first])
{
change(1,1,M,flag.second,1,0);
tle[flag.first]=flag.second;
}
else if(tle[flag.first]>flag.second)
{
change(1,1,M,tle[flag.first],-1,0);
change(1,1,M,flag.second,1,0);
tle[flag.first]=flag.second;
}
change(1,1,M,flag.second,0,1);
}
}
int ask(int x,int l,int r,int k)
{
if(k<=0)return 0;
if(l==r)return tr[x];
pushdown(x);
int mid=(l+r)>>1;
if(size[L(x)]<=k)return tr[L(x)]+ask(R(x),mid+1,r,k-size[L(x)]);
else return ask(L(x),l,mid,k);
}
void connect(int x,int y)
{
for(int i=0;i<super[y].size();i++)
super[x].push_back(super[y][i]);
super[y].clear();
}
void pre_dfs(int x)
{
size[x]=super[x].size();
vis[x]=1;
for(int i=head[x];i;i=e[i].nxt)
if(!vis[e[i].to])
{
pre_dfs(e[i].to);
size[x]+=size[e[i].to];
if(size[e[i].to]>size[son[x]])son[x]=e[i].to;
}
}
void pro_dfs(int x,int fa)
{
for(int i=head[x];i;i=e[i].nxt)
if(e[i].to!=fa&&e[i].to!=son[x])
{
pro_dfs(e[i].to,x);
super_memset(e[i].to);
}
if(son[x])pro_dfs(son[x],x);
add(x);
for(int i=head[x];i;i=e[i].nxt)
if(e[i].to!=fa&&e[i].to!=son[x])
add(e[i].to);
ans[x]=ask(1,1,M,t[x]);
if(son[x])
{
connect(son[x],x);
swap(super[x],super[son[x]]);
for(int i=head[x];i;i=e[i].nxt)
if(e[i].to!=fa)
connect(x,e[i].to);
}
}
int main()
{
scanf("%d",&N);
for(int i=1;i<N;i++)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
for(int i=1;i<=N;i++)
scanf("%d",&t[i]);
scanf("%d",&M);
for(int i=1;i<=M;i++)
{
scanf("%d%d",&q[i].x,&q[i].c);
if(!col[q[i].c])
{
col[q[i].c]=++sum;
q[i].c=sum;
}
else q[i].c=col[q[i].c];
super[q[i].x].push_back(make_pair(q[i].c,i));
}
pre_dfs(1);
memset(size,0,sizeof(size));
pro_dfs(1,0);
scanf("%d",&Q);
while(Q--)
{
int x;
scanf("%d",&x);
printf("%d\n",ans[x]);
}
return 0;
}

rp++

[CSP-S模拟测试]:模板(ac)(线段树启发式合并)的更多相关文章

  1. Bzoj 2733: [HNOI2012]永无乡(线段树+启发式合并)

    2733: [HNOI2012]永无乡 Time Limit: 10 Sec Memory Limit: 128 MB Description 永无乡包含 n 座岛,编号从 1 到 n,每座岛都有自己 ...

  2. [ZJOI2019]语言(树链剖分+动态开点线段树+启发式合并)

    首先,对于从每个点出发的路径,答案一定是过这个点的路径所覆盖的点数.然后可以做树上差分,对每个点记录路径产生总贡献,然后做一个树剖维护,对每个点维护一个动态开点线段树.最后再从根节点开始做一遍dfs, ...

  3. [CSP-S模拟测试]:Weed(线段树)

    题目描述 $duyege$的电脑上面已经长草了,经过辨认上面有金坷垃的痕迹.为了查出真相,$duyege$准备修好电脑之后再进行一次金坷垃的模拟实验.电脑上面有若干层金坷垃,每次只能在上面撒上一层高度 ...

  4. [CSP-S模拟测试]:光线追踪(线段树)

    题目背景 初中时的乔猫试着组建了$NEWorld$开发组,可是不久之后却因为合作上的问题(和乔猫工程水平差,代码混乱的问题),开发组成员之间常常产生矛盾,关系越来越不如以前......一年下来,受到长 ...

  5. [CSP-S模拟测试]:椎(线段树维护区间最值和单调栈)

    题目描述 虽不能至,心向往之. $Treap=Tree+Heap$ 椎$=$树$+$堆 小$\pi$学习了计算机科学中的数据结构$Treap$. 小$\pi$知道$Treap$指的是一种树. 小$\p ...

  6. [CSP-S模拟测试]:bird(线段树优化DP)

    题目传送门(内部题89) 输入格式 第一行两个数$n$和$k$,分别表示小鸟的只数和$R$装弹时间.接下来$n$行,每行两个数$l,r$表示$n$只小鸟初始时的头和尾的$x$坐标. 输出格式 输出一个 ...

  7. [CSP-S模拟测试]:string(线段树)

    题目描述 给定一个由小写字母组成的字符串$s$. 有$m$次操作,每次操作给定$3$个参数$l,r,x$. 如果$x=1$,将$s[l]~s[r]$升序排序: 如果$x=0$,将$s[l]~s[r]$ ...

  8. [CSP-S模拟测试]:Permutation(线段树+拓扑排序+贪心)

    题目描述 你有一个长度为$n$的排列$P$与一个正整数$K$你可以进行如下操作若干次使得排列的字典序尽量小对于两个满足$|i−j|\geqslant K$且$|P_i−P_j|=1$的下标$i$与$j ...

  9. HDU 5029 Relief grain(离线+线段树+启发式合并)(2014 ACM/ICPC Asia Regional Guangzhou Online)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5029 Problem Description The soil is cracking up beca ...

随机推荐

  1. c++ k^1

    如果k是偶数,则使k=k+1:若k是奇数,则使k=k-1.

  2. 第六周课程总结&实验报告

    一.实验目的 (1)掌握类的继承 (2)变量的继承和覆盖,方法的继承,重载和覆盖的实现: 二.实验的内容 (1)根据下面的要求实现圆类Circle. 1.圆类Circle的成员变量:radius表示圆 ...

  3. PHP数据结构基本概念

    原文:https://www.cnblogs.com/crystaltu/p/6408484.html 学习任何一种技术都应该先清楚它的基本概念,这是学习任何知识的起点!本文是讲述数据结构的基本概念, ...

  4. 连连看(简单搜索)bfs

    连连看Time Limit: 20000/10000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others)Total Submis ...

  5. window-tree命令

    tree 以图形方式显示在驱动器中的目录结构或磁盘的路径. 有时候需要整理文档目录时,而文件太多,一个个去写相应的文件目录结构也不现实,就用到了window下的tree命令 语法 tree [< ...

  6. js放大镜特效

    在平时网上商城购物时,我们能够通过放大镜效果来使我们看图片能够更加的清楚,今天我就来给大家分享一下我学习的放大镜特效 下图是原图的样子                                 ...

  7. js中的生成器函数

    入门 简单来说,用法如下: function* fn() { console.log(1); //暂停! yield; //调用next方法继续执行 console.log(2); } var ite ...

  8. MySQL中Innodb的聚簇索引和非聚簇索引

    聚簇索引 数据库表的索引从数据存储方式上可以分为聚簇索引和非聚簇索引(又叫二级索引)两种.Innodb的聚簇索引在同一个B-Tree中保存了索引列和具体的数据,在聚簇索引中,实际的数据保存在叶子页中, ...

  9. 关于阅读Struts2部分拦截器源码的记录

    Struts2中的拦截器在ActionInvocation对象的invoke()方法中执行. ActionInvocation对象从配置文件中读取Interceptor对象,加入到自己的存取拦截器的容 ...

  10. 一、在 ASP.NET Core 中使用 SignalR

    一.介绍 SignalR 是一个用于实现实时网站的 Microsoft .NET 库.它使用多种技术来实现服务器与客户端间的双向通信,服务器可以随时将消息推送到连接的客户端. https://docs ...