洛谷 P3783 - [SDOI2017]天才黑客(前后缀优化建图)

题面传送门

神仙题一道。

首先注意到这里的贡献涉及到边的顺序,并且只与相邻的边是什么有关,因此不难想到一个做法——边转点,点转边,具体来说对于每条边 \(e\),我们将其拆成两个点 \(in_e,out_e\),并连边 \(in_e\to out_e\),权值为 \(c_e\),同时对于所有 \(b_i=a_j\) 的边 \(i,j\),连边 \(out_i\to in_j\),权值为 \(dep[\text{LCA}(d_i,d_j)]\),以及对于所有 \(a_i=1\) 的边连 \(S\to in_i\),权值为 \(0\) 的边,跑最短路,最后 \(ans_i=\min\limits_{b_e=i}dis_{out_e}\)。正确性显然。

然而该做法效率低下,边数最高可达到 \(\mathcal O(m^2)\),一脸过不去的亚子。于是又到了优化建图的时间了,一个 observation 是对于一个点 \(u\) 而言,我们在 \(b_i=a_j=u\) 的边 \(i,j\) 之间连边,这些边边权的种类是不会太多的,因为假设 \(S=\{d_i|b_i=u\lor a_i=u\}\),那么这些边权肯定只能\(S\) 当中某两个点的 \(\text{LCA}\) 的深度,根据虚树那一套理论,\(S\) 当中任意两点的 \(\text{LCA}\) 总共只有 \(\mathcal O(|S|)\) 种可能。而对于所有点 \(u\),它们对应 \(|S|\) 的总和为 \(\mathcal O(m)\) 级别,这个是在我们能接受的范围内的。因此考虑从这个性质入手优化这道题,我们将 \(S\) 当中的点按 DFS 序排个序,那么根据虚树中一个定理:对于待建立虚树的 \(m\) 个点 \(a_1,a_2,\cdots,a_m\),假设将其按照 DFS 序排序得到 \(b_1,b_2,\cdots,b_m\),那么记 \(h_i=dep[\text{LCA}(a_i,a_{i+1})]\),有 \(dep[\text{LCA}(b_l,b_r)]=\min\limits_{i=l}^{r-1}h_i\)(和 SA 有点像),因此考虑按照套路求出 \(h_i\),那么问题可以转化为:

  • 有一排 \(t\) 个点,每个点有两种类型 \(0/1\),每两个点中间写有一个数 \(h_i\),我们要从所有 \(0\) 点向 \(1\) 点连边,权值为它们之间数的最小值。

这个有一个显然的做法——对于某个 \(h_x\),满足 \(\min\limits_{i=l}^{r-1}h_i=h_x\)\(l,r\) 肯定分别在一段区间内,单调栈求出对应的区间,线段树优化建图即可,但是这个做法多一个 \(\log\),不知道能不能通过,而且实现起来过于麻烦,因此我们还需考虑更简便做法。考虑最短路的一个性质,就是对于两点间如果有多条边的情况,那么显然只有权值最小的边是有用的,其他边可连可不连——连了也不影响你最短路的大小。因此考虑对于某个 \(h_i\),我们不维护什么单调栈,直接从 \([1,i]\) 中的 \(0\) 点向 \([i+1,t]\) 中的 \(1\) 点连边,\([i+1,t]\) 中的 \(0\) 权点向 \([1,i]\) 中的 \(1\) 点连边,根据上面的结论,对于某个 \(l,r\),只有 \([l,r)\)\(h\) 的最小值(设为 \(h_x\))是有用的,其他边连上也没事,而显然在考虑 \(x\) 的贡献时,已经连了 \(l,r\) 之间的边,因此最短路跑出来依然是对的。考虑优化,注意到每次都是某个前缀向后缀,或者后缀向前缀连边,因此考虑前后缀优化建图,具体来说建立四排点——前缀 in,前缀 out,后缀 in,后缀 out,然后随便瞎连连即可,具体见代码。

时间复杂度 \(\mathcal O(Tm\log m)\),然鹅常数非常大,直接上天……

