kruskal重構樹練習

洛谷 P4197 Peaks

題意:

nn 個山峯,每一個山峯高 hih_i ,有 mm 條雙向帶權邊將一些山峯連接起來,有 qq 次詢問,每次詢問 (v,x,k)(v,x,k) ,即從 vv 山峯出發經過邊權不超過 xx 的邊能到的點裏的第 kk 高的山峯。

思路:

考慮在線的解法,要求經過邊權不超過 xx 的邊能到的點,所以就想到使用 kruskalkruskal 重構樹,那麼每一個山峯就是葉子節點,使用 dfsdfs 序,那麼每一個點都有一個區間,而 kruskalkruskal 重構樹是一個大根堆,要求不超過 xx 的邊權,即找到深度的最小的點且點權小於等於 xx ,那麼在該點對應區間上求區間第 kk 大即可

代碼:

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+10;
struct edge{
	int from,to,w;
	edge(int a,int b,int c){from=a,to=b,w=c;}
	bool operator < (const edge &t)const {
		return w<t.w;
	}
};
vector<edge>e,E[N];
vector<int>v;
int in[N],out[N],mx;
int val[N],w[N],fa[N],f[N][22];
int getid(int x){return lower_bound(v.begin(),v.end(),x)-v.begin()+1;}
int fi(int x){if(x==fa[x])return x;return fa[x]=fi(fa[x]);}
int n,m,q,tot,Time;
//zx_tree
struct zx_tree{
	int ls[N*50],rs[N*50],rt[N*50],tot=0,sum[N*50];
	inline void update(int &root,int pre,int l,int r,int pos){
		root=++tot;ls[root]=ls[pre],rs[root]=rs[pre],sum[root]=sum[pre]+1;
		if(l==r)return ;
		int mid=(l+r)/2;
		if(pos<=mid)update(ls[root],ls[pre],l,mid,pos);
		else update(rs[root],rs[pre],mid+1,r,pos);
	}
    inline int query(int root,int pre,int l,int r,int k){//區間第k小
        if(l==r)return l;
        int mid=(l+r)/2;int Sum=sum[ls[root]]-sum[ls[pre]];
        if(Sum>k)return query(ls[root],ls[pre],l,mid,k);
        else return query(rs[root],rs[pre],mid+1,r,k-Sum);
    }
}T;
void cl(int x){
	for(int i=1;(1<<i)<=n;i++){
		f[x][i]=f[f[x][i-1]][i-1];
	}
}
void dfs(int x){
	cl(x);
	in[x]=++Time;
	if(x<=n)T.update(T.rt[Time],T.rt[Time-1],1,mx,getid(val[x]));//利用時間戳來建樹
	else T.rt[Time]=T.rt[Time-1];
	for(edge now:E[x]){
		int v=now.to;
		dfs(v);
	}
	out[x]=Time;
}
int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++){
		scanf("%d",&val[i]);v.push_back(val[i]);
	}
	for(int i=1;i<=2*n;i++)fa[i]=i;
	sort(v.begin(),v.end());v.erase(unique(v.begin(),v.end()),v.end());mx=v.size();
	for(int i=1,u,v,w;i<=m;i++){
		scanf("%d%d%d",&u,&v,&w);
		e.push_back(edge(u,v,w));
	}
	sort(e.begin(),e.end());tot=n;
	for(edge now:e){
		int f1=fi(now.from),f2=fi(now.to);
		if(f1==f2)continue;
		++tot;
		w[tot]=now.w;f[f1][0]=f[f2][0]=tot;
		fa[f1]=fa[f2]=tot;
		E[tot].push_back(edge(0,f1,0));E[tot].push_back(edge(0,f2,0));
		if(tot==2*n-1)break;
	}
	for(int i=1;i<=tot;i++)if(!in[i])dfs(fi(i));
	while(q--){
		int a,b,c;
		scanf("%d%d%d",&a,&b,&c);
		for(int i=18;~i;i--){
			if(f[a][i]&&w[f[a][i]]<=b)a=f[a][i];
		}
		int L=T.rt[in[a]-1],R=T.rt[out[a]];
		int Max=T.sum[R]-T.sum[L];
		if(Max<c){
			puts("-1");continue;
		}
		int pos=T.query(R,L,1,mx,Max-c);//第k大轉化爲第k小
		printf("%d\n",v[pos-1]);
	}
}

