本文介绍: 以前从来没见过除了板子以外的题,但最近总是做题见到欧拉回路然后一样的 trick 每次都想不到。怎么一点举一反三的能力都没有的?板子有向图欧拉回路dfs当前优化。 Codestack<int> q;void dfs(int u){ for(int i=head[u];i;i=head[u]) { head[u]=e[i].nx

以前从来没见过除了板子以外的题,但最近总是做题见到欧拉回路然后一样的 trick 每次都想不到。
怎么一点举一反三的能力都没有的?

板子

有向图的欧拉回路

dfs当前优化

Code
stack<int> q;
void dfs(int u)
{
    for(int i=head[u];i;i=head[u])
    {
        head[u]=e[i].nxt; 
        int v=e[i].to; dfs(v);
    }
    q.push(u);
}

无向图的欧拉回路

双向标记已经选过的边。

Code
vector<int> stk;
void dfs(int u)
{
	for(int &i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to; if(Vis[i]) continue;
		Vis[i]=Vis[i^1]=1,dfs(v);
	}
	stk.push_back(u);
}

混合图的欧拉回路

其实是网络流,跟欧拉回路关系不大。

与有向图、无向图的欧拉回路不同,在混合图中,需要对所有无向边进行合理的定向,使之转化为有向图求解

先对所有边随机指定一个方向,令点的权值 (d_i=in_i-out_i)我们希望通过调整边的方向,使所有 (d_i=0)
考虑将原本 (uto v) 的边反向,对 (d) 数组影响(d_ugets d_u+2,d_vgets d_v-2)。因此我们(d) 数组全部除以 (2)一次反向操作的贡献变为 (1)
对于 (d_i&gt;0),连边 ((s,i,d_i));对于 (d_i<0),连边 ((i,t,-d_i))。对于原图的每条无向边 (uto v),连边 ((u,v,1))。跑最大流,流满的边即为需要反向的边。
对所有边定向完毕后,跑有向图的欧拉回路即可

Code
const int N=1005,inf=1e9;
int n,m,s,t;
struct edge{int nxt,to,w,id;} e[N<<1];
int head[N],cnt=1;
il void add(int u,int v,int w,int id)
{
    e[++cnt]={head[u],v,w,id};head[u]=cnt;
    e[++cnt]={head[v],u,0,0};head[v]=cnt;
}
int dis[N],now[N];
queue<int&gt; q;
il bool bfs()
{
    for(int i=s;i<=t;i++) dis[i]=inf,now[i]=head[i];
    dis[s]=0;q.push(s);
    while(!q.empty())
    {
        int u=q.front(); q.pop();
        for(int i=head[u];i;i=e[i].nxt)
        {
            int v=e[i].to;
            if(e[i].w&amp;&amp;dis[v]==inf) dis[v]=dis[u]+1,q.push(v);
        }
    }
    return dis[t]!=inf;
}
int dfs(int u,int sum)
{
    int res=0;
    if(u==t) return sum;
    for(int i=now[u];i&amp;&amp;sum;i=e[i].nxt)
    {
        now[u]=i; int v=e[i].to;
        if(!e[i].w||dis[v]!=dis[u]+1) continue;
        int k=dfs(v,min(sum,e[i].w));
        e[i].w-=k,e[i^1].w+=k,sum-=k,res+=k;
    }
    return res;
}
bool flag[N];
int U[N],V[N],Tp[N],d[N],st[N],sum;
vector<int&gt; E[N];
stack<int> Q;
void dfs1(int u)
{
    for(int i=st[u];i<E[u].size();i=st[u])
    {
        st[u]++;
        int v=E[u][i]; dfs1(v);
    }
    Q.push(u);
}
int main()
{
    int T=read();
    while(T--)
    {
        n=read(),m=read(); s=0,t=n+1,sum=0;
        for(int i=0;i<=n+1;i++) d[i]=0,head[i]=0,st[i]=0,E[i].clear(); cnt=1;
        for(int i=1;i<=m;i++)
        {
            int u=read(),v=read(); char c;cin>>c;
            U[i]=u,V[i]=v,Tp[i]=(c=='D'?0:1);
            d[u]++,d[v]--,flag[i]=0;
        }
        bool Fg=1;
        for(int i=1;i<=n;i++) if(d[i]&amp;1) Fg=0; else d[i]/=2;
        if(!Fg) {printf("No euler circuit existnn");continue;}
        for(int i=1;i<=n;i++) if(d[i]>0) add(s,i,d[i],0),sum+=d[i]; else if(d[i]<0) add(i,t,-d[i],0);
        for(int i=1;i<=m;i++)
        {
            int u=U[i],v=V[i]; if(!Tp[i]) continue;
            add(u,v,1,i);
        }
        int flw=0;
        while(bfs()) flw+=dfs(s,inf);
        if(flw!=sum) {printf("No euler circuit existnn");continue;}
        for(int u=1;u<=n;u++)
        {
            for(int i=head[u];i;i=e[i].nxt)
            {
                if(!e[i].id) continue;
                if(!e[i].w) flag[e[i].id]=1;
            }
        }
        for(int i=1;i<=m;i++) 
        {
            if(flag[i]) swap(U[i],V[i]);
            E[U[i]].push_back(V[i]);
        }
        dfs1(1);
        while(!Q.empty()) printf("%d ",Q.top()),Q.pop(); printf("nn");
    }
    return 0;
}

CF527E Data Center Drama

发现要求每个出度和入度都是偶数,那么这个点的总度数必须是偶数
如果这张图已经有一条长度偶数的欧拉回路,构造答案是容易的,我们只要在这条路径上每经过一条反向一次
反之也可以证明如果不存在一条长度偶数的欧拉回路,这个图一定找不到解。

所以贪心地将度数为奇数的点两两连边就好了。
最后总边数是奇数就随便找个点加自环,用如上方法构造答案

Code
const int N=4e5+5;
int n,m;
struct edge{int nxt,to;} e[N<<1];
int head[N],cnt=1;
il void add(int u,int v) {e[++cnt]={head[u],v};head[u]=cnt;}
int d[N],tot,t[N];
int ans[N],vis[N<<1];
void dfs(int u)
{
    for(int &amp;i=head[u];i;i=e[i].nxt)
    {
        if(vis[i]) continue;
        int v=e[i].to;
        vis[i]=vis[i^1]=1;
        dfs(v);
    }
    ans[++tot]=u;
}
int main()
{
    n=read(),m=read();
    for(int i=1;i<=m;i++)
    {
        int u=read(),v=read();
        add(u,v),add(v,u),d[u]++,d[v]++;
    }
    for(int i=1;i<=n;i++) if(d[i]&amp;1) t[++tot]=i;
    for(int i=1;i<=tot;i+=2) add(t[i],t[i+1]),add(t[i+1],t[i]),m++;
    if(m&amp;1) add(1,1),m++;
    tot=0,dfs(1);
    printf("%dn",m);
    for(int i=1;i<tot;i++) 
    {
        if(i&amp;1) printf("%d %dn",ans[i],ans[i+1]);
        else printf("%d %dn",ans[i+1],ans[i]);
    }
    return 0;
}

CF547D Mike and Fish

将点 ((x,y)) 看作横坐标(x) 的点向纵坐标(y) 的点连无向边。
我们要做的事情就是给这些无向边定向,使每个点的入度和出度至多相差 (1)。这看起来就很欧拉回路了:我们先把所有度数为奇数的点向一个虚拟点连边,使所有度数为偶数
根据边的方向颜色即可。

Code
#define pii pair<int,int>
const int N=4e5+5;
int n,L=2e5;
struct edge{int nxt,to;} e[N<<1];
int head[N],cnt=1;
il void add(int u,int v) {e[++cnt]={head[u],v};head[u]=cnt;}
int vis[N<<1],ans[N],tot,d[N],flag[N];
map<pii,int> mp;
void dfs(int u)
{
    for(int &amp;i=head[u];i;i=e[i].nxt)
    {
        if(vis[i]) continue;
        int v=e[i].to; vis[i]=vis[i^1]=1;
        dfs(v);
    }
    ans[++tot]=u,flag[u]=1;
}
char col[N];
int main()
{
    n=read();
    for(int i=1;i<=n;i++)
    {
        int x=read(),y=read();
        add(x,y+L),add(y+L,x),d[x]++,d[y+L]++;
        mp[pii(x,y+L)]=i,mp[pii(y+L,x)]=-i;
    }
    for(int i=1;i<=2*L;i++) if(d[i]&amp;1) add(0,i),add(i,0);
    for(int i=0;i<=2*L;i++) if(!flag[i]) dfs(i);
    for(int i=1;i<tot;i++)
    {
        if(!ans[i]||!ans[i+1]) continue;
        int id=mp[pii(ans[i],ans[i+1])];
        if(id>0) col[id]='b';
        else col[-id]='r';
    }
    for(int i=1;i<=n;i++) printf("%c",col[i]);
    printf("n");
    return 0;
}

[省选联考 2020 B 卷] 丁香之路

首先对这个神奇的距离定义式找性质:走 (xto y) 其实等价于走 (xto x+1to cdotsto y)
那么把边划分为题里要求的边和链上的相邻边,最优方案一定可以只经过题里要求的边恰好一次
设终点为 (i),一些边需要恰好经过一次其实就是要走一个 (s) 为起点,(i) 为终点的欧拉路径。但是起点终点不同不好搞,考虑连个无向边 (sto i),这样就是欧拉回路了。

然而还需要调整度数的奇偶性:从 (1)(n) 考虑每个点的度数,如果是奇数就连边 (ito i+1)。容易证明这样做最后所有点度数都是偶数。
还有一个问题是图不一定连通我们要用代价最少的边使这个图连通本质上是一个最小生成树。看起来有 (n^2) 条边不可做,但根据这个代价的性质只需要考虑相邻点的连边。
枚举终点即可,时间复杂度 (O(n^2log n))

Code
const int N=2505;
int n,m,s,sum,fa[N],y[N],ydeg[N],deg[N];
il int find(int x) {return fa[x]==x?x:fa[x]=find(fa[x]);}
il void merge(int x,int y) {if(find(x)!=find(y)) fa[find(x)]=find(y);}
struct edge{int u,v,w;} e[N];
il bool cmp(edge x,edge y) {return x.w<y.w;}
il int solve(int t)
{
    int ans=sum;
    for(int i=1;i<=n;i++) deg[i]=ydeg[i],fa[i]=y[i];
    deg[s]++,deg[t]++;
    for(int i=1;i<=n;i++) if(deg[i]&1) deg[i]++,deg[i+1]++,ans++,merge(i,i+1);
    int lst=0,tot=0;
    for(int i=1;i<=n;i++) if(deg[i])
    {
        if(lst) e[++tot]={lst,i,i-lst};
        lst=i;
    }
    sort(e+1,e+tot+1,cmp);
    for(int i=1;i<=tot;i++)
    {
        int u=e[i].u,v=e[i].v;
        if(find(u)!=find(v)) ans+=2*e[i].w,fa[find(u)]=find(v);
    }
    return ans;
}
int main()
{
    n=read(),m=read(),s=read();
    for(int i=1;i<=n;i++) fa[i]=i;
    for(int i=1;i<=m;i++)
    {
        int u=read(),v=read();
        ydeg[u]++,ydeg[v]++,sum+=abs(u-v),merge(u,v);
    }
    for(int i=1;i<=n;i++) y[i]=fa[i];
    for(int i=1;i<=n;i++) printf("%d ",solve(i)); printf("n");
    return 0;
}

AGC025E Walking on a Tree

考虑如果所有树边都被偶数条路径覆盖怎么做。

对于每条路径,连 (x_ito y_i) 的无向边。那么每个点的度数都为偶数,也就是每个连通子图都存在欧拉回路。我们先跑出欧拉回路,再根据回路中每条路径连的边被经过的方向来定向,则所有树边正反通过次数相同。

这个结论的证明就是对于一条树边 ((u,v)),我们把这棵树上的点划分成不经过这条边的两部分。因为我们没连树边,路径从 (u) 一侧到 (v) 一侧必须要经过一条路径边,又因为是回路,连接两部分之间的边正反向通过次数一定相同。

需要覆盖次数不全是偶数的情况。这时我们需要补一些边使所有点度数都是偶数。

仿照 [省选联考 2020 B 卷] 丁香之路 的思路,考虑从下到上 dfs 处理原树:如果处理完子树 (u),点 (u) 的度数仍是奇数,则连一条 (uto fa_u) 的无向边。
和之前同理,可以证明这样每条树边正反通过次数至多差 (1),为最优解。

给所有边定向后,树上差分(或直接暴力)即可统计所有树边的权值和。代码长是因为粘了一堆板子。

Code
const int N=10005;
typedef pair<int,int> pir;
map<pir,int> mp;
int n,m;
struct edge{int nxt,to;} e[N<<1];
int head[N],cnt=1;
il void add(int u,int v) {e[++cnt]={head[u],v};head[u]=cnt;}
vector<int> E[N];
struct LCA
{
    int dfn[N],fa[N],tot,dep[N],st[20][N];
    il int get(int x,int y) {return dep[x]<dep[y]?x:y;}
    void dfs(int u,int ff)
    {
        dfn[u]=++tot,st[0][tot]=ff,fa[u]=ff; dep[u]=dep[ff]+1;
		for(auto v:E[u]) if(v^ff) dfs(v,u);
    }
    il void init()
    {
        dfs(1,0);
        for(int i=1;(1<<i)<=n;i++)
            for(int j=1;j<=n-(1<<i)+1;j++)
                st[i][j]=get(st[i-1][j],st[i-1][j+(1<<i-1)]);
    }
    il int lca(int x,int y)
    {
        if(x==y) return x;
        if((x=dfn[x])>(y=dfn[y])) swap(x,y);
        int l=__lg(y-x);
        return get(st[l][x+1],st[l][y-(1<<l)+1]);
    }
}l;
int ans[N],St[N],Ed[N],vis[3][N],Vis[N<<1],deg[N];
int flag[N];
void getedge(int u)
{
	for(auto v:E[u]) if(v^l.fa[u])
	{
		getedge(v);
		if(deg[v]&1) deg[v]++,deg[u]++,add(u,v),add(v,u);
	}
}
vector<int> stk;
void dfs(int u)
{
	flag[u]=1;
	for(int &i=head[u];i;i=e[i].nxt)
	{
		int v=e[i].to; if(Vis[i]) continue;
		Vis[i]=Vis[i^1]=1,dfs(v);
	}
	stk.push_back(u);
}
void getvis(int u,int fa)
{
	for(auto v:E[u]) if(v^fa)
		getvis(v,u),vis[0][u]+=vis[0][v],vis[1][u]+=vis[1][v];
}
int main()
{
	n=read(),m=read();
	for(int i=1;i<n;i++)
	{
		int u=read(),v=read();
		E[u].push_back(v),E[v].push_back(u);
	}
	l.init();
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read(); St[i]=u,Ed[i]=v;
		add(u,v),add(v,u); deg[u]++,deg[v]++;
		mp[pir(u,v)]=i,mp[pir(v,u)]=-i;
	}
	getedge(1);
	for(int i=1;i<=n;i++) if(!flag[i]) dfs(i);
	reverse(stk.begin(),stk.end());
	for(int i=0;i+1<stk.size();i++) 
		if(mp.count(pir(stk[i],stk[i+1]))) 
		{
			int x=mp[pir(stk[i],stk[i+1])];
			ans[abs(x)]=x>0?0:1;
		}
	for(int i=1;i<=m;i++)
	{
		int u=St[i],v=Ed[i],x=ans[i];
		vis[x][u]++,vis[x][l.lca(u,v)]--;
		vis[x^1][v]++,vis[x^1][l.lca(u,v)]--;
	}
	getvis(1,0);
	int res=0;
	for(int i=1;i<=n;i++) res+=(vis[0][i]!=0)+(vis[1][i]!=0);
	printf("%dn",res);
	for(int i=1;i<=m;i++) 
		if(ans[i]==0) printf("%d %dn",St[i],Ed[i]);
		else printf("%d %dn",Ed[i],St[i]);
	return 0;
}

先写这些,剩下的再做到这类题再说。

原文地址:https://blog.csdn.net/yingxue_cat/article/details/134740788

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任

如若转载,请注明出处:http://www.7code.cn/show_28840.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱suwngjj01@126.com进行投诉反馈,一经查实,立即删除

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注