1. 程式人生 > 實用技巧 >P3247 [HNOI2016]最小公倍數

P3247 [HNOI2016]最小公倍數

題目連結

題意分析

這道題的題意:給定一個無向圖,每一次詢問u與v之間是否存在一條路徑使得ai最大值是a,bi最大值是b

我一開始以為是離線+瓶頸樹

後來發現 這道題必須是恰好等於 而不是大於等於或者小於等於

我們看一下 這道題如果路徑上只有一個權值的話 我們可以使用雙指標

**把對於當前詢問ai 路徑中的ai小於等於這個ai的我們用帶權並查集維護 然後最大值是否等於就可以了 **

但是很不幸的是有兩個

無恥的參考了dalao的見解之後

對原始路徑按照ai進行排序 對詢問按照bi排序

對原始的邊來一個分塊

我們先列舉塊 並暴力列舉詢問 把ai在當前塊的先存起來

然後我們對於之前的塊按照bi排序

那麼加入的詢問以及排好序的一部分我們只需要考慮bi就可以了

因為現在我們使用的邊都是ai小於等於詢問的ai 所以只需要考慮ai最大值等於詢問的ai就可以了

由於只考慮bi並且都是有序的 我們使用雙指標就可以了

雙指標思路同上

對於當前塊內的我們暴力新增就可以了

但是要注意 由於詢問的ai還是無序的

所以新增的塊內的邊我們需要從並查集內刪除

也就是可撤銷並查集 利用棧就可以了

具體參見程式碼

CODE:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<cstdlib>
#include<string>
#define N 100008
using namespace std;
int n,m,q,S,top,tot,pos;
int bel[N],cdy[N],wzy[N],deep[N],ans[N];
struct Edge
{
	int u,v,a,b,id;
}ed[N],qe[N],tmp[N];
struct Node
{
	int x,y,deep,a,b;
}sta[N];

bool cmp1(const Edge &A,const Edge &B)
{return A.a==B.a ? A.b<B.b : A.a<B.a;}
bool cmp2(const Edge &A,const Edge &B)
{return A.b==B.b ? A.a<B.a : A.b<B.b;}

int find(int x)
{return bel[x]==x ? x : find(bel[x]);}

void merge(Edge X)
{
	int x=find(X.u),y=find(X.v);
	if(deep[x]<deep[y]) swap(x,y);
	sta[++top]=(Node){x,y,deep[x],cdy[x],wzy[x]};//入棧 
	cdy[x]=max(cdy[x],X.a);wzy[x]=max(wzy[x],X.b);
	if(x==y) return;
	bel[y]=x;deep[x]=max(deep[x],deep[y]+1);
	cdy[x]=max(cdy[x],cdy[y]);
	wzy[x]=max(wzy[x],wzy[y]);
}
bool query(int id)
{
	int x=find(tmp[id].u),y=find(tmp[id].v);
	if(x!=y) return 0;
	if(cdy[x]!=tmp[id].a || wzy[x]!=tmp[id].b) return 0;
	return 1;
}
void del(int now)
{
	int x=sta[now].x,y=sta[now].y;
	bel[y]=y;deep[x]=sta[now].deep;
	cdy[x]=sta[now].a;wzy[x]=sta[now].b;
}
int main()
{
	cin>>n>>m;S=sqrt(m);
	for(int i=1;i<=m;++i)
	cin>>ed[i].u>>ed[i].v>>ed[i].a>>ed[i].b; 
	
	cin>>q;
	for(int i=1;i<=q;++i)
	cin>>qe[i].u>>qe[i].v>>qe[i].a>>qe[i].b,qe[i].id=i;
	
	sort(ed+1,ed+m+1,cmp1);sort(qe+1,qe+q+1,cmp2);
	for(int i=1;i<=m;i+=S)
	{
		tot=0;pos=1;
		for(int j=1;j<=n;++j) bel[j]=j,cdy[j]=wzy[j]=-1,deep[j]=0;
		for(int j=1;j<=q;++j)
		if(qe[j].a>=ed[i].a && (i+S>m || qe[j].a<ed[i+S].a))
		tmp[++tot]=qe[j];
		
		if(tot==0) continue;
		if(i!=1) sort(ed+1,ed+i,cmp2);//前面的塊我們就可以雙指標了 
		
		for(int j=1;j<=tot;++j)
		{
			while(pos<i && ed[pos].b<=tmp[j].b)
			merge(ed[pos]),++pos;//雙指標維護 
			top=0;//top : 這裡是當前塊中的 不是普遍適用的 所以我們要入棧然後刪掉 
			for(int k=i;k<i+S&&k<=m;++k)
			if(ed[k].a<=tmp[j].a&&ed[k].b<=tmp[j].b)
			merge(ed[k]);
			ans[tmp[j].id]=query(j);
			while(top) del(top--); //刪棧 
		} 
	}
	for(int i=1;i<=q;++i)
	if(ans[i]) puts("Yes");
	else puts("No");
	return 0;
}