Comet OJ - Contest #11 D.isaster

題意:

對一張 nn 個點 mm 條邊點帶權的無向連通圖進行以下兩種操作:
1.修改點 xx 的點權。
2.詢問從點 xx 出發只經過編號不大於 yy 的點能到達的所有點的點權之積取模 998244353998244353

思路:

和上一題類似,只要將kruskal重構樹求出來,在利用線段樹維護每一個點的點權積即可,支持單點修改。
(不知道爲什麼用c++就一直RE,改了兩個小時,用c++17就過了)

代碼:

#include <bits/stdc++.h>
#define ll long long
#define ls x<<1
#define rs x<<1|1
using namespace std;
const int N=1e6+10;
const int mod=998244353;
int n,m,q,op,x,y;
int val[N],fa[N],f[N][22],arr[N];
int fi(int x){
	if(x==fa[x])return x;
	return fa[x]=fi(fa[x]);
}
struct edge{
	int from,to,w;
	edge(int a,int b,int c){from=a,to=b,w=c;}
	bool operator < (const edge &t)const {
		return w<t.w;
	}
};
vector<edge>e,E[N];//原邊和新構的二叉樹,一個大根堆
void kurskal(){
	for(edge now:e){//
		int f1=fi(now.from),f2=fi(now.to);
		if(f1==f2)continue;
		if(f1<f2)swap(f1,f2);
		fa[f2]=f1;
		E[f1].push_back(edge(0,f2,0));//加入新邊
	}
}
int Time,in[N],out[N],id[N];
void dfs(int x,int Fa){
	f[x][0]=Fa;
	for(int i=1;i<20;i++)f[x][i]=f[f[x][i-1]][i-1];
	in[x]=++Time;id[Time]=x;
	for(edge now:E[x]){
		int v=now.to;
		dfs(v,x);
	}
	out[x]=Time;
}
//
struct node{
	int l,r;
	ll sum;
}T[N*4];
void up(int x){
	T[x].sum=(T[ls].sum*T[rs].sum)%mod;
}
void built(int x,int l,int r){
	T[x].l=l;T[x].r=r;
	if(l==r){
		T[x].sum=val[id[l]];return ;
	}
	int mid=(l+r)/2;
	built(ls,l,mid);built(rs,mid+1,r);
	up(x);
}
void add(int x,int pos,int val){
	if(T[x].l==T[x].r){
		T[x].sum=val%mod;return ;
	}
	int mid=(T[x].l+T[x].r)/2;
	if(pos<=mid)add(ls,pos,val);
	else add(rs,pos,val);
	up(x);
}
ll query(int x,int LL,int RR){
	if(T[x].l>=LL&&T[x].r<=RR){
		return T[x].sum%mod;
	}
	int mid=(T[x].l+T[x].r)/2;
	ll ans=1;
	if(LL<=mid)ans=(ans*query(ls,LL,RR))%mod;
	if(RR>mid)ans=(ans*query(rs,LL,RR))%mod;
	return ans;
}
//

int main()
{
	scanf("%d%d%d",&n,&m,&q);
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=n;i++)scanf("%d",&val[i]),val[i]%=mod;
	for(int i=1,u,v;i<=m;i++){
		scanf("%d%d",&u,&v);
		//if(u==v)continue;
		e.push_back(edge(u,v,max(u,v)));
	}
	sort(e.begin(),e.end());
	kurskal();
	dfs(n,0);
	built(1,1,Time);
	while(q--){
		scanf("%d%d%d",&op,&x,&y);
		if(op==1){
			if(x>y){
				puts("0");continue;
			}
			for(int i=19;~i;i--){
				if(f[x][i]&&f[x][i]<=y)x=f[x][i];
			}
			int L=in[x],R=out[x];
			printf("%lld\n",query(1,L,R));
		}else{
			add(1,in[x],y);
		}
	}
}

洛谷 P4768 [NOI2018]歸程

題意:

給一個無向圖,每一條邊有一個長度和海拔,結點 11 是終點,多次詢問,每次詢問從 vv 點出發要到終點,給定一個 kk 值,對於經過的每一條邊,如果海拔大於等於 kk 可以使用車,如果小於則只能步行,在每一個結點可以隨時下車步行,但不能再次上車,問到終點的最小步行長度。

思路:

