拿出切这道题的觉悟来!准备好草稿纸分类讨论环上情况(我没搭图床)

按照代码执行顺序看吧,我啃的地方都写出来了.

代码承袭自\(uoj\)\(StormySea\)大佬

//这道题环的遍历顺序尤为重要 
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
char buf[1<<23],*p1=buf,*p2=buf;
#define getChar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<22,stdin),p1==p2)?EOF:*p1++)
const int N=600600;
inline int read(){
	int x=0,f=1;
	char ch=getChar();
	while(ch<'0'||ch>'9'){
		if(ch=='-')f=-1;
		ch=getChar();
	}
	while('0'<=ch&&ch<='9')x=(x<<3)+(x<<1)+(ch^48),ch=getChar();
	return x*f;
}
int n,m,q;
int dfn[N],low[N],Index,fa[N][22],tot,dep[N],Len[N],Rank[N];//用rank会CE 
vector<int>mp[N],edge[N];
int pool[N<<2],*curpos=pool;//不空间池处理会mle 
struct Bit{//树状数组(修改操作)
	int size;
	int *c;
	void New(int x){
		c=curpos;
		size=x;
		curpos+=size;
	}
	#define lowbit(i) i&(-i)
	void add(int x,int val){
		for(int i=x;i<=size;i+=lowbit(i))c[i]+=val;
	}
	int query(int x){
		int ret=0;
		for(int i=x;i;i-=lowbit(i))ret+=c[i];
		return ret;
	}
	void update(int l,int r,int val){
		add(l,val);
		add(r+1,-val);//边权差分后区间修改 
	}
}f,g,h,ring[N];
//f表示root到i最短路和最长路的并集
//g表示root到i的最短路(新司机)
//h表示root到i的最长路(老司机)
//ring维护环上从顶端到底端的前缀和 

