咋肥似呀(joke)--发了一堆tarjan论文...

一道十分巧妙的分治题,很明显是需要离线的

那么我们看看时间轴分了类后,一条边有贡献当且仅当连接的两个点在左边没有形成环

如果前半部分在左边能够形成环,我们就把他们加入下一层的左边,否则就加入右边

然后递归tarjan处理,已经成环的就可以直接并查集合并为一个点,那么可以发现复杂度就是mlogm

理解了之后就是非常简单的一道题了

#include<bits/stdc++.h>
using namespace std;
int u[1000005],v[1000005],n,m;
long long ans[1000005],tot;
int size[100005],father[100005],instk[100005];
vector <int> id,vec[100005];
int dfn[100005],low[100005],color[100005],vis[100005],cnt,stk[100005],top;
int minn(int x,int y){if(x<y) return x;return y;}
inline int find(int x)
{
	if(x==father[x]) return x;
	return father[x]=find(father[x]);
}
inline void unionn(int x,int y)
{
	int r1=find(x),r2=find(y);
	if(r1==r2)return;
	father[r1]=r2;
	size[r2]+=size[r1];
}
inline void tarjan(int x)
{
	low[x]=dfn[x]=++cnt;
	stk[++top]=x;instk[x]=1;
	for(vector <int> :: iterator it=vec[x].begin(),ed=vec[x].end();it!=ed;it++)
	{
		int y=*it;
		if(!dfn[y])
		{
			tarjan(y),low[x]=minn(low[x],low[y]);
		}
		else if(instk[y]) low[x]=minn(low[x],dfn[y]);
	}
	if(dfn[x]==low[x])
	{
		int sum=0;
		while(233)
		{
			int now=stk[top--];
			instk[now]=0;
			tot+=1ll*sum*size[now];
			sum+=size[now];
			color[now]=x;
			if(now==x) break;
		}
	}
}
inline void clear(vector <int> a)
{
	for(int i=0;i<a.size();i++)
	{
		int x=a[i];
		dfn[x]=low[x]=vis[x]=0;
		vec[x].clear();
	}
}
inline void solve(int l,int r,vector <int> id)
{
	int mid=(l+r)>>1;
	vector <int> cur;
	for(int i=0;i<id.size();i++)
	if(id[i]<=mid)
	{
		int x=find(u[id[i]]),y=find(v[id[i]]);
		if(!vis[x]) vis[x]=1,cur.push_back(x);
		if(!vis[y]) vis[y]=1,cur.push_back(y);
		vec[x].push_back(y);
	}
	tot=cnt=top=0;
	for(int i=0;i<cur.size();i++)
	if(!dfn[cur[i]]) tarjan(cur[i]);
	if(l==r)
	{
		ans[l]=ans[l-1]+tot;
		for(int i=0;i<cur.size();i++) unionn(cur[i],color[cur[i]]);
		clear(cur);
		return;
	}
	vector <int> lef,rig;
	for(int i=0;i<id.size();i++)
	{
		int now=id[i],x=find(u[now]),y=find(v[now]);
		if(vis[x]&&vis[y]&&color[x]==color[y])
		{
			if(now<=mid) lef.push_back(now);
		}
		else rig.push_back(now);
	}
	clear(cur);
	solve(l,mid,lef);solve(mid+1,r,rig);
}
int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	scanf("%d%d",&u[i],&v[i]),id.push_back(i);
	for(int i=1;i<=n;i++)
	size[i]=1,father[i]=i;
	solve(1,m,id);
	for(int i=1;i<=m;i++)
	printf("%lld\n",ans[i]);
	return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值