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;
    }
    

    希望不会码风劝退...

  • 相关阅读:
    CSS盒子模型
    getContextPath、getServletPath、getRequestURI、request.getRealPath的区别
    MYSQL中的CASE WHEN END AS
    单点登录的精华总结
    git&github
    June 21st 2017 Week 25th Wednesday
    June 20th 2017 Week 25th Tuesday
    June 19th 2017 Week 25th Monday
    June 18th 2017 Week 25th Sunday
    June 17th 2017 Week 24th Saturday
  • 原文地址:https://www.cnblogs.com/wzhh/p/14392378.html
Copyright © 2011-2022 走看看