由於是大於等於p,故我們在建重構樹的時候使用最大生成樹的建法,使其成爲一個小根堆,那麼對於每次詢問,和上面一樣我們倍增找到一個結點,可以知道該結點形成的子樹都可以使用車,那麼步行的最短距離就是子樹的點中與終點的最短距離,可以預處理出來。

代碼:

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
const int N=4e5+10;
int n,m,T,ans,q,k,s,v,p,dis[N],w[N],v0,p0;
struct edge{
	int from,to,w;
	edge(int a,int b,int c){from=a,to=b,w=c;}
	bool operator < (const edge &t)const {
		return w>t.w;
	}
};
int f[N][22],fa[N];
int fi(int x){
	if(x==fa[x])return x;
	return fa[x]=fi(fa[x]);
}
vector<edge>e,E[N],E1[N];
int tot;
void kurskal(){
	for(edge now:e){
		int f1=fi(now.from),f2=fi(now.to);
		if(f1==f2)continue;
		++tot;
		w[tot]=now.w;f[f1][0]=f[f2][0]=tot;
		fa[f1]=fa[f2]=tot;
		E[tot].push_back(edge(0,f1,0));E[tot].push_back(edge(0,f2,0));//加入新邊
		if(tot==2*n-1)break;
	}
}
void init(){
	tot=n;e.clear();ans=0;for(int i=1;i<=n;i++)w[i]=inf;
	for(int i=1;i<=2*n;i++)fa[i]=i,E[i].clear(),E1[i].clear();
	for(int i=1;i<=2*n;i++)
	for(int j=0;j<=20;j++)f[i][j]=0;

}
void change(){
	v=(v0+k*ans-1)%n+1;
	p=(p0+k*ans)%(s+1);
}
bool vis[N];
int val[N];
void dfs(int x){
	vis[x]=1;
	for(int i=1;i<=20;i++)f[x][i]=f[f[x][i-1]][i-1];
	val[x]=dis[x];
	//cout<<"edge:"<<x<<" "<<val[x]<<endl;
	for(edge now:E[x]){
		int v=now.to;
		//cout<<"next:"<<v<<endl;
		dfs(v);
		val[x]=min(val[x],val[v]);
	}
}
void dij(){
	memset(dis,inf,sizeof(dis));
	memset(vis,0,sizeof(vis));
	priority_queue<pair<int,int> >q;
	q.push(make_pair(0,1));dis[1]=0;
	while(!q.empty()){
		int now=q.top().second;q.pop();
		if(vis[now])continue;
		vis[now]=1;
		for(edge nnow:E1[now]){
			int v=nnow.to;
			if(dis[v]>dis[now]+nnow.w){
				dis[v]=dis[now]+nnow.w;
				q.push(make_pair(-dis[v],v));
			}
		}
	}
	//for(int i=1;i<=2*n;i++)cout<<dis[i]<<endl;
}
int main()
{
	scanf("%d",&T);
	while(T--){
		scanf("%d%d",&n,&m);
		init();
		for(int i=1,u,v,x,y;i<=m;i++){
			scanf("%d%d%d%d",&u,&v,&x,&y);
			e.push_back(edge(u,v,y));
			E1[u].push_back(edge(0,v,x));
			E1[v].push_back(edge(0,u,x));
		}sort(e.begin(),e.end());kurskal();dij();
		memset(vis,0,sizeof(vis));
		for(int i=1;i<=tot;i++)if(!vis[i])dfs(fi(i));
		//for(int i=1;i<=tot;i++)cout<<val[i]<<endl;
		scanf("%d%d%d",&q,&k,&s);
		while(q--){
			scanf("%d%d",&v0,&p0);
			change();
			for(int i=19;~i;i--){
				if(f[v][i]&&w[f[v][i]]>p)v=f[v][i];
			}
			//cout<<v<<endl;
			printf("%d\n",val[v]);ans=val[v];
		}

	}
}

2016 ACM-ICPC CHINA-Final G. Pandaria

題意:

給一個樹,每條邊有邊權,每個點有顏色,多次詢問,強制在線,每次詢問從 vv 點出發,經過邊權不超過 qq 的邊能到的點裏,出現最多次的顏色。

思路:

