[ZJOI2022] 简单题——圆方树、树链剖分

[ZJOI2022] 简单题

题解

先说是满足环长相等的,然后又任改边权?我寻思这不是没性质了吗?

哦,正整数边权啊,那没事了。

由于原来的边权是正整数,为了使得环长能够通过改边权变得相等,原图的每个点双连通分量一定长成“杨桃”形:
在这里插入图片描述
严格来说,每个点双内最多有两个点 s s s t t t 度数大于2,其它点的度数都等于2(两个点的点双例外),相当于被 s s s t t t 割成很多条链。

此时,点双内任意两点 x , y x,y x,y 间的简单路径数量和长度和是可以快速求得的。
我们先做亿点预处理,求出每个点双内的 s , t s,t s,t、链数 m m m、每条链的长度 l t [ i ] lt[i] lt[i],所有链的长度和 s u m sum sum,以及每个点所在链的标号 n e [ x ] ne[x] ne[x]、每个点走自己那条链到 s s s 的距离 d s [ x ] ds[x] ds[x] 等,然后分情况讨论:

  • x = s , y = t x=s,y=t x=s,y=t x = t , y = s x=t,y=s x=t,y=s
    此时答案为 ( m , s u m ) (m,sum) (m,sum);(前一个是路径数量,后一个是路径长度和)
  • x x x y y y 的其中一个是 s s s t t t,另一个在链上:
    显然共有4种情况,求法都是一样的。我们以 x = s x=s x=s 为例,此时路径要么直接走 y y y 所在链到达 y y y,要么先走另一条链到 t t t,再走背面到 y y y,答案为 ( m , s u m + ( l t [ n e [ y ] ] − d s [ y ] ) × ( m − 2 ) ) (m,sum+(lt[ne[y]]-ds[y])\times(m-2)) (m,sum+(lt[ne[y]]ds[y])×(m2));(这是化简后的结果)
  • x , y x,y x,y 在同一条链上:
    要么在链上直达,要么先走到 s s s t t t,再走另一条链,答案是 ( m , s u m + ( l t [ n e [ x ] ] − ∣ d s [ x ] − d s [ y ] ∣ ) × ( m − 2 ) ) (m,sum+(lt[ne[x]]-|ds[x]-ds[y]|)\times (m-2)) (m,sum+(lt[ne[x]]ds[x]ds[y])×(m2))
  • x , y x,y x,y 不在同一条链上:
    这个时候 x x x 可以走 s s s,也可以走 t t t,而走到 s s s t t t 过后就和第二种情况及其相似了,所以可以写出总共4种情况的答案的和:
    ( 2 m − 2 ,     d s [ x ] + d s [ y ] + ( d s [ x ] + l t [ n e [ y ] ] − d s [ y ] ) × ( m − 2 ) + s u m − l t [ n e [ x ] ] − l t [ n e [ y ] ] + l t [ n e [ x ] ] − d s [ x ] + l t [ n e [ y ] ] − d s [ y ] + ( d s [ y ] + l t [ n e [ x ] ] − d s [ x ] ) × ( m − 2 ) + s u m − l t [ n e [ x ] ] − l t [ n e [ y ] ] ) (2m-2,\,\,\,ds[x]+ds[y]\\ +(ds[x]+lt[ne[y]]-ds[y])\times(m-2)+sum-lt[ne[x]]-lt[ne[y]]\\ +lt[ne[x]]-ds[x]+lt[ne[y]]-ds[y]\\ +(ds[y]+lt[ne[x]]-ds[x])\times(m-2)+sum-lt[ne[x]]-lt[ne[y]]) (2m2,ds[x]+ds[y]+(ds[x]+lt[ne[y]]ds[y])×(m2)+sumlt[ne[x]]lt[ne[y]]+lt[ne[x]]ds[x]+lt[ne[y]]ds[y]+(ds[y]+lt[ne[x]]ds[x])×(m2)+sumlt[ne[x]]lt[ne[y]])化简得到 ( 2 m − 2 , ( l t [ n e [ x ] ] + l t [ n e [ y ] ] ) × ( m − 3 ) + 2 s u m ) (2m-2,(lt[ne[x]]+lt[ne[y]])\times(m-3)+2sum) (2m2,(lt[ne[x]]+lt[ne[y]])×(m3)+2sum)

