古代文明

古代人类文明逐渐发展,但不是所有的原始人都互相认识。
你得到了一份古代的城市文明表a[i],表示在i时刻,a[i]这个城市有人。
城市的地图也会给出,这是一个无向图,每两个城市间有且只有一条路径,就是说这是一棵树。
你的任务是,若i时刻到j时刻的所有城市都存在,那么他们分为多少个大部落。
即ai到aj这些结点后在原图中构成的联通集数(一个节点重复出现只算出现一次)。
对于结点i,结点j,若路径上所有的结点都存在,那么i,j同属一个联通集。

输入格式:

第一行3个整数n,m,q,分别表示树的大小,表的大小,询问个数。
接下来n-1行,每行两个整数x,y表示x城市和y城市间有一条无向边,用空格隔开。点从1开始编号。
接下来m行,每行一个整数,分别表示a1,a2……an,即文明表。注意可能此中结点有重复。
接下来q行,每行两个整数l,r用空格隔开,表示询问第ai到aj间的结点组成的联通集个数。
题目保证树的合法,结点的合法,区间l,r满足1<=l<=r<=m。

输出格式:

输出共q行,对于每个询问输出一行,表示联通集个数。

样例输入:

样例输出:

数据范围:

数据编号 n m q 注释
1 50 100 50
2 50 100 100
3 500 1000 1000
4 500 1000 2000
5 10000 20000 20000
6 10000 30000 30000
7 20000 40000 30000
8 20000 40000 60000
9 100000 100000 100000 树为一条链
10 100000 100000 100000
11 150000 300000 300000
12 150000 300000 300000
13 100000 100000 50000
14 100000 50000 100000
15 100000 200000 200000
16 100000 200000 200000
17 150000 300000 300000
18 150000 300000 300000
19 150000 300000 300000
20 150000 300000 300000

时间限制:

3S

空间限制:

256M



没修改要果断想询问分块!!!(莫队算法)

由于这是棵树不是图。

所以就能O(1)转移。。


!!!!转移一定要先加再减!!!!

	rep(i,L,R){
		rep(j,qt[i].l,lL-1) calc(j,1);
		rep(j,rR+1,qt[i].r) calc(j,1);
		rep(j,lL,qt[i].l-1) calc(j,-1);
		ans[qt[i].pos]=res;
		lL=qt[i].l,rR=qt[i].r;
		}


#include <cmath>
#include <cstdio>
#include <algorithm>
#define rep(i,l,r) for (int i=l;i<=r;++i)
#define per(i,r,l) for (int i=r;i>=l;--i)
int getx(){
	char c;int x;
	for (c=getchar();c<'0'||c>'9';c=getchar());
	for (x=0;c>='0'&&c<='9';c=getchar())
		x=(x<<3)+(x<<1)+c-'0';
	return x;
}
int Min(const int &a,const int &b){return a<b?a:b;}
const int MAX_N=150050,MAX_M=300050;
int first[MAX_N],next[MAX_N<<1],to[MAX_N<<1];
int tal=0;
void tjb(int x,int y){
	next[++tal]=first[x];
	first[x]=tal;to[tal]=y;
}
int fa[MAX_N];
int que[MAX_N];
void bfs(int v){
	int t=0,h=0,u;
	que[t]=v;fa[v]=0;
	while (t<=h){
		v=que[t++];
		for (int k=first[v];k;k=next[k]){
			if ((u=to[k])!=fa[v])
				fa[u]=v,que[++h]=u;
			}
		}
}
int a[MAX_M];
int n,m,q;
int r,s;
struct Q{int l,r,pos;} qt[MAX_M];
int ans[MAX_M];
bool cmpl(const Q &a,const Q &b){return a.l<b.l;}
bool cmpr(const Q &a,const Q &b){return a.r<b.r;}
int son[MAX_N];
int is[MAX_N];
int res;
void calc(int i,int f){
	if (f>0&&(is[a[i]]++>0)) return;
	if (f<0&&(--is[a[i]]>0)) return;
	son[fa[a[i]]]+=f;
	res+=f*(1-(is[fa[a[i]]]?1:0)-son[a[i]]);
}
void work(int L,int R){
	res=0;
	std::fill(is,is+n+10,0);
	std::fill(son,son+n+10,0);
	int lL=qt[L].l;
	std::sort(qt+L,qt+R+1,cmpr);
	int rR=qt[L].r;
	rep(i,lL,rR) calc(i,1);
	rep(i,L,R){
		rep(j,qt[i].l,lL-1) calc(j,1);
		rep(j,rR+1,qt[i].r) calc(j,1);
		rep(j,lL,qt[i].l-1) calc(j,-1);
		ans[qt[i].pos]=res;
		lL=qt[i].l,rR=qt[i].r;
		}
}
int main(){
	freopen("3.in","r",stdin);
	freopen("3.out","w",stdout);
	n=getx(),m=getx(),q=getx();
	s=(int)sqrt(q),r=(q-1)/s;
	rep(i,2,n){
		int x=getx(),y=getx();
		tjb(x,y),tjb(y,x);
		}
	bfs(1);
	rep(i,1,m) a[i]=getx();
	rep(i,0,q-1){
		qt[i].l=getx(),qt[i].r=getx();
		qt[i].pos=i;
		}
	std::sort(qt,qt+q,cmpl);
	rep(i,0,r) work(i*s,Min(q,i*s+s)-1);
	rep(i,0,q-1) printf("%d\n",ans[i]);
}



