[TK] 尋寶遊戲

HaneDaniko發表於2024-08-09

在樹上標記若干個點,求出從某個點走過全部點並回到該點的最小路徑. 有多次詢問,每次詢問只改變一個點.

首先是一個暴力的思路.

會發現,從標記點中的其中一個開始走,結果一定更優,並且無論從哪個點開始走,其結果都是相同的. 假若不考慮還要走回來,那麼答案就應該是標記點兩兩之間路徑和的最小值,至於還要走回來,可以發現走回來的最優策略相同,因此答案乘二即可.

因此這個題就轉化為求標記點兩兩之間路徑和的最小值. 考慮到:將這些點按 DFS 序排序,得到的結果會是最小的. 為了使答案最小,可以想到將相鄰的點都放到一起,或者說在這裡是將距離最近的點放到一起,因此我麼用節點在 DFS 序上的距離來刻畫實際距離,因此按 DFS 序排序.

隨後,可以想到對每一對相鄰的點跑一遍 LCA,求出簡單路徑長. 可以發現,假如我們將 \(dist(1,n)\) 也加入答案的話,正好在每個點都出入了一遍,因此求出來的正好是走一個來回的貢獻.

貼上暴力程式碼

#include<bits/stdc++.h>
using namespace std;
#define int long long
namespace reader{
	template<typename T>
	inline void read(T& x){
		x=0;bool sym=0;char c=getchar();
	    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
	    while(isdigit(c)){x=x*10+c-48;c=getchar();}
	    if(sym)x=-x;
	}
	template<size_t N>
	inline void read(char (&str)[N]){
	    size_t n=0;char c=getchar();
	    while(n<N-1&&!isspace(c)){str[n]=c;c=getchar();++n;}
	    str[n]=0;
	}
	template<typename T,size_t N>
	inline void read(T (&a)[N],int range=N){
		for(int i=1;i<=range;++i){read(a[i]);}
	}
	template<typename T,typename... Args>
	inline void read(T& x,Args&... args){
	    read(x);read(args...);
	}
	template<typename T,typename T2>
	inline void readarray(T& x,T2& args){
		read(x);read(args,x);
	}
	template<typename func,typename... Args>
	inline void readact(int x,function<func>fu,Args&... args){
		for(int i=1;i<=x;++i){
			read(args...);
			fu(args...);
		}
	}
}
using namespace reader;
int n,m;
int fa[100001][17];
int w[100001][17];
struct edge{
	int to,w;
};
int deep[100001],dfn[100001],dfncnt;
vector<edge>e[100001];
void dfs(int now,int last,int lastw,int nowdeep){
	dfn[now]=++dfncnt;
	deep[now]=nowdeep;
	fa[now][0]=last;
	w[now][0]=lastw;
	for(int i=1;i<=16;++i){
		fa[now][i]=fa[fa[now][i-1]][i-1];
		w[now][i]=w[fa[now][i-1]][i-1]+w[now][i-1];
	}
	for(edge i:e[now]){
		if(i.to!=last){
			dfs(i.to,now,i.w,nowdeep+1);
		}
	}
}
int lca(int x,int y){
//	cout<<"lca "<<x<<" "<<y<<endl;
	if(deep[x]<deep[y]) swap(x,y);
	int res=0;
	for(int i=16;i>=0;--i){
		if(deep[x]-deep[y]>=(1<<i)){
//			cout<<"jump "<<x<<" "<<fa[x][i]<<" "<<w[x][i]<<endl;
			res+=w[x][i];
			x=fa[x][i];
		}
	}
//	cout<<"jd "<<res<<" "<<x<<" "<<y<<endl;
	if(x==y) return res;
	for(int i=16;i>=0;--i){
		if(fa[x][i]!=fa[y][i]){
			res+=w[x][i]+w[y][i];
			x=fa[x][i];
			y=fa[y][i];
		}
	}
	if(x==y) return res;
	return res+w[x][0]+w[y][0];
}
vector<int>g;
bool vis[100001];
signed main(){
//	freopen("game1.in","r",stdin);
//	freopen("out.out","w",stdout);
	read(n,m);
	function<void(int,int,int)>f=[](int x,int y,int z){e[x].push_back({y,z});e[y].push_back({x,z});};
	int x,y,z;readact(n-1,f,x,y,z);
	dfs(1,0,0,1);
	while(m--){
		read(x);
		if(!vis[x]){
			vis[x]=true;
			g.push_back(x);
		}
		else{
			vis[x]=false;
			sort(g.begin(),g.end());
			g.erase(lower_bound(g.begin(),g.end(),x));
		}
		sort(g.begin(),g.end(),[](int a,int b){return dfn[a]<dfn[b];});
		int ans=0;
//		cout<<"g:";
//		for(int i:g)cout<<i<<" ";cout<<endl;
		for(int i=0;i<=(int)g.size()-2;++i){
//			cout<<g.size()-2<<endl; 
//			cout<<g.size()<<"act "<<i<<endl;
			ans+=lca(g[i],g[i+1]);
		}
		ans+=lca(g.front(),g.back());
		printf("%lld\n",ans);
	}
}

需要注意的是這裡這個 size()-2 一定要強轉,這玩意是 unsigned 減不成負數,直接給我 for 迴圈搞炸了.

