P5680 [GZOI2017] 共享单车 题解

P5680 [GZOI2017] 共享单车

题意:

(真的是非常难懂啊)
一张带权双向连通图和源点 k k k,画出它的最短路径树。树上每个点颜色初始为 0 0 0,有两种操作: 0 0 0 操作是把部分点的颜色取反,$ 1$ 操作是根据给定点和根节点(也就是前面的源点),建虚树,问在虚树上使得颜色为 1 1 1 的点与 K K K 不连通的所需的最小代价。
N ≤ 50000 N\le 50000 N50000 M ≤ 100000 M\le 100000 M100000 Q ≤ 1500 Q\le 1500 Q1500 n u m ≤ 500 num\le 500 num500
道路无自环,所有道路长度小于 2000 2000 2000,且区域 K K K 任意时刻均非投放区域。

思路:

跟着题意一步步做就好啦。
首先跑最短路同时存下每个点由哪个点跑到,以及跑向它的那条边边权。注意这里如果遇到跑向它的两个点路径长度相同的情况,要取最小的节点编号。(这里不用重复入队。)
接着我们每次查询建虚树,在虚树上 D P \text DP DP。注意这里虚点不仅有输入的点还有根节点 K K K D P \text DP DP的过程类似消耗战这道题。按是否为标记点分类讨论(不是虚点哦!因为题目说的是阻断标记点。),如果是标记点,则必须断掉这条边,也就是 f u = ∑ ( d i s v − d i s u ) f_u=\sum (dis_v-dis_u) fu=(disvdisu)。如果不是标记点,那可以选择断掉其与父亲节点的连边也可以把子树内的关键点都和它断掉,即 f u = ∑ min ⁡ ( f v , d i s v − d i s u ) f_u=\sum\min (f_v,dis_v-dis_u) fu=min(fv,disvdisu)

code:

#include
using namespace std;
#define ll long long
#define mms(a2,b2) memset(a2,b2,sizeof(a2))
//回收--关键点。 投放 DP
const int N=1e5+5;
const int M=2e5+5;
const int inf=0x3f3f3f3f;
struct node{
	int v,nxt;
	int w;
}tu[M],e[M<<1],e2[M<<1];
int tott,tot,tot2;
int n,m,K,Q;
int flag=0;
int ht[N],h[N],h2[N];
int dis[N],pre[N],jw[M];
int sp[M],xu[M],fl[M];
int dep[N],fa[N][22],dfn[N],cnt,dw[N];
int a[N];
int f[N];
int read(){	
	int x=0,ff=1;
	char c=getchar();
	while(c<'0'||c>'9') 	c=getchar();
	while(c>='0'&&c<='9'){
		x=(x<<1)+(x<<3)+(c^48);	c=getchar();
	}
	return x;
}
void addt(int x,int y,int z){	tu[++tott].nxt=ht[x];tu[tott].v=y;tu[tott].w=z;ht[x]=tott; }
void add(int x,int y,int z){	e[++tot].nxt=h[x];	e[tot].v=y; e[tot].w=z;	h[x]=tot;}
void add2(int x,int y,int z){	e2[++tot2].nxt=h2[x];	e2[tot2].v=y;e2[tot2].w=z;h2[x]=tot2;}
void build(){
	mms(dis,-1);
	priority_queue<pair<int,int> > q;
	q.push(make_pair(0,K));
	dis[K]=0;
	while(!q.empty()){
		int u=q.top().second;
		q.pop();
		for(int i=ht[u];~i;i=tu[i].nxt){
			int v=tu[i].v,w=tu[i].w;
			if(dis[v]==-1||dis[v]>dis[u]+w){
				dis[v]=dis[u]+w;
				pre[v]=u;
				jw[v]=w;
				q.push(make_pair(-dis[v],v));
			}else if(dis[u]+w==dis[v]&&pre[v]>u){
				pre[v]=u;
				jw[v]=w;
			}
		}
	}
	for(int i=1;i<=n;i++){
		if(i!=K){
			//cout<
			add(i,pre[i],jw[i]);
			add(pre[i],i,jw[i]);
		}
	}
}
void dfs(int u,int u_fa,int ww){
	fa[u][0]=u_fa;
	dep[u]=dep[u_fa]+1;
	dfn[u]=++cnt;
	dw[u]=dw[u_fa]+ww;
	for(int i=h[u];~i;i=e[i].nxt){
		int v=e[i].v,w=e[i].w;
		if(v==u_fa) continue;
		dfs(v,u,w);
	}
}
void fa_fa(){
	for(int i=1;i<=20;i++)
		for(int j=1;j<=n;j++)
			fa[j][i]=fa[fa[j][i-1]][i-1];
}
bool cmp(int x,int y){
	return dfn[x]<dfn[y];
}
int lca(int x,int y){
	if(dep[x]<dep[y]) swap(x,y);
	for(int i=19;i>=0;i--){
		if(dep[fa[x][i]]>=dep[y]) x=fa[x][i];
	}
	if(x==y) return x;
	for(int i=19;i>=0;i--){
		if(fa[x][i]!=fa[y][i]){
			x=fa[x][i],y=fa[y][i];
		}
	}
	return fa[x][0];
}
int solve(int u,int u_fa){
	int ans=0;
	for(int i=h2[u];~i;i=e2[i].nxt){
		int v=e2[i].v;
		if(v==u_fa) continue;
		int tt=solve(v,u);
		if(a[v]) flag=1,ans+=dw[v]-dw[u];
		else ans+=min(tt,dw[v]-dw[u]);
	//	cout<
	}	
//	cout<
	h2[u]=-1;
//	fl[u]=0;
	return ans;
}
int main() {
	mms(ht,-1),mms(h,-1),mms(h2,-1);
	n=read(),m=read(),K=read(),Q=read();
	for(int i=1;i<=m;i++){
		int x,y,z;
		scanf("%d%d%d",&x,&y,&z);
		addt(x,y,z),addt(y,x,z);
	}
	build();
	dfs(K,0,0);
	fa_fa();
	int t,le;
	while(Q--){
		scanf("%d",&t);
		if(t==0){
			scanf("%d",&le);
			for(int i=1;i<=le;i++){
				int x;
				scanf("%d",&x);
				if(a[x]==0) a[x]=1;
				else a[x]=0;
			}
		}else{
			scanf("%d",&le);
			for(int i=1;i<=le;i++){
				scanf("%d",&sp[i]);
			//	fl[sp[i]]=1;
			}	
			sp[++le]=K;
			flag=0;
		//	fl[K]=1;
			sort(sp+1,sp+1+le,cmp);
			int len=0;
			for(int i=1;i<le;i++){
				xu[++len]=sp[i];
				xu[++len]=lca(sp[i],sp[i+1]);
			}
			xu[++len]=sp[le];
			sort(xu+1,xu+1+len,cmp);
			len=unique(xu+1,xu+1+len)-xu-1;
			for(int i=1;i<len;i++){
				int lc=lca(xu[i],xu[i+1]);
				add2(lc,xu[i+1],dw[xu[i+1]]-dw[lc]);
				add2(xu[i+1],lc,dw[xu[i+1]]-dw[lc]);
			}
			int anss=solve(K,0);
			if(flag)
			printf("%d\n",anss);
			else printf("-1\n");
		}
	}
	return 0;

你可能感兴趣的:(题解,图论,c++,算法,学习)