适当使用unordered_map,预处理可以做到 O ( n ) O(n) O(n),查询 O ( 1 ) O(1) O(1)

然后对于不在一个点双内的点,我们可以建出广义圆方树,令圆点的点权为到它爷爷节点(同在父亲节点代表的点双内)的路径数和长度和,那么两点间的答案可由圆方树上路径上的圆点点权合并而来,只不过需要再特殊处理一下LCA。

用重链剖分可以比较省空间地做到 O ( log ⁡ n ) O(\log n) O(logn) 查询,只需要每个节点记录到链顶的答案的合并,然后对于中间的小段则做个逆变换除掉即可。

总复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn)O(n) 部分的常数比 log 大

代码

#include<bits/stdc++.h>//JZM yyds!!
#define ll long long//God JZM!!
#define lll __int128//JZM RollInDark!!
#define uns unsigned
#define fi first
#define se second
#define IF (it->fi)
#define IS (it->se)
#define lowbit(x) ((x)&-(x))
#define END putchar('\n')
#define inline jzmyyds
#pragma GCC optimize("Ofast")
using namespace std;
const int MAXN=2e6+6;
const ll INF=1e18;
ll read(){
	ll x=0;bool f=1;char s=getchar();
	while((s<'0'||s>'9')&&s>0){if(s=='-')f^=1;s=getchar();}
	while(s>='0'&&s<='9')x=(x<<1)+(x<<3)+(s^48),s=getchar();
	return f?x:-x;
}
int ptf[50],lpt;
void print(ll x,char c='\n'){
	if(x<0)putchar('-'),x=-x;
	ptf[lpt=1]=x%10;
	while(x>9)x/=10,ptf[++lpt]=x%10;
	while(lpt)putchar(ptf[lpt--]^48);
	if(c>0)putchar(c);
}

const ll MOD=998244353;
ll ksm(ll a,ll b,ll mo){
	ll res=1;if(a==1)return 1;
	for(;b;b>>=1,a=a*a%mo)if(b&1)res=res*a%mo;
	return res;
}
#define pii pair<int,int>
struct edge{
	int v,to,w;edge(){}
	edge(int V,int T,int W){v=V,to=T,w=W;}
}e[MAXN];
int EN,G[MAXN];
void addedge(int u,int v,int w){
	e[++EN]=edge(v,G[u],w),G[u]=EN;
	e[++EN]=edge(u,G[v],w),G[v]=EN;
}
pii del[MAXN];
int n,m,Q,dfn[MAXN],low[MAXN],TN,tot;
vector<int>D[MAXN];
void adge(int u,int v){
	D[u].push_back(v),D[v].push_back(u);
}

#define pll pair<ll,ll>
pll operator*(const pll&a,const pll&b){
	return pll(a.fi*b.fi%MOD,(a.fi*b.se+a.se*b.fi)%MOD);
}
pll operator/(const pll&a,const pll&b){
	ll iv=ksm(b.fi,MOD-2,MOD),f=a.fi*iv%MOD;
	return pll(f,(a.se-f*b.se%MOD+MOD)*iv%MOD);
}