//一条边的贡献:最长&最短,最长,最短,未经过,上述结构体可以导出这些值 
void tarjan(int u){
	dfn[u]=low[u]=++Index;
	int v;
	for(int i=0;i<edge[u].size();++i){
		v=edge[u][i];
		if(v==fa[u][0])continue;
		if(!dfn[v]){
			fa[v][0]=u;
			dep[v]=dep[u]+1;//仙人掌上dfs树深度 
			tarjan(v);
			low[u]=min(low[u],low[v]);
			if(dfn[u]<low[v]){
				mp[u].push_back(v);
			}
		}
		else low[u]=min(low[u],dfn[v]);
	}	
	for(int i=0;i<edge[u].size();++i){
		v=edge[u][i];
		if(fa[v][0]!=u&&dfn[v]>dfn[u]){
			++tot;
			mp[u].push_back(tot);
			Rank[u]=0;
			Len[tot]=dep[v]-dep[u]+1;//环长 
//			Len[tot]和u对应的都是环顶点,便于环上边权和处理 
			for(int j=v;j!=u;j=fa[j][0]){
				Rank[j]=dep[j]-dep[u];//给环上点排名 
				mp[tot].push_back(j);
			}
			reverse(mp[tot].begin(),mp[tot].end());//按排名从小到大遍历环 
			ring[tot].New(Len[tot]+3);//开空间,tot加大了可以被卡 
		}
	}
}
int l[N],r[N],num;//dfs序下子树区间 
void dfs(int u,int Fa){
	fa[u][0]=Fa;
	l[u]=++num;//l[u]表示u这个点的dfs序,环上处理作环顶点时不受本环上边权影响 
	for(int v,i=0;i<mp[u].size();++i){
		v=mp[u][i];
		dep[v]=dep[u]+1;//注意这里dep重新定义为圆方树上深度 
		dfs(v,u);
	}
	r[u]=num;
}
inline int getlca(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(dep[x]==dep[y])break;
	}
	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];
}
inline void update(int u,int v,int w){
	if(dep[u]<dep[v])swap(u,v);//先让u的深度大
	if(dep[u]==dep[v]+1){//树边 
		f.update(l[u],r[u],w);
		g.update(l[u],r[u],w);
		h.update(l[u],r[u],w);
	} 
	else if(dep[u]==dep[v]){//u,v不是环顶点,(u,v)是环上边 
		int sq=fa[u][0];//方点 square 
		int mid=mp[sq][Len[sq]>>1];//环上最中间的一条边Rank大的那个点
		if(Rank[u]>Rank[v])
			swap(u,v);//钦定u是边上Rank小的点 
		f.update(l[sq]+1,r[sq],w);//环顶点到环上圆点有两条路,所以所有点f+w
		ring[sq].add(Rank[v],w);//环上前缀和 
		if((Rank[u]<<1)<Len[sq]){
			h.update(l[sq]+1,r[u],w);//环顶点到u的点的最长路 
			g.update(l[v],l[mid]-1,w);//v到mid以前的点的最短路 
			h.update(l[mid],r[sq],w);//mid到环末端的点的最长路 
		}
		else{
			h.update(l[sq]+1,l[mid]-1,w);//环顶点到mid以前的点的最长路 
			g.update(l[mid],r[u],w);//mid到u的点的最短路 
			h.update(l[v],r[sq],w);//v到环末端的点的最长路 
		}
	}
	else{//v是环上顶点,u是环上紧挨v的点 
		int sq=fa[u][0];//园方树上:fa[sq]=v
		int mid=mp[sq][Len[sq]>>1],flag=(Rank[u]==1);
		f.update(l[sq]+1,r[sq],w);
		ring[sq].add(flag?1:Len[sq],w);//更新从第一个点或从最后一个点 
		//发现两种情况可以合并 
		(flag?g:h).update(l[sq]+1,l[mid]-1,w);
		(flag?h:g).update(l[mid],r[sq],w);//和上面类似,画图吧 
	}
}//考虑分析一条边被哪些情况包括 
int U[N],V[N],W[N];
int k,size,x[N],y[N],type[N],a[N<<1];//最坏开双倍点 
int sum[N][2];//处理树上差分 
int px[N],py[N];//px,py记录一个方点上待询问的圆点
vector<int>Q[N]; 
inline void add_tag(int id,int x,int y,int t){
	++sum[x][t],++sum[y][t];
	a[++size]=x,a[++size]=y;
	if(dep[x]<dep[y])swap(x,y);
	int lca;
	for(int i=19;i>=0;--i) {
		if(dep[fa[x][i]]>=dep[y])x=fa[x][i];
		if(dep[x]==dep[y])break;
	}
	if(x==y)lca=x;
	else{
		for(int i=19;i>=0;--i)
			if(fa[x][i]!=fa[y][i])x=fa[x][i],y=fa[y][i];
		lca=fa[x][0];
	}
	if(lca<=n){ 
		px[id]=py[id]=lca;
	}
	else{
		px[id]=x,py[id]=y;
		a[++size]=x,a[++size]=y; 
		Q[lca].push_back(id);
	}
	--sum[px[id]][t],--sum[py[id]][t];
	//如果是lca是方点,lca处的点双留着后面讨论 
}
inline bool cmp(int aa,int bb){
	return l[aa]<l[bb];
}
int s[N],top;
inline int getson(int x,int ed){
	for(int i=19;i>=0;--i){
		if(dep[fa[x][i]]>dep[ed])x=fa[x][i];
		if(dep[x]==dep[ed]+1)break;
	}
	return x;
}
int fir[N],cnt,sz[N];//sz记录方点子树个数,包含了上传到i祖先和就在i所在环处理的两种情况 
struct node{
	int nxt,to;
}e[N<<1];
void add(int u,int v){
	e[++cnt].nxt=fir[u];fir[u]=cnt;e[cnt].to=v;
}
inline int solve_circle(int u){
	int ret=0;
	for(int v,i=fir[u];i;i=e[i].nxt){
		v=e[i].to;
		for(int t=0;t<2;++t)sum[u][t]+=sum[v][t];
		if(sum[v][0]&&sum[v][1]){
			ret+=f.query(l[v])-f.query(l[u]);//查询(u,v)的路径 
//			dis(v,u)=dis(root,v)-dis(root,u) 
		}
		else if(sum[v][0]){
			ret+=g.query(l[v])-g.query(l[u]);
		}
		else if(sum[v][1]){
			ret+=h.query(l[v])-h.query(l[u]);
		}
	}
	return ret;
}
int S[N],Num,p[N];//s统计环上差分 
inline int solve_square(int u){
	int Cnt=sz[u];
	Num=0;
	for(int i=0;i<=Cnt;++i)S[i]=0;
	for(int v,i=fir[u];i;i=e[i].nxt){//从Rank大的访问起 
		v=e[i].to;
		for(int t=0;t<2;++t)sum[u][t]+=sum[v][t];//标记上传 
		if(sum[v][0]&&sum[v][1])++S[0];
		else if(sum[v][0]){
			(Rank[v]<<1)>Len[u]?++S[Cnt-Num]:(++S[0],--S[Cnt-Num]);
			//考虑环上走哪边到u 
			//因为v的Rank单降,前面有的点对应边可通过差分判断选不选,所以这里Cnt-Num(对应环上准确位置)
		}
		else if(sum[v][1]){
			(Rank[v]<<1)<Len[u]?++S[Cnt-Num]:(++S[0],--S[Cnt-Num]);
		}
		p[++Num]=Rank[v];
	}
	reverse(p+1,p+Cnt+1);//Num==Cnt
	p[Cnt+1]=Len[u];
	//1、要上传到u祖先的标记 
	for(int i=0;i<Q[u].size();++i){
		int x=px[Q[u][i]],y=py[Q[u][i]],l,r;
		if(Rank[x]>Rank[y])swap(x,y);//钦定x的Rank更小 
		l=lower_bound(p+1,p+Cnt+1,Rank[x])-p;
		r=lower_bound(p+1,p+Cnt+1,Rank[y])-p;
		if(type[Q[u][i]]!=(((Rank[y]-Rank[x])<<1)<Len[u])){
			++S[l],--S[r];//在[l,r]处理 
		}
		else{
			++S[0],--S[l],++S[r];//在环顶端两边处理 
		}
		//0 1分类讨论环上最短/长路走法 
	}//2、只在这个环处理 
	Q[u].clear();//即时清空 
	int ret=0;
	for(int i=0;i<=Cnt;++i){
		S[i+1]+=S[i];
		if(S[i]){
			ret+=ring[u].query(p[i+1])-ring[u].query(p[i]);//这一条链要选 
		}
	}
	return ret;
}
inline int query(){
	if(k==0)return 0;
	size=top=0;
	for(int i=1;i<=k;++i){
		add_tag(i,x[i],y[i],type[i]);
	}
	sort(a+1,a+size+1,cmp);
	size=unique(a+1,a+size+1)-a-1;
	int tmp=size;
	for(int i=1;i<tmp;++i){
		a[++size]=getlca(a[i],a[i+1]);
	}
	sort(a+1,a+size+1,cmp);
	size=unique(a+1,a+size+1)-a-1;
	s[++top]=a[1];
	tmp=size;
	for(int i=2;i<=tmp;++i){
		int u=a[i],v;
		while(top&&l[u]>r[s[top]])--top;
		v=s[top];
		if(v>n&&dep[v]+1<dep[u]){
			int w=getson(u,v);
			add(v,w),add(w,u);
			s[++top]=a[++size]=w;
			++sz[v],++sz[w];
		}
		else ++sz[v],add(v,u);
		s[++top]=u;
		//改成邻接表建图,每个环会先访问Rank大的点(自己分析) 
	}	
	inplace_merge(a+1,a+tmp+1,a+size+1,cmp);//新加入点排序方式相同(dfs序升序) 
	size=unique(a+1,a+size+1)-a-1;
	//虚圆方树建立,避免方方边
	int ret=0;
	for(int i=size;i>=1;--i){//从后往前遍历完 
		if(a[i]<=n){
			ret+=solve_circle(a[i]);//圆点 
		}
		else ret+=solve_square(a[i]);//方点 
	}
	//clear
	cnt=0;
	for(int i=1;i<=size;++i){
		int v=a[i];
		sz[v]=sum[v][0]=sum[v][1]=fir[v]=0;
	}
	return ret;
}
int main(){
//	freopen("ex_train3.in","r",stdin);
	n=read(),m=read(),q=read();
	tot=n;
	int u,v,w;
	for(int i=1;i<=m;++i){
		u=read(),v=read(),w=read();
		edge[u].push_back(v),edge[v].push_back(u);
		U[i]=u,V[i]=v,W[i]=w;
	}
	tarjan(1);
	dfs(1,0);
	for(int i=1;i<=19;++i){
		for(int j=1;j<=tot;++j)
			fa[j][i]=fa[fa[j][i-1]][i-1];
	}
	f.New(tot+3),g.New(tot+3),h.New(tot+3);//开空间
	for(int i=1;i<=m;++i){
		update(U[i],V[i],W[i]);
	}
	while(q--){
		k=read();
		for(int i=1;i<=k;++i){
			x[i]=read(),y[i]=read(),type[i]=read();
		}
		printf("%d\n",query()); 
		int id=read(),w=read();
		if(id==0)continue;
		update(U[id],V[id],w-W[id]);
		W[id]=w;//边权差分处理 
	}
	return 0;
}

希望不会码风劝退...

posted on 2021-02-09 11:37  Bwzhh  阅读(98)  评论(0编辑  收藏  举报