洛谷P3783 [SDOI2017]天才黑客【前缀和/后缀和优化建图】

题目描述:

n个点,m条单向边(可能有重边,自环),保证1号点可以到任意点,一棵大小为k的字典树,每条边对应一个字符串(字典树中的某个节点)和一个代价ci。从边u经过点x紧接着走边v会有额外代价|LCP(u对应的字符串,v对应的字符串)|,即u,v在字典树上的对应的点的lca的深度。
求1号点走到2,3…n号点的最小代价。
n , m ≤ 50000 , k ≤ 20000 n,m\le50000,k\le20000 n,m50000,k20000

题目分析:

dalao题解,写得很清晰:
在这里插入图片描述

边化点,拆为入点和出点,原点周围的入边和出边拿出来按字典树上dfs序排序之后利用与后缀数组的height类似的性质变为 h i h_i hi左边所有出点向 h i h_i hi右边所有入点连边(还有右边向左边连边,所以出入点分别要拆2个,入点要向两个出点连边)。

还有另一种做法是将周围一圈边在字典树上的点拿出来建两棵虚树,一棵儿子向父亲连边,另一棵父亲向儿子连边,一个LCA提供的边权就让它在第一棵的儿子向第二棵的兄弟连边(同样可以前缀和后缀和优化,新建儿子个数个点表示前缀和/后缀和,前缀第i个点向前缀第i-1个点和第i个儿子连边,后缀同理),边本身的点作LCA的情况,就让边对应的另一棵树上的点直接连向它或它直接连向另一棵树上的点。

优化建图好题。

Code(第一种做法,注意一下总边数的范围和多测清空):

#include<bits/stdc++.h>
#define maxn 200005
#define maxm 1000005
#define LL long long
using namespace std;
char cb[1<<18],*cs,*ct;
#define getc() (cs==ct&&(ct=(cs=cb)+fread(cb,1,1<<18,stdin),cs==ct)?0:*cs++)
inline void read(int &a){
	char c;while(!isdigit(c=getc()));
	for(a=c-'0';isdigit(c=getc());a=a*10+c-'0');
}
int T,n,m,k;
namespace Dic{
	const int N = 20005, Log = 15;
	int st[N<<1][Log+1],cnt,lg[N<<1]={-1},in[N];
	vector<int>G[N];
	void dfs(int u,int d){
		st[in[u]=++cnt][0]=d;
		for(int v: G[u]) dfs(v,d+1),st[++cnt][0]=d;
	}
	inline void init(){
		for(int i=1;i<=k;i++) G[i].clear(); cnt=0;
		for(int i=1,x,y,z;i<k;i++) read(x),read(y),read(z),G[x].push_back(y);
		dfs(1,0);
		for(int i=1;i<=cnt;i++) lg[i]=lg[i>>1]+1;
		for(int j=1;j<=Log;j++)
			for(int i=1;i+(1<<j)-1<=cnt;i++)
				st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
	}
	inline int lcp(int x,int y){
		if((x=in[x])>(y=in[y])) swap(x,y);
		int k=lg[y-x+1];
		return min(st[x][k],st[y-(1<<k)+1][k]);
	}
}
//in1 m in2 2m out1 3m out2 4m
vector<int>in[maxn],out[maxn],vec;
int a[maxn];
int fir[maxn],nxt[maxm],to[maxm],w[maxm],tot;
inline void line(int x,int y,int z){nxt[++tot]=fir[x],fir[x]=tot,to[tot]=y,w[tot]=z;}
#define ID(x) (x<=m?x:x-2*m)
bool cmp(int i,int j){return Dic::in[a[ID(i)]]<Dic::in[a[ID(j)]];}
typedef pair<LL,int> pli;
LL dis[maxn];
priority_queue<pli,vector<pli>,greater<pli> >q;
int main()
{
	int x,y,z;
	read(T);
	while(T--){
		read(n),read(m),read(k);
		memset(fir,0,(4*m+1)<<2),tot=0;
		for(int i=1;i<=n;i++) in[i].clear(),out[i].clear();
		for(int i=1;i<=m;i++)
			read(x),read(y),read(z),read(a[i]),in[y].push_back(2*m+i),out[x].push_back(i),
			line(i,2*m+i,z),line(i,3*m+i,z),line(m+i,2*m+i,z),line(m+i,3*m+i,z);
		Dic::init();
		for(int u=1;u<=n;u++){
			sort(in[u].begin(),in[u].end(),cmp);
			sort(out[u].begin(),out[u].end(),cmp);
			for(int i=in[u].size()-1;i>0;i--) line(in[u][i-1],in[u][i],0),line(m+in[u][i],m+in[u][i-1],0);
			for(int i=out[u].size()-1;i>0;i--) line(out[u][i-1],out[u][i],0),line(m+out[u][i],m+out[u][i-1],0);
			vec.resize(in[u].size()+out[u].size());
			merge(in[u].begin(),in[u].end(),out[u].begin(),out[u].end(),vec.begin(),cmp);
			for(int t=0,i=0,j=0;t<vec.size()-1;t++){
				if(vec[t]<=m) j++; else i++;
				int w=Dic::lcp(a[ID(vec[t])],a[ID(vec[t+1])]);
				if(i&&j<out[u].size()) line(in[u][i-1],out[u][j],w);
				if(i<in[u].size()&&j) line(m+in[u][i],m+out[u][j-1],w);
			}
		}
		memset(dis,0x3f,(4*m+1)<<3);
		for(int i:out[1]) q.push(pli(dis[i]=0,i));
		while(!q.empty()){
			LL d=q.top().first;int u=q.top().second;q.pop();
			if(dis[u]!=d) continue;
			for(int i=fir[u],v;i;i=nxt[i]) if(dis[v=to[i]]>d+w[i]) q.push(pli(dis[v]=d+w[i],v));
		}
		for(int i=2;i<=n;i++){
			LL ret=1ll<<60;
			for(int j:in[i]) ret=min(ret,dis[j]);
			printf("%lld\n",ret);	
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值