[硫化铂]签到题

签到题

题目描述

在这里插入图片描述
在这里插入图片描述

题解

我连签到都签不了…

首先如果只看原题面的话很容易联想到最近做的最小割树的题,显然可以建出一棵最小割树出来,但那样是铁定会 T T T飞的,我们需要继续观察一下此题的性质。
这道题最关键的一个点是所有点的度数 ⩽ 3 \leqslant 3 3,这也就说明我们的任意两个点间的最大流都不会超过 3 3 3
不超过 3 3 3也就意味着我们两个点间的最大流只有 0 , 1 , 2 , 3 0,1,2,3 0,1,2,3 4 4 4种可能。
最大流为 0 0 0也就是不连通的情况,可以通过并查集预处理出来。
最大流为 1 1 1也就是通过一条边联通,这种情况下这两个点显然是属于不同的边双连通分量的,我们也可以通过 t a r j a n tarjan tarjan预处理。

所以我们现在需要的是对于每个边双连通分量,找到我们通过切断其中的两条边,可以使得哪些点对不连通,除去这个,剩下的就都是最大流为 3 3 3的了。
我们可以考虑先将 d f s dfs dfs树建出来,在 d f s dfs dfs树上切断两条边,能够将树分成两块。
显然,我们不可能切两条非树边,切开照样是棵树,仍是连通的,那我们就只剩两种情况:

  • 切一条树边与一条非树边,这种情况切掉树边 ( u , v ) (u,v) (u,v)后肯定 v v v的子树中肯定有且仅有一条边连到 v v v的联通块中。如果没有的话就该是两个边双联通分量了,如果有两条我们切完仍是连通的。这种情况也就是说我们能够将这棵树从这里划分成两块。
  • 切两条树边,这种情况下我们切的两条树边一定是祖孙关系,这两条树边之间的部分没有能够超越第一条树边的返祖边,它的返祖边来自第二条树边的下方,现在与之割掉自然也就不联通了。这样的话我们相当于将整棵树分成两条树边之间与之外的两块。

容易发现,当且仅当两个点在所有的划分方案中都在同一个块中,它们之间的最大流才是 3 3 3,否则一定存在一种方案可以割两条边将它们划开。
我们可以将其看作一个等价类,同一个等价类中的点的最大流是 3 3 3

考虑怎么维护这个等价类。
我们发现我们每次划分的块其 d f s dfs dfs序都是几乎连续的,只会有很少的几个区间。
那我们不妨就在这个 d f s dfs dfs区间上异或一个很大的数,来 H a s h Hash Hash它这个等价类,显然 H a s h Hash Hash值不同的两个点属于不同的等价类。
第一种情况我们只需记录一下从子树中传到祖先的返祖边的数量,这个树上差分就行,如果只有一条,就可以给这个子树的区间异或上一个数。
第二种情况可以转化成跨越这两条树边的返祖边集合相同,那么这两条边就可以被割开划分整棵树。
跨越这两条边的返祖边集合相同好判断,我们只需要用上面的做法在树上差分用 H a s h Hash Hash维护就可以了。
但集合相同的树边其实是可能很多的,我们不可能没两条边之间都处理一下,但我们事实上只需要处理它祖先中离它最近的一条,因为对于 ( x , y , z ) ( x < y < z ) (x,y,z)(x<y<z) (x,y,z)(x<y<z),如果我们划分 ( x , z ) (x,z) (x,z)显然是不敌我们先划分 ( x , y ) (x,y) (x,y)再划分 ( y , z ) (y,z) (y,z)这相当于将一个等价类分成了两个不同的等价类,显然限制是更强的,所以我们处理这些就行了。
我们可以按 d f s dfs dfs序的方式再次遍历这棵树,记录下它前面离它最近的 H a s h Hash Hash值相同的边时哪条边,划分一下就可以。
最后只用统计一下每个等价类的大小,就可以贡献到答案上了。
不同等价类之间是 2 2 2的贡献,同一个等价类中是 3 3 3的贡献。