const int MAXN=5e4;
const int MAXK=2e4;
const int LOG_N=16;
const int MAXV=6e5+1;
const int MAXE=1e7;
int n,m,k,S=6e5+1,ncnt;
int hd[MAXV+5],to[MAXE+5],nxt[MAXE+5],val[MAXE+5],ec=0;
void adde(int u,int v,int w){
//	printf("%d %d %d\n",u,v,w);
	to[++ec]=v;val[ec]=w;nxt[ec]=hd[u];hd[u]=ec;
}
struct edge{int a,b,c,d;} e[MAXN+5];
vector<int> in[MAXN+5],out[MAXN+5],g[MAXK+5];
int dfn[MAXK+5],fa[MAXK+5][LOG_N+2],dep[MAXK+5],tim=0;
void dfs(int x=1,int f=0){
	dfn[x]=++tim;fa[x][0]=f;
	for(int y:g[x]) dep[y]=dep[x]+1,dfs(y,x);
}
int getlca(int x,int y){
	if(dep[x]<dep[y]) x^=y^=x^=y;
	for(int i=LOG_N;~i;i--) if(dep[x]-(1<<i)>=dep[y]) x=fa[x][i];
	if(x==y) return x;
	for(int i=LOG_N;~i;i--) if(fa[x][i]^fa[y][i]) x=fa[x][i],y=fa[y][i];
	return fa[x][0];
}
bool cmp(pii x,pii y){return dfn[x.fi]<dfn[y.fi];}
ll dis[MAXV+5],ans[MAXN+5];
void clear(){
	S=6e5+1;ncnt=0;memset(hd,0,sizeof(hd));ec=0;
	memset(dfn,0,sizeof(dfn));memset(fa,0,sizeof(fa));
	memset(dep,0,sizeof(dep));tim=0;
	memset(dis,63,sizeof(dis));memset(ans,63,sizeof(ans));
	for(int i=1;i<=MAXN;i++) in[i].clear(),out[i].clear();
	for(int i=1;i<=MAXK;i++) g[i].clear();
}
void solve(){
	scanf("%d%d%d",&n,&m,&k);clear();ncnt=m<<1;
	for(int i=1;i<=m;i++){
		scanf("%d%d%d%d",&e[i].a,&e[i].b,&e[i].c,&e[i].d);
		out[e[i].a].pb(i);in[e[i].b].pb(i);
	}
	for(int i=1,u,v;i<k;i++) scanf("%d%d%*d",&u,&v),g[u].pb(v);dfs(1,0);
	for(int i=1;i<=LOG_N;i++) for(int j=1;j<=k;j++) fa[j][i]=fa[fa[j][i-1]][i-1];
	for(int i=1;i<=m;i++) adde(i,i+m,e[i].c);
	for(int i=1;i<=m;i++) if(e[i].a==1) adde(S,i,0);
	for(int i=1;i<=n;i++){
		vector<pii> ed;
		for(int x:in[i]) ed.pb(mp(e[x].d,x+m));
		for(int x:out[i]) ed.pb(mp(e[x].d,x));
		sort(ed.begin(),ed.end(),cmp);
		vector<int> h(ed.size(),0);
		vector<int> pre_in(ed.size(),0),pre_out(ed.size(),0);
		vector<int> suf_in(ed.size(),0),suf_out(ed.size(),0);
		for(int j=0;j+1<ed.size();j++) h[j]=dep[getlca(ed[j].fi,ed[j+1].fi)];
//		printf("node %d\n",i);
//		for(int j=0;j<ed.size();j++) printf("pts %d %d\n",ed[j].fi,ed[j].se);
//		for(int j=0;j+1<ed.size();j++) printf("%d\n",h[j]);
		for(int j=0;j<ed.size();j++) pre_in[j]=++ncnt;
		for(int j=0;j<ed.size();j++) pre_out[j]=++ncnt;
		for(int j=0;j<ed.size();j++) suf_in[j]=++ncnt;
		for(int j=0;j<ed.size();j++) suf_out[j]=++ncnt;
		for(int j=0;j+1<ed.size();j++) adde(pre_in[j+1],pre_in[j],0);
		for(int j=0;j+1<ed.size();j++) adde(pre_out[j],pre_out[j+1],0);
		for(int j=0;j+1<ed.size();j++) adde(suf_in[j],suf_in[j+1],0);
		for(int j=0;j+1<ed.size();j++) adde(suf_out[j+1],suf_out[j],0);
		for(int j=0;j<ed.size();j++){
			if(ed[j].se<=m) adde(suf_in[j],ed[j].se,0),adde(pre_in[j],ed[j].se,0);
			else adde(ed[j].se,suf_out[j],0),adde(ed[j].se,pre_out[j],0);
		}
		for(int j=0;j+1<ed.size();j++){
			adde(suf_out[j+1],pre_in[j],h[j]);
			adde(pre_out[j],suf_in[j+1],h[j]);
		}
	} memset(dis,63,sizeof(dis));memset(ans,63,sizeof(ans));
	priority_queue<pii,vector<pii>,greater<pii> > q;
	q.push(mp(dis[S]=0,S));
	while(!q.empty()){
		pii p=q.top();q.pop();
		int sum=p.fi,x=p.se;
		if(dis[x]<sum) continue;
		for(int e=hd[x];e;e=nxt[e]){
			int y=to[e],z=val[e];
			if(dis[y]>dis[x]+z) q.push(mp(dis[y]=dis[x]+z,y));
		}
	}
	for(int i=1;i<=m;i++) chkmin(ans[e[i].b],dis[i+m]);
	for(int i=2;i<=n;i++) printf("%lld\n",ans[i]);
}
int main(){int qu;scanf("%d",&qu);while(qu--) solve();return 0;}
posted @ 2021-07-02 17:22  tzc_wk  阅读(80)  评论(0编辑  收藏  举报