• [luogu 5163] WD与地图


    一、题目

    点此看题

    二、解法

    只能说是精神污染了,虽然每个部分都不难把但是放在一起就很难写了。

    考虑无向图的情况是好做的,我们直接离线逆序询问,那么删边操作就变成了加边,单点增加操作就变成了单点减少。那么做法是显然的,我们线段树合并维护加边操作,再支持线段树单点修改和线段树上二分即可。

    本题是强连通分量怎么办呢?我们类比无向图的情况,还是先离线逆序询问,考虑把每一条边的效果等效为合并两个强连通块。那么我们求出每一条边有用的时间 \(f\),也就是恰好在这个时刻这条边的两个端点在同一个强连通分量里

    发现这个时刻是单调的,而我们要对所有边都求出这样一个时刻,那么可以考虑整体二分。考虑现在要判断一条边的 \(f\)\(mid\) 的大小关系,那么我们保留加入时间在 \([0,mid]\) 的边(为 \(0\) 就表示没有被删除),如果这条边的加入时间 \(\leq mid\) 并且两端点在同一强连通分量中,那么它的 \(f\leq mid\),否则 \(f>mid\)

    但是这样做时间复杂度又有点不对,我们可以考虑动态 \(\tt tarjan\),也就是假设我们已经对保留 \([0,l)\) 边的图缩过点了,那么我们再此基础上加入 \([l,mid]\) 的边就可以了。所以我们整体二分的时候先走右边再走左边,走右边的时候把新加入的边也保留,然后使用可撤销并查集就可以维护缩点的结果。

    时间复杂度 \(O(n\log^2 n)\),复杂度瓶颈在可撤销并查集。

    #include <cstdio>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    #include <stack>
    #include <map>
    using namespace std;
    const int M = 200005;
    const int N = 30*M;
    #define ll long long
    const int up = 1e9;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,k,t,c[M],fa[M],siz[M],top,a[M],b[M],p[M<<1];
    int cnt,rt[M],ls[N],rs[N],num[N];ll ans[M],sum[N];
    int Ind,low[M],dfn[M],in[M];stack<int> s;
    vector<int> g[M];map<ll,int> mp;
    struct node
    {
    	int t,x,y;
    	bool operator < (const node &r) const
    		{return t<r.t;}
    }q[M],e[M],el[M],er[M],d[M];
    //namespace : divide and conquer
    int find(int x)
    {
    	return fa[x]==x?x:find(fa[x]);
    }
    void merge(int u,int v)
    {
    	int x=find(u),y=find(v);
    	if(x==y) return ;
    	if(siz[x]<siz[y]) swap(x,y);
    	//guarantee that siz[x]>siz[y]
    	siz[x]+=siz[y];fa[y]=x;
    	top++;a[top]=x;b[top]=y;
    }
    void tarjan(int u)
    {
    	low[u]=dfn[u]=++Ind;
    	s.push(u);in[u]=1;
    	for(int v:g[u])
    	{
    		if(!dfn[v])
    		{
    			tarjan(v);
    			low[u]=min(low[u],low[v]);
    		}
    		else if(in[v])
    			low[u]=min(low[u],dfn[v]);
    	}
    	if(low[u]==dfn[u])
    	{
    		int v=0;
    		do
    		{
    			v=s.top();s.pop();
    			merge(v,u);in[v]=0;
    		}while(v!=u);
    	}
    }
    void popout()
    {
    	int u=a[top],v=b[top--];
    	siz[u]-=siz[v];fa[v]=v;
    }
    void cdq(int l,int r,int L,int R)
    {
    	if(l>r) return ;
    	if(L==R)
    	{
    		for(int i=l;i<=r;i++)
    			d[++t]=node{L,e[i].x,e[i].y};
    		return ;
    	}
    	int mid=(L+R)>>1,tl=0,tr=0,num=0,now=top;
    	//build the graph of [L,mid]
    	for(int i=l;i<=r;i++)
    	{
    		if(e[i].t>mid) continue;
    		int x=find(e[i].x),y=find(e[i].y);
    		p[++num]=x;p[++num]=y;g[x].push_back(y);
    	}
    	//do partial tarjan
    	for(int i=1;i<=num;i++)
    		if(!dfn[p[i]]) tarjan(p[i]);
    	//partition [l,r]
    	for(int i=l;i<=r;i++)
    	{
    		if(e[i].t<=mid && find(e[i].x)==find(e[i].y))
    			el[++tl]=e[i];
    		else er[++tr]=e[i];
    	}
    	for(int i=1;i<=tl;i++) e[l+i-1]=el[i];
    	for(int i=1;i<=tr;i++) e[l+tl+i-1]=er[i];
    	//clear
    	for(int i=1;i<=num;i++)
    		low[p[i]]=dfn[p[i]]=0,g[p[i]].clear();
    	Ind=num=0;
    	//right first !!!
    	cdq(l+tl,r,mid+1,R);
    	while(top>now) popout();
    	cdq(l,l+tl-1,L,mid);
    }
    //namespace : segment tree
    int Find(int x)
    {
    	if(x!=fa[x]) fa[x]=Find(fa[x]);
    	return fa[x];
    }
    void ins(int &x,int l,int r,int id,int f)
    {
    	if(!x) x=++cnt;
    	sum[x]+=f*id;num[x]+=f;
    	if(l==r) return ;
    	int mid=(l+r)>>1;
    	if(mid>=id) ins(ls[x],l,mid,id,f);
    	else ins(rs[x],mid+1,r,id,f);
    }
    int comb(int x,int y)
    {
    	if(!x || !y) return x+y;
    	sum[x]+=sum[y];num[x]+=num[y];
    	ls[x]=comb(ls[x],ls[y]);
    	rs[x]=comb(rs[x],rs[y]);
    	return x;
    }
    ll ask(int x,int l,int r,int k)
    {
    	if(num[x]<=k) return sum[x];
    	if(!x) return 0;
    	if(l==r) return 1ll*min(num[x],k)*l;
    	int mid=(l+r)>>1;
    	if(num[rs[x]]>=k) return ask(rs[x],mid+1,r,k);
    	return ask(ls[x],l,mid,k-num[rs[x]])+sum[rs[x]];
    }
    signed main()
    {
    	//initialize
    	n=read();m=read();k=read();
    	for(int i=1;i<=n;i++)
    		c[i]=read(),fa[i]=i,siz[i]=1;
    	for(int i=1;i<=m;i++)
    	{
    		int u=read(),v=read();
    		e[i].x=u;e[i].y=v;mp[1ll*u*n+v]=i;
    	}
    	for(int i=1;i<=k;i++)
    	{
    		q[i].t=read(),q[i].x=read(),q[i].y=read();
    		if(q[i].t==2) c[q[i].x]+=q[i].y;
    	}
    	//reverse the queries
    	reverse(q+1,q+1+k);
    	//calc the time in queries and go to cdq
    	for(int i=1;i<=k;i++) if(q[i].t==1)
    		e[mp[1ll*q[i].x*n+q[i].y]].t=i;
    	cdq(1,m,0,k+1);
    	//calc the answer as undirected graph
    	sort(d+1,d+1+t);
    	for(int i=1;i<=n;i++)
    		fa[i]=i,ins(rt[i],0,up,c[i],1);
    	for(int i=1,j=1;i<=k;i++)
    	{
    		//link the edge
    		while(j<=t && d[j].t<=i)
    		{
    			int x=Find(d[j].x),y=Find(d[j].y);j++;
    			if(x==y) continue ;
    			fa[y]=x;rt[x]=comb(rt[x],rt[y]);
    		}
    		int x=q[i].x,y=q[i].y;ans[i]=-1;
    		if(q[i].t==2)
    		{
    			ins(rt[Find(x)],0,up,c[x],-1);
    			c[x]-=y;
    			ins(rt[Find(x)],0,up,c[x],1);
    		}
    		if(q[i].t==3)
    			ans[i]=ask(rt[Find(x)],0,up,y);
    	}
    	for(int i=k;i>=1;i--) if(ans[i]!=-1)
    		printf("%lld\n",ans[i]);
    }
    
  • 相关阅读:
    分布式事务的四种解决方案
    uber-go/guide 的中文翻译
    域名解析-CNAME
    Nginx节点存活状态检查
    Laravel核心解读--中间件(Middleware)
    Mac dyld: Library not loaded: /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib
    前缀树算法实现路由匹配原理解析
    原创-实用salt部署文档(持续更新)
    线上案例-调参-设置滑动窗口提高视频云播放性能
    NGINX日志割切
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15955558.html
Copyright © 2020-2023  润新知