点我看题目
题意:
给出一颗n个点的树 结点编号1-n。
给出一个二元组[l,r] 定义一个结点为好结点 当且仅当 其编号 i 满足 l< i < r 时 i结点为好结点。
两个相邻好结点之间的边为好边。
现在有Q次询问 每次询问给出[l,r]
对于每次询问 求出由好边链接的连通块的个数。
解题思路:
最容易想到就是对每次询问进行一遍DFS,找坏边,然后求连通块的个数, 总复杂度为O(nq)
之后想到 其实连通块的个数等于好点个数减去好边个数。
然后询问很像线段树的询问,联想到区间求和问题。
把所有边全部放在一个一维坐标轴上,那么问题可以转化为两端点都在[l,r] 范围内的边有多少条。
先得到一个最暴力的算法 每次询问 暴力遍历所有边,找其中符合条件的边。 复杂度同样为O(nq)
在想如何使用线段树去优化。
一开始就把所有点都加到线段树里面去,然后再查询的做法很容易可以证明是行不通的。情况太多。
想如何化简问题的情况, 根据以前的题目,联想到排序后进行离线处理求结果。
对于询问和边 都用右端点为关键字排序。
然后在询问过程中添加符合右端点小于询问 r的边, 将之左端点l加入到线段树中。 这样 复杂度就降到了O(qlogn)
然后用线段树写老报错 ,改成树状数组就A了
#include<bits/stdc++.h>
#define LL long long
#define INF 0x3f3f3f3f
using namespace std;
const int MAX=(1e5+10)*2;
int tree[MAX],n=MAX;
void add(int x,int num)
{
for(;x<=n;x+=x&-x)
tree[x]+=num;
}
int sum(int x)
{
int answer =0;
for(;x>0;x-=x&-x)
answer+=tree[x];
return answer;
}
class Node{
public:
int l,r,id;
bool operator <(const Node &b) const {
return r<b.r;
}
} ;
Node node1[MAX];
Node node2[MAX];
int ans[MAX];
int main(){
int T;
scanf("%d",&T);
while(T--){
int n,q;
memset(tree,0,sizeof tree);
scanf("%d %d",&n,&q);
for(int i=1;i<n;i++){
scanf("%d %d",&node1[i].l,&node1[i].r);
if(node1[i].l>node1[i].r) swap(node1[i].l,node1[i].r);
}
for(int i=1;i<=q;i++){
scanf("%d %d",&node2[i].l,&node2[i].r);
node2[i].id=i;
}
sort(node1+1,node1+n);
sort(node2+1,node2+q+1);
int tot=1;
for(int i=1;i<=q;i++){
int l=node2[i].l,r=node2[i].r;
//cout<<l<<","<<r<<","<<node2[i].id<<endl;
while(node1[tot].r<=r && tot<n){
add(node1[tot].l,1);
//cout<<"up"<<node1[tot].l<<" "<<node1[tot].r<<endl;
tot++;
}
int sums=sum(r)-sum(l-1);
ans[node2[i].id]=(r-l+1-sums);
}
for(int i=1;i<=q;i++){
printf("%d\n",ans[i]);
}
}
}