然後我們來考慮正解. 注意到題目中每次詢問只改變一個點這條性質我們還沒用到,考慮改變一個點會對答案有什麼影響.

假設我們要加入一個點 \(x\),距離 \(x\) 的 DFS 序最近的左右兩個標記點分別是 \(y,z\),可以發現,加入前這一段是 \(dist(y,z)\),加入後會變成 \(dist(y,x)+dist(x,z)\),因此貢獻即為 \(dist(y,x)+dist(x,z)-dist(y,z)\),同理,刪除點的話就變成減去. 這樣我們就把更新答案的複雜度變成 \(\log n\) 了.

做這個題的時候,調 STL 調的很難受,STL 的迭代器確實動不動就 RE

#include<bits/stdc++.h>
using namespace std;
#define int long long
namespace reader{
	template<typename T>
	inline void read(T& x){
		x=0;bool sym=0;char c=getchar();
	    while(!isdigit(c)){sym^=(c=='-');c=getchar();}
	    while(isdigit(c)){x=x*10+c-48;c=getchar();}
	    if(sym)x=-x;
	}
	template<size_t N>
	inline void read(char (&str)[N]){
	    size_t n=0;char c=getchar();
	    while(n<N-1&&!isspace(c)){str[n]=c;c=getchar();++n;}
	    str[n]=0;
	}
	template<typename T,size_t N>
	inline void read(T (&a)[N],int range=N){
		for(int i=1;i<=range;++i){read(a[i]);}
	}
	template<typename T,typename... Args>
	inline void read(T& x,Args&... args){
	    read(x);read(args...);
	}
	template<typename T,typename T2>
	inline void readarray(T& x,T2& args){
		read(x);read(args,x);
	}
	template<typename func,typename... Args>
	inline void readact(int x,function<func>fu,Args&... args){
		for(int i=1;i<=x;++i){
			read(args...);
			fu(args...);
		}
	}
}
using namespace reader;
int n,m;
int fa[100001][17];
int w[100001][17];
struct edge{
	int to,w;
};
int deep[100001],dfn[100001],redfn[100001],dfncnt;
vector<edge>e[100001];
void dfs(int now,int last,int lastw,int nowdeep){
	dfn[now]=++dfncnt;
	redfn[dfncnt]=now;
	deep[now]=nowdeep;
	fa[now][0]=last;
	w[now][0]=lastw;
	for(int i=1;i<=16;++i){
		fa[now][i]=fa[fa[now][i-1]][i-1];
		w[now][i]=w[fa[now][i-1]][i-1]+w[now][i-1];
	}
	for(edge i:e[now]){
		if(i.to!=last){
			dfs(i.to,now,i.w,nowdeep+1);
		}
	}
}
int lca(int x,int y){
//	cout<<"lca "<<x<<" "<<y<<endl;
	if(deep[x]<deep[y]) swap(x,y);
	int res=0;
	for(int i=16;i>=0;--i){
		if(deep[x]-deep[y]>=(1<<i)){
//			cout<<"jump "<<x<<" "<<fa[x][i]<<" "<<w[x][i]<<endl;
			res+=w[x][i];
			x=fa[x][i];
		}
	}
//	cout<<"jd "<<res<<" "<<x<<" "<<y<<endl;
	if(x==y) return res;
	for(int i=16;i>=0;--i){
		if(fa[x][i]!=fa[y][i]){
			res+=w[x][i]+w[y][i];
			x=fa[x][i];
			y=fa[y][i];
		}
	}
	if(x==y) return res;
	return res+w[x][0]+w[y][0];
}
//vector<int>g;
bool vis[100001];
std::set<int> s;
std::set<int>::iterator it;
signed main(){
//	freopen("game1.in","r",stdin);
//	freopen("out.out","w",stdout);
	read(n,m);
	function<void(int,int,int)>f=[](int x,int y,int z){e[x].push_back({y,z});e[y].push_back({x,z});};
	int x,y,z;readact(n-1,f,x,y,z);
	dfs(1,0,0,1);int ans=0;
	while(m--){
		read(x);x=dfn[x];
		if(!vis[redfn[x]]){
			s.insert(x);
//			g.push_back(x);
		}
		int y=redfn[(it=s.lower_bound(x))==s.begin()?(*--s.end()):*(--it)];
		int z=redfn[(it=s.upper_bound(x))==s.end()?*s.begin():*it];
//		sort(g.begin(),g.end(),[](int a,int b){return dfn[a]<dfn[b];});
//		auto ity=lower_bound(g.begin(),g.end(),dfn[x],[](int a,int b){return dfn[a]<dfn[b];});
//		auto itz=upper_bound(g.begin(),g.end(),dfn[x],[](int a,int b){return dfn[a]<dfn[b];});
//		int y=((ity==g.begin())?g.back():*--ity);
//		int z=((itz==g.end())?*g.begin():*itz);
		if(vis[redfn[x]]){
			s.erase(x);
//			g.erase(lower_bound(g.begin(),g.end(),dfn[x]));
		}
		x=redfn[x];
		if(vis[x]){
			vis[x]=false;
			ans-=lca(x,y)+lca(x,z)-lca(y,z);
		}
		else{
			vis[x]=true;
			ans+=lca(x,y)+lca(x,z)-lca(y,z);
		}
		printf("%lld\n",ans);
	}
}

相關文章