但是很遗憾这样会T。。。虽然我怎么也觉得不会T。。,。O(N^1.5)


原题解:

10%的数据是用来调的……
20%的数据可以用以下几种暴力解决
算法1:
第一种想法是用在区间里的点数减去总共的边数。
这种算法的每个询问效率是线性的。
总的效率为平方级别。
算法2:
另外一种想法是每次询问把点逐个加入。
当然要开一个数组判重。
保存树上所有点的儿子数目,然后每加入一个询问点时维护它。
每加入一个点后可以把答案减去它的儿子数目,然后看看它是否有父亲。
第三种想法更加直接而简单。将其记为算法3。
算法3:直接计算询问点中没有父亲的点的个数即可。
显然每个这种点代表一个联通集,所以是正确的。
算法4:
基于算法2,用莫队算法优化,即按照莫队的顺序逐个都加入点及删除点,仍然需要判重。
这样的效率是o(n SQRT(N)))
期望得分40-50(可能有10分运气……)
算法5:
发现有20%链的情况,具体有什么特异算法,我也没想过……
假设有的话,结合算法4
可以得到60-70分
100%的算法是基于算法3的。
看看我们究竟要统计什么。
若询问区间[a,b],实际要求的就是[a,b]中"父亲未在[a,b]出现,且无重复的点"。
更深一层,实际实际要求的就是[a,b]中“父亲未在[a,b]出现,且在该数右边到b为止没有出现过的数”的数目。
这样就把判重的问题解决了。
于是不妨对每个数组中的每个点,计算它父亲出现的时间中离它左右最近的两个时间。分别记为x和y。
然后再计算右边它自己所表示的这个点的出现的最小时间,若小于y,那么更新y。
如此,只需x<a<“这个点的位置”<b<y,那么这个点就可以被区间(a,b)统计。
但是,这样仍然很难统计。因为要满足总共4个不等式的条件。
显然直接是不可能的。
经典的处理方法是可持久或排序来减少不等式。
我们以排序为例。
注意到本题可以离线,所以先把所有询问按右端点排序。按照这个顺序处理所有询问,然后当一个点的右指针所指的点被访问时,就可以将它删除了。
按顺序来处理询问之后。我们现在要求的问题是什么呢?
来看一下之前的条件:
x<a<“这个点的位置”<b<y
1.如果有了删除,那么 “b<y”肯定成立。
2.由于是按顺序的,所以 ““这点的位置”<b”也一定成立。
既然这样,我们的问题变成了维护一个数据结构:
1.能够删除和添加元素[x,y](x表示位置,y表示左指针位置)。
2.可以处理询问满足x<a<y的元素个数。
仍然有两个不等式,直接套数据结构还不行。
但是问题已经变得非常简单:
下面给出两种解决方法:
1.询问x<a<y的元素个数,不妨看做有多少个区间覆盖a,那么用线段树处理,每次给[x,y]中的数+1,单点询问。
但这样由于线段树常数大有可能会超时。
2.观察式子特征x<a<y,那么不妨先求出元素x<a的元素个数。
这些元素包括两类:x<y<=a;x<a<y,
所以只需求出y<=a的元素个数即可,此时一定满足x<y<=a,所以可以用两个树状数组,变成单点修改,非常容易实现,常数很小。
最后还有一点细节,就是如何求出每个求出每个点的左右指针。
用logn的方法不难,但是这样常数太大,有可能导致超时。而且也麻烦。
我们可从左往右扫描,以用一个表来表示每个点最后出现的位置。由此可以每加入一个点,查找表中父亲及当前点在表中的值中的较小值,得到左指针,然后就更新表。
最后归纳一下整体思路:
1.求出每个点的父亲。
2.分别从左往右,从右往左扫描,得出每个点的左右指针,将其设为元素(l,a,r)。
3.将所有(l,a,r)按r排序,然后将所有询问(x,y)按y排序。
4.从左往右扫描,到一个点时加入先处理与这个点的位置相同的元素的插入与删除,先将该位置的元素的l和a分别插入,然后将所有r="这个位置坐标"的点删除。接着处理询问,用两个树状数组的结果得出。
5.最后按原顺序输出询问。
至此,本题得到完美解决,效率O(nlogn),期望得分100
可持久化方法:
将之前的排序改为用可持久的方式处理,即保存每个点的持久化线段树,但这样就只能用线段树了而不能用树状数组了。
效率不变,变为离线。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值