struct{//点双
	int s,t,siz,m;ll sum;
	vector<int>p,ne;
	unordered_map<int,int>id;
	unordered_map<int,vector<pii> >G;
	vector<ll>ds,lt;
	void insp(int x){p.push_back(x),id[x]=233,siz++;}
	void inse(int u,int v,int w){
		if(u==v||!id.count(u)||!id.count(v))return;
		G[u].emplace_back(pii(v,w));
		G[v].emplace_back(pii(u,w));
	}
	void dfs(int x,int fa,int fd,int w){
		if(x==t){
			lt.emplace_back(ds[fd]+w);
			sum+=ds[fd]+w,m++;return;
		}int d=id[x];
		ne[d]=m,ds[d]=ds[fd]+w;
		for(auto&v:G[x])if(v.fi^fa)dfs(v.fi,x,d,v.se);
	}
	void build(){
		ne.resize(siz),ds.resize(siz);
		int mx=0;
		for(int i=0,x;i<siz;i++)
			x=p[i],id[x]=i,ne[i]=G[x].size(),mx=max(mx,ne[i]);
		s=t=m=0;
		for(int i=0;i<siz&&!t;i++)if(ne[i]==mx){
			if(!s)s=p[i];
			else t=p[i];
		}dfs(s,s,id[s],0);
	}
	pll cont(int x,int y){
		if(x==y||!id.count(x)||!id.count(y))return pll(1,0);
		int u=id[x],v=id[y];
		if(u>v)swap(x,y),swap(u,v);
		if(x==s){
			if(y==t)return pll(m,sum%MOD);
			return pll(m,(sum+(lt[ne[v]]-ds[v])%MOD*(m+MOD-2))%MOD);
		}
		if(x==t)return pll(m,(sum+ds[v]%MOD*(m+MOD-2))%MOD);
		if(y==s)return pll(m,(sum+(lt[ne[u]]-ds[u])%MOD*(m+MOD-2))%MOD);
		if(y==t)return pll(m,(sum+ds[u]%MOD*(m+MOD-2))%MOD);
		if(ne[u]==ne[v]){
			ll d=abs(ds[u]-ds[v]);
			return pll(m,(sum+(lt[ne[u]]-d)%MOD*(m+MOD-2))%MOD);
		}
		return pll((m-1)<<1,((lt[ne[u]]+lt[ne[v]])%MOD*(m+MOD-3)+(sum<<1))%MOD);
	}
}sc[MAXN];

int sk[MAXN],le,bl[MAXN];
void tarjan(int x,int fa){
	dfn[x]=low[x]=++TN,sk[++le]=x;
	for(int i=G[x],v;i;i=e[i].to){
		if((v=e[i].v)==fa)continue;
		if(dfn[v])low[x]=min(low[x],dfn[v]);
		else{
			tarjan(v,x),low[x]=min(low[x],low[v]);
			if(low[v]>=dfn[x]){
				tot++,sc[tot].insp(x),adge(x,n+tot);
				while(le>0){
					sc[tot].insp(sk[le]),bl[sk[le]]=tot,adge(sk[le--],n+tot);
					if(sk[le+1]==v)break;
				}
			}
		}
	}
}

int fa[MAXN],siz[MAXN],hs[MAXN],dep[MAXN],tp[MAXN];
pll a[MAXN],f[MAXN];
void dfs1(int x){
	siz[x]=1,hs[x]=0,a[x]=pll(1,0),dep[x]=dep[fa[x]]+1;
	for(int v:D[x])if(v^fa[x]){
		fa[v]=x,dfs1(v),siz[x]+=siz[v];
		if(siz[v]>siz[hs[x]])hs[x]=v;
	}
	if(x>n)for(int v:D[x])if(v^fa[x])a[v]=sc[x-n].cont(v,fa[x]);
}
void dfs2(int x){
	tp[x]=(x==hs[fa[x]]?tp[fa[x]]:x),f[x]=a[x];
	if(tp[x]^x)f[x]=f[x]*f[fa[x]];
	if(hs[x])dfs2(hs[x]);
	for(int v:D[x])if((v^fa[x])&&(v^hs[x]))dfs2(v);
}
ll query(int u,int v){
	int x=u,y=v;
	pll res=pll(1,0);
	while(tp[u]^tp[v]){
		if(dep[tp[u]]<dep[tp[v]])swap(u,v),swap(x,y);
		res=res*f[u],x=tp[u],u=fa[tp[u]];
	}if(dep[u]>dep[v])swap(u,v),swap(x,y);
	if(v^u)y=hs[u],res=res*f[v]/f[u];
	if(u>n)res=res*sc[u-n].cont(x,y)/a[x]/a[y];
	return res.se;
}
int main()
{
	freopen("simple.in","r",stdin);
	freopen("simple.out","w",stdout);
	n=read(),m=read(),Q=read();
	for(int i=1,u,v;i<=m;i++)
		u=read(),v=read(),addedge(u,v,read());
	tarjan(1,0);
	for(int i=1;i<=EN;i+=2){
		int u=e[i].v,v=e[i+1].v,w=e[i].w;
		if(bl[u]>0)sc[bl[u]].inse(u,v,w);
		if(bl[v]>0&&(bl[u]^bl[v]))sc[bl[v]].inse(u,v,w);
	}
	for(int i=1;i<=tot;i++)sc[i].build();
	dfs1(1),dfs2(1);
	while(Q--)print(query(read(),read()));
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值