题目分析
这道题其实最容易想到的方法是离线枚举,但是其时间复杂度为,很明显会超时(这题数据连离线化都救不了) 那该如何办?
并查集;因为他说有一个推荐列表,而这个推荐列表中i号视频推荐了j号视频,那j号视频也一定会推荐i号视频,即在同一个推荐列表中的视频于当前视频互相可达;这不就是一个连通块的思想吗,题目所求的就是当前连通块的元素个数;
梦回银河英雄传说,但其实这题还要简单一些,只需要更新father数组的同时更新size就行了,最后的答案就是
size[get(vi)]-1
注意还要减去这个视频本身(不会推荐自己)Buuuuuuuuuuu uuuuuut,怎么建边?每次询问BFS暴力,TLE;记忆化,照样有被卡TLE的可能;那我们在来构思一个奇特的样例:有一个只有一条直链的图,问题问的是同一个点,那么随着ki的值依次递减,那么其ans将会越来越大(单调递增)……有没有规律?
我们可以看一下对于同一个点,k的值越单调递增,其ans单调递减,那我们可以尝试着把其每一次的询问离线出来(存储),按k的值从大到小排序,每次把可以满足条件的点加进并查集再存储当前答案(注意,由于问题被我们排过序,所以我们不能直接输出,同时,在排序时也应当用结构体存好当前问题der编号),最后离线输出即可。
那我们来想一下什么样的边可以在k=ki可以被嘉靖并查集里呢?很明显,就是这条边的权值大于当前的k时才能被嘉靖并查集,可以证明,如果这条边本身的权值小于k,那么经过这条边的路径的最小值中的最大值就小于k了,撅对没有可能路径中相关性的最小值大于等于k,所以这条边并不会出现在推荐列表中。并且,由于现在的k单调递减,所以说一条边在这次可以被加入,那下一次也可以被加入,所以我们只需要一个并查集就行了;(同时,为了优化加边是的时间复杂度,可以把边的权值从大到小排序,每次从上一次结束的地方开始判断,有一条边不满足条件就Break存储答案开始下一次添边)
完结撒花!
AC Code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,q,head,anss[400005];
int father[400005],size[400005];
struct lyt{
int to;int be;int data;
}dis[400005];
struct lxy{
int k;int v;int tip;
}question[400005];
bool cmp1(lyt x,lyt y){
return x.data>y.data;
}
bool cmp2(lxy x,lxy y){
return x.k>y.k;
}
int get(int x){
if(father[x]==x){return x;}
return father[x]=get(father[x]);
}
void onion(int x,int y){
int fx=get(x);
int fy=get(y);
father[fx]=fy;
size[fy]+=size[fx];
size[fx]=0;
return;
}
int dfs(int wiki){
while(head<n&&dis[head].data>=question[wiki].k){
onion(dis[head].to,dis[head].be);head++;
}
return size[get(question[wiki].v)];
}
signed main(){
scanf("%lld%lld",&n,&q);
for(int i=1;i<n;i++){
scanf("%lld%lld%lld",&dis[i].to,&dis[i].be,&dis[i].data);
father[i]=i;
size[i]=1;
}father[n]=n;size[n]=1;
sort(dis+1,dis+n,cmp1);
for(int i=1;i<=q;i++){
scanf("%lld%lld",&question[i].k,&question[i].v);
question[i].tip=i;
}
sort(question+1,question+q+1,cmp2);
head=1;
for(int i=1;i<=q;i++){
anss[question[i].tip]=dfs(i);
}
for(int i=1;i<=q;i++){
printf("%lld\n",anss[i]-1);
}
return 0;
}