1. 程式人生 > >[JZOJ 5895]【NOIP2018模擬10.5】旅遊

[JZOJ 5895]【NOIP2018模擬10.5】旅遊

Description

給出一個n個點,m條邊的無向圖,保證無重邊自環,第i條邊的長度為 2 i 2^i
現在需要你找一條從1號點出發從1號點結束的迴路,保證每條邊至少經過一次,可以重複經過。求迴路長度的最小值。
n

, m 5 1 0 5 n,m\leq 5*10^5

Solution

考慮一個無向圖存在歐拉回路的條件:每個點的度數均為偶數
定義度數為偶數的點為偶點,相應的定義奇(ji,一聲)點
那麼我們將所有奇點提出來,要將它們變成偶點。

相當於我們要在這些奇點之間連邊,長度為它們之間的最短路,要連總長最少的邊使得所有奇點都變成偶點。
答案就是原本所有的邊權和+我們新連的邊

根據最短路的性質,若存在邊(x,y),(y,z),(y,p),那麼不如連(x,z),(y,p),因為dis(x,y)+dis(y,z)>=dis(x,z)(相當於列舉中轉點,聯想Floyd演算法)
這樣一來,我們連的邊必然是一個完美匹配(即兩兩匹配,每個點會且只會匹配另一個)

這麼一來好像變成了帶邊權完全圖的最小權最大匹配
好像是什麼帶花樹?好像還是N^3的?(逃)
這可是NOIP!

咦我們發現,邊權有問題
第i條邊的長度為2^i
也就是說,所有小的加起來都不如一個大的大
那麼路徑一定就在最小生成樹上了。。。

我們不妨先任意匹配這些奇點
此時我們考慮兩個匹配(x,y),(p,q)
如果它們路徑有重疊那肯定不優,因為一定可以重組變成沒有重疊
發現這和樹上路徑的異或很像。
任意匹配,將每一組奇點在最小生成樹上的路徑標記,如果一條邊被標記了奇數次則計入答案

然而有一種更機智的做法。
在每個奇點都打上1的標記,DFS最小生成樹,如果某個點i為根的子樹中標記的異或值為1則答案需要加上i到i父親這條邊的邊權
這與上面的過程是等價的

由於邊權是按順序2^i給出的,我們連排序都省了。
複雜度 O ( n α ( n ) + m ) O(n\alpha(n)+m)

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
#define LL long long
#define mo 998244353
#define N 500005
using namespace std;
int m1,n,m,rd[N],fs[N],pr[2*N],nt[2*N],dt[2*N],d[N],d1[N],bz[N],f[N],a1[N][2];
LL ans,cf[N];
void link(int x,int y,int z)
{
	nt[++m1]=fs[x];
	dt[fs[x]=m1]=y;
	pr[m1]=z;
}
int getf(int k)
{
	if(!f[k]) return k;
	return f[k]=getf(f[k]);
}
void dfs(int k,int fa,LL s)
{
	for(int i=fs[k];i;i=nt[i])
	{
		int p=dt[i];
		if(p!=fa) dfs(p,k,pr[i]),bz[k]^=bz[p];
	}
	if(bz[k]) ans=(ans+s)%mo;
}
int main()
{
	cin>>n>>m;
	fo(i,1,m)
	{
		int x,y;
		scanf("%d%d",&x,&y);
		a1[i][0]=x,a1[i][1]=y;
		rd[x]++,rd[y]++;
	}
	fo(i,1,n) if(rd[i]&1) bz[i]=1;
	int j=0;
	cf[0]=1;
	ans=0;
	fo(i,1,m) cf[i]=cf[i-1]*(LL)2%mo,ans=(ans+cf[i])%mo;
	fo(i,1,m)
	{
		int fx=getf(a1[i][0]),fy=getf(a1[i][1]);
		if(fx!=fy)
		{
			f[fx]=fy;
			link(a1[i][0],a1[i][1],cf[i]);
			link(a1[i][1],a1[i][0],cf[i]);
		}
	}
	dfs(1,0,0);
	printf("%lld\n",ans);
}