學習了 kruskalkruskal 重構樹後這題就比較簡單了,首先利用重構樹得到能到達的點集,問題就是如何維護出現最多次的顏色,我們可以先不重構樹,而是對每一個點建立一個權值線段樹,在重構的時候合併線段樹,那麼合併後的結點信息就是答案,在回答時先倍增到目標結點,然後利用線段樹查詢。(細節較多,注意初始化)

代碼:

#include <bits/stdc++.h>
#define ls e[x].l
#define rs e[x].r
using namespace std;
const int N=2e5+10;
int n,T,m,q,c[N];
struct node{//權值線段樹
	int l,r,sum,id;
}e[N*50];
void up(int x){
	e[x].sum=max(e[ls].sum,e[rs].sum);
	if(e[ls].sum==e[x].sum)e[x].id=e[ls].id;//取較小的
	else e[x].id=e[rs].id;
}
int cnt,tot,rt[N*50];//開50倍
void update(int &x,int l,int r,int pos){
	if(!x)x=++cnt;
	if(l==r){
		e[x].sum++;e[x].id=l;return ;
	}
	int mid=(l+r)/2;
	if(pos<=mid)update(ls,l,mid,pos);
	else update(rs,mid+1,r,pos);
	up(x);
}
int merge(int x,int y,int l,int r){
	if(!x||!y)return x+y;
	if(l==r){
		e[x].sum+=e[y].sum;
		e[x].id=l;return x;
	}
	int mid=(l+r)/2;
	ls=merge(e[x].l,e[y].l,l,mid);
	rs=merge(e[x].r,e[y].r,mid+1,r);
	up(x);
	return x;
}
int ans[N];
int fa[N];
int fi(int x){
	if(x==fa[x])return x;
	return fa[x]=fi(fa[x]);
}
struct edge{
	int from,to,w;
	edge(int a=0,int b=0,int c=0){from=a,to=b,w=c;}
	bool operator < (const edge &t)const{
		return w<t.w;
	}
}E[N*2];
bool vis[N];
int son[N][2],f[N][22],w[N];
void kruskal(){
	sort(E+1,E+m+1);
	for(int i=1;i<=m;i++){
		int f1=fi(E[i].from),f2=fi(E[i].to);
		if(f1==f2)continue;
		++tot;
		w[tot]=E[i].w;rt[tot]=merge(rt[f1],rt[f2],1,n);//將合併的結點給新節點賦值
		son[tot][0]=f1,son[tot][1]=f2;
		f[f1][0]=f[f2][0]=tot;fa[f2]=fa[f1]=tot;
		ans[tot]=e[rt[tot]].id;//存儲答案
	}
}
void dfs(int u){
	vis[u]=1;
	for(int i=1;i<=20;i++)f[u][i]=f[f[u][i-1]][i-1];//倍增
	if(son[u][0])dfs(son[u][0]);
	if(son[u][1])dfs(son[u][1]);
}
int Ans;
void init(){//
	Ans=0;
	for(int i=1;i<=2*n;i++)rt[i]=0,vis[i]=0;
	for(int i=1;i<=cnt;i++)e[i].l=e[i].r=e[i].sum=e[i].id=0;
	for(int i=1;i<=2*n;i++)fa[i]=i,son[i][0]=son[i][1]=0;//很關鍵,否則TLE
	for(int i=1;i<=2*n;i++)
	for(int j=1;j<=20;j++)f[i][j]=0;
	tot=n;cnt=0;
}
int main()
{
	scanf("%d",&T);int ca=1;
	while(T--){
		scanf("%d%d",&n,&m);
		init();
		printf("Case #%d:\n",ca++);
		for(int i=1;i<=n;i++)scanf("%d",&c[i]),update(rt[i],1,n,c[i]);
		for(int i=1,u,v,w;i<=m;i++){
			scanf("%d%d%d",&u,&v,&w);E[i]=edge(u,v,w);
		}
		for(int i=1;i<=n;i++)ans[i]=c[i];
		kruskal();for(int i=1;i<=tot;i++)if(!vis[i])dfs(fi(i));
		scanf("%d",&q);
		while(q--){
			int x,W;
			scanf("%d%d",&x,&W);
			x^=Ans;W^=Ans;
			for(int i=19;~i;i--){
				if(f[x][i]&&w[f[x][i]]<=W)x=f[x][i];
			}
			//cout<<x<<endl;
			printf("%d\n",ans[x]);Ans=ans[x];
		}
	}
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章