zoukankan      html  css  js  c++  java
  • UOJ189火车司机出秦川,虚仙人掌,仙人掌上差分

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

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

    代码承袭自(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
    ",query()); 
    		int id=read(),w=read();
    		if(id==0)continue;
    		update(U[id],V[id],w-W[id]);
    		W[id]=w;//边权差分处理 
    	}
    	return 0;
    }
    

    希望不会码风劝退...

  • 相关阅读:
    数据仓库_Linux(3)
    2.1(构造序对)
    要修改一万个位置的jdk版本
    8个球7个一样重的,有一个偏重,一个天平,如何两次找出偏重的小球
    玄学
    异常:java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/LoopTag
    提高输入效率
    fan
    idea
    打印整数的补码
  • 原文地址:https://www.cnblogs.com/wzhh/p/14392378.html
Copyright © 2011-2022 走看看