1. 程式人生 > 實用技巧 >【Comet OJ - Contest #15】孤獨的吉姆 6

【Comet OJ - Contest #15】孤獨的吉姆 6

題目

題目連結:https://cometoj.com/contest/79/problem/G?problem_id=4215&tdsourcetag=s_pcqq_aiomsg
給你一個 \(n\) 個點 \(m\) 條邊的簡單連通無向圖,請拔掉一些邊使得圖中奇數度數的點儘可能多,並輸出字典序最大的方案。
如果刪掉第 \(i\) 條邊則 \(01\) 串第 \(i\) 位為 \(1\), 否則為 \(0\)

思路

注意:beginend 學長用曾經 AC 的程式碼重新提交確認標程會 RE。所以這份程式碼部分點在 cometOJ 上會 RE。
顯然,選擇一條路徑反色會讓這條路徑的兩個端點的奇偶性改變,而其他點均不變。
那麼顯然最終度數為偶數的點只會有不超過 \(1\)

個,這取決於原圖有多少個點是偶數度數。
而顯然只選擇原圖任意一棵生成樹的邊反色也是有可行解的,所以為了讓反色的邊的邊權儘量大,選擇最大生成樹顯然最優。
求出最大生成樹後分類討論:

  • 當偶數點數量為偶數時,最大生成樹中一條邊 \((u,v)\) 能不刪除當且僅當 \(u,v\) 兩點的子樹各有偶數個度數為偶數的點。否則一定被刪除。
    這個結論十分顯然,在此不過多贅述。
  • 當偶數點數量為奇數時,我們可以先無視一個偶數點,隨意欽定剩下偶數個偶數點的連邊方式,,然後從我們無視的點開始 dfs,顯然我們要儘量翻轉已經被翻轉的編號最小的邊。所以用單調棧維護到達一個點 \(x\) 時,路徑上長度單調不增的邊。然後從第一個邊權小於現在邊權的位置向現在位置連邊。
    然後每次貪心選點即可。
    時間複雜度 \(O(n+m)\)

程式碼

#include <bits/stdc++.h>
using namespace std;

const int N=2000010;
int n,m,tot,cnt,top,top1,flag,deg[N],head[N],father[N],U[N],V[N],st[N],st1[N];
bool rev[N];
vector<int> e2[N];

int read()
{
	int d=0; char ch=getchar();
	while (!isdigit(ch)) ch=getchar();
	while (isdigit(ch)) d=(d<<3)+(d<<1)+ch-48,ch=getchar();
	return d;
}

struct edge
{
	int next,to,id;
}e[N*2];

void add(int from,int to,int WYCtxdy)
{
	e[++tot].to=to;
	e[tot].id=WYCtxdy;
	e[tot].next=head[from];
	head[from]=tot;
}

int find(int x)
{
	return x==father[x]?x:father[x]=find(father[x]);
}

int dfs1(int x,int fa,int id)
{
	int size=!(deg[x]&1);
	for (int i=head[x];~i;i=e[i].next)
	{
		int v=e[i].to;
		if (v!=fa) size+=dfs1(v,x,e[i].id);
	}
	if (id) rev[id]=!(size&1);
	return size;
}

void dfs2(int x,int fa,int id)
{
	int cnt=top1;
	if (id)
	{
		while (top>1 && st[top]>id)
		{
			st1[++top1]=st[top];
			top--;
		}
		e2[st[top]].push_back(id);
		st[++top]=id;
	}
	for (int i=head[x];~i;i=e[i].next)
		if (e[i].to!=fa) dfs2(e[i].to,x,e[i].id);
	if (top>1 && st[top]==id) top--;
	for (;top1>cnt;top1--) st[++top]=st1[top1];
}

void dfs3(int x)
{
	int pos=1000000000;
	for (int i=0;i<(int)e2[x].size();i++)
	{
		int v=e2[x][i];
		if (v<pos && !rev[v]) pos=v;
	}
	if (pos==1000000000) flag=x;
		else dfs3(pos);	
}

void dfs4(int x,int fa,int id)
{
	if (id==flag)
	{
		rev[id]^=1; flag=-1;
		return;
	}
	for (int i=head[x];~i;i=e[i].next)
		if (e[i].to!=fa)
		{
			dfs4(e[i].to,x,e[i].id);
			if (flag<0)
			{
				rev[id]^=1;
				return;
			}
		}
}

void solve_even()
{
	dfs1(1,0,0);
}

void solve_odd()
{
	int rt;
	for (int i=1;i<=n;i++)
		if (!(deg[i]&1))
		{
			deg[i]=19260817; rt=i;
			break;
		}
	dfs1(1,0,0); st[++top]=0;
	dfs2(rt,0,0); dfs3(0); dfs4(rt,0,0);
}

int main()
{
	memset(head,-1,sizeof(head));
	n=read(); m=read();
	for (int i=1;i<=m;i++)
	{
		rev[i]=1;
		U[i]=read()+1; V[i]=read()+1;
		deg[U[i]]++; deg[V[i]]++;
	}
	for (int i=1;i<=n;i++)
	{
		father[i]=i;
		if (!(deg[i]&1)) cnt++;
	}
	for (int i=m;i>=1;i--)
	{
		int x=find(U[i]),y=find(V[i]);
		if (x!=y)
		{
			father[x]=y;
			add(U[i],V[i],i); add(V[i],U[i],i);
		}
	}
	if (cnt&1) solve_odd();
		else solve_even();
	for (int i=1;i<=m;i++)
		printf("%d",rev[i]);
	return 0;
}