天天看點

JZOJ5895. 【NOIP2018模拟10.5】旅遊

JZOJ5895. 【NOIP2018模拟10.5】旅遊
JZOJ5895. 【NOIP2018模拟10.5】旅遊

題解

很顯然,一條邊至少走一次,最多走兩次,

先建一棵最小生成樹,

對于一條不在樹上面的邊,就将與它在最小生成樹上構成的邊的權值全部加一。

最後權值為偶數的邊邊權算兩次,其餘的都算一次。

如果被其他非最小生成樹上面的經過偶數次,也就是要原路傳回。

code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <string.h>
#define N 500003
#define P putchar
#define G getchar
using namespace std;
char ch;
void read(int &n)
{
	n=0;
	ch=G();
	while((ch<'0' || ch>'9') && ch!='-')ch=G();
	int w=1;
	if(ch=='-')w=-1,ch=G();
	while('0'<=ch && ch<='9')n=(n<<3)+(n<<1)+ch-'0',ch=G();
	n*=w;
}

const int mo=998244353;
int f[19][N],nxt[N*2],lst[N],to[N*2],num[N*2],tot;
int n,m,x,y,xx,yy,z[N],fa[N],v[N],id[N],ans;
int l[N],r[N],w,q[N],head,tail,dep[N];

void ins(int x,int y,int z)
{
	nxt[++tot]=lst[x];
	to[tot]=y;
	num[tot]=z;
	lst[x]=tot;
}

void bfs()
{
	int x;
	for(head=0,q[tail=1]=1;head<tail;)
	{
		x=q[++head];dep[x]=dep[f[0][x]]+1;
		for(int i=lst[x];i;i=nxt[i])
			if(to[i]!=f[0][x])f[0][q[++tail]=to[i]]=x,id[to[i]]=num[i];
	}
}

int lca(int x,int y)
{
	if(dep[x]<dep[y])swap(x,y);
	for(int j=18;j+1;j--)
		if(dep[f[j][x]]>=dep[y])x=f[j][x];
	if(x==y)return x;
	for(int j=18;j+1;j--)
		if(f[j][x]!=f[j][y])x=f[j][x],y=f[j][y];
	return f[0][x];
}

int get(int x){return fa[x]=(fa[x]==x?x:get(fa[x]));}

int main()
{
	freopen("travel.in","r",stdin);
	freopen("travel.out","w",stdout);
	
	read(n);read(m);tot=z[0]=1;
	for(int i=1;i<=n;i++)fa[i]=i;
	for(int i=1;i<=m;i++)
	{
		read(x),read(y);
		xx=get(x);yy=get(y);
		if(xx!=yy)ins(x,y,i),ins(y,x,i),fa[xx]=yy;
		else w++,l[w]=x,r[w]=y;
		z[i]=z[i-1]*2%mo;
		ans=(ans+z[i])%mo;
	}
	
	bfs();
	for(int j=1;j<19;j++)
		for(int i=1;i<=n;i++)
			f[j][i]=f[j-1][f[j-1][i]];
	
	for(int i=1;i<=w;i++)
	{
		x=lca(l[i],r[i]);
		v[l[i]]++,v[r[i]]++;
		v[x]=v[x]-2;
	}
	
	for(int i=n;i;i--)
		v[f[0][q[i]]]=v[f[0][q[i]]]+v[q[i]];
	
	for(int i=2;i<=n;i++)
		if(v[i]%2==0)ans=(ans+z[id[i]])%mo;
	printf("%lld",ans);
	
	return 0;
}