由于我们的点达到了 1 0 6 10^6 106的级别,所以我们每次异或上的 H a s h Hash Hash值得很大,反正 i n t int int一类我好像过不了。
然后就很卡常了,居然还不能用 m a p map map,我还得手打一个 H a s h M a p HashMap HashMap
时间复杂度 O ( n ) O\left(n\right) O(n),最开始不用并查集维护,直接 d f s dfs dfs遍历也可以。

源码

#pragma GCC optimize(2)
#pragma GCC optimize(3)
#pragma GCC optimize("Ofast")
#include<bits/stdc++.h>
using namespace std;
#define MAXN 1000005
#define lowbit(x) (x&-x)
#define reg register
#define pb push_back
#define mkpr make_pair
#define fir first
#define sec second
#define lson (rt<<1)
#define rson (rt<<1|1)
typedef long long LL;
typedef unsigned long long uLL; 
typedef long double ld;
typedef pair<int,int> pii;
const int INF=0x3f3f3f3f;
const int mo=998244353;
const int mod=1e6+3;
const int inv2=499122177;
const int jzm=2333;
const int zero=2000;
const int n1=2000;
const int orG=3,ivG=332748118;
const long double Pi=acos(-1.0);
const double eps=1e-12;
template<typename _T>
_T Fabs(_T x){return x<0?-x:x;}
char gc(){static char buf[20000000],*p1=buf,*p2=buf;return p1==p2&&(p2=(p1=buf)+fread(buf,1,20000000,stdin),p1==p2)?EOF:*p1++;}
#define getchar gc
template<typename _T>
void read(_T &x){
	_T f=1;x=0;char s=getchar();
	while(s>'9'||s<'0'){if(s=='-')f=-1;s=getchar();}
	while('0'<=s&&s<='9'){x=(x<<3)+(x<<1)+(s^48);s=getchar();}
	x*=f;
}
template<typename _T>
void print(_T x){if(x<0){x=(~x)+1;putchar('-');}if(x>9)print(x/10);putchar(x%10+'0');}
int gcd(int a,int b){return !b?a:gcd(b,a%b);}
int add(int x,int y,int p){return x+y<p?x+y:x+y-p;}
void Add(int &x,int y,int p){x=add(x,y,p);}
int qkpow(int a,int s,int p){int t=1;while(s){if(s&1)t=1ll*t*a%p;a=1ll*a*a%p;s>>=1;}return t;}
int n,m,dfn[MAXN],low[MAXN],belong[MAXN],idx,cnt,sz[MAXN],lw[MAXN];
int sta[MAXN],stak,ord[MAXN],fa[MAXN],rd[MAXN],pre[MAXN],dep[MAXN];
LL sp[MAXN],dif[MAXN];
bool vis[MAXN],vp[MAXN];
LL ans,num;bool insta[MAXN];
vector<int>vec[MAXN],G[MAXN],P[MAXN];
mt19937 e(time(NULL));
uniform_int_distribution<LL> g(1LL,(1LL<<62)-1LL);
struct HashMap{
	int sum[MAXN],nxt[MAXN],head[MAXN],sta[MAXN],stak,tot;LL val[MAXN];
	void insert(LL ai,int aw){
		int pos=ai%mod,now=head[pos];if(!head[pos])sta[++stak]=pos;
		while(now&&val[now]!=ai)now=nxt[now];
		if(!now)now=++tot,val[now]=ai,nxt[now]=head[pos],head[pos]=now;
		sum[now]=aw;
	}
	int query(LL ai){
		int pos=ai%mod,now=head[pos];
		while(now&&val[now]!=ai)now=nxt[now];
		return sum[now];
	}
	void clear(){
		for(int i=1;i<=tot;i++)sum[i]=nxt[i]=val[i]=0;tot=0;
		while(stak)head[sta[stak--]]=0;
	}
}mp;
void makeSet(int x){for(int i=1;i<=x;i++)fa[i]=i;}
int findSet(int x){return fa[x]==x?x:fa[x]=findSet(fa[x]);}
void unionSet(int a,int b){int u=findSet(a),v=findSet(b);if(u^v)fa[u]=v;}
void tarjan(int u,int fa){
	dfn[u]=low[u]=++idx;sta[++stak]=u;insta[u]=1;
	int siz=G[u].size();
	for(int i=0;i<siz;i++){
		int v=G[u][i];if(v==fa)continue;
		if(!dfn[v])tarjan(v,u),low[u]=min(low[u],low[v]);
		else if(insta[v])low[u]=min(low[u],dfn[v]);
	}
	if(dfn[u]==low[u]){cnt++;int v;do{v=sta[stak--];belong[v]=cnt;insta[v]=0;}while(u^v);}
}
void dosaka1(int u,int fa){
	int siz=P[u].size();vis[u]=1;
	dep[u]=dep[fa]+1;dfn[u]=++idx;pre[idx]=u;
	for(int i=0;i<siz;i++){
		int v=P[u][i];if(v==fa)continue;
		if(!vis[v])dosaka1(v,u),sp[u]^=sp[v],lw[u]+=lw[v];
		else if(dep[v]<dep[u]){LL tmp=g(e);sp[u]^=tmp,sp[v]^=tmp;lw[u]++;lw[v]--;}
	}
	rd[u]=idx;
}
void dosaka2(int u,int fa){
	if(lw[u]==1){LL tmp=g(e);dif[dfn[u]]^=tmp;dif[rd[u]+1]^=tmp;}
	int las=mp.query(sp[u]),siz=P[u].size();
	if(las){
		LL tmp=g(e);
		dif[dfn[las]]^=tmp;dif[rd[las]+1]^=tmp;
		dif[dfn[u]]^=tmp;dif[rd[u]+1]^=tmp;
	}
	mp.insert(sp[u],u);
	for(int i=0;i<siz;i++){
		int v=P[u][i];
		if(dep[v]^dep[u]+1)continue;
		dosaka2(v,u);
	}
	mp.insert(sp[u],las);
}
signed main(){
	freopen("juice.in","r",stdin);
	freopen("juice.out","w",stdout);
	read(n);read(m);makeSet(n);
	for(int i=1,u,v;i<=m;i++)
		read(u),read(v),G[u].pb(v),G[v].pb(u),unionSet(u,v);
	for(int i=1;i<=n;i++)sz[findSet(i)]++;int summ=0;
	for(int i=1;i<=n;i++)ans-=1ll*sz[i]*summ,summ+=sz[i],sz[i]=0;
	for(int i=1;i<=n;i++)if(!dfn[i])tarjan(i,0);summ=0;
	for(int i=1;i<=n;i++)sz[belong[i]]++,vec[belong[i]].pb(i);
	for(int i=1;i<=cnt;i++)ans+=1ll*sz[i]*summ,num+=1ll*sz[i]*summ,summ+=sz[i],sz[i]=0;
	for(int i=1;i<=n;i++)if(!vp[belong[i]]){
		int z=belong[i],siz1=vec[z].size();vp[z]=1;
		for(int j=0;j<siz1;j++)ord[vec[z][j]]=j+1;
		for(int j=0;j<siz1;j++){
			int x=vec[z][j],y=ord[x],siz=G[x].size();
			for(int k=0;k<siz;k++){
				int v=G[x][k];
				if(belong[x]^belong[v])continue;
				P[y].pb(ord[v]);
			}
		}
		for(int j=1;j<=siz1;j++)dfn[j]=low[j]=0;idx=0;
		dosaka1(1,0);dosaka2(1,0);mp.clear();
		for(int j=1;j<=idx;j++){
			dif[j]^=dif[j-1];int tp=mp.query(dif[j]);
			ans+=3ll*tp,num+=tp;mp.insert(dif[j],tp+1);
		}
		mp.clear();for(int j=1;j<=idx+1;j++)dif[j]=0;
		for(int j=1;j<=siz1;j++)vis[j]=sp[j]=lw[j]=0,P[j].clear();
	}
	ans+=2ll*(1ll*n*(n-1)/2LL-num);
	printf("%lld\n",ans);
	return 0;
}

谢谢!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值