• [学习笔记] 邓老师调整法


    本篇博客和邓老师论文的区别就是不严谨有代码。

    简介

    组合优化问题有如下形式:一个问题有一些合法解和不合法解,每个合法解有一个对应的权值,你需要在所有合法解中找出权值最大的一个。

    一种显然的做法是:先任取一个合法解,然后对合法解进行微调使得权值变大,一直操作直到无法进行。这一算法看似简单,但在许多问题中有出色的表现。

    值得注意的是,这种调整方法得到的答案是不降的,它并不像模拟退火一样有一定概率接受劣解,所以调整方式不能让当前解被局限(能通过答案不降的路径调整到最优解\(/\)较优解)

    下面将结合不同的题目类型讲解邓老师调整法的应用,着重体会其思想。

    一般图最大匹配

    点此看题

    我们考虑一个未匹配的点集 \(V\),每次我们从中随机出一个点 \(u\),然后考虑 \(u\) 的邻接点中是否存在未配对的。如果存在那么随机一个直接匹配,答案\(+1\);否则我们随机一个已匹配的点 \(v\),断开 \(v\) 和其匹配点的边,然后把 \((u,v)\) 作为新的匹配加入。

    一直重复上述步骤到超时为止,很可惜的是无法通过 \(\tt extra\ test\)

    #include <cstdio>
    #include <vector>
    #include <cstdlib>
    #include <iostream> 
    #include <ctime>
    using namespace std;
    const int M = 505; 
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,k,ans,mat[M],s[M];vector<int> g[M];
    int random(int x)
    {
    	return (1ll*rand()*rand()+rand())%x;
    }
    void zxy()
    {
    	k=0;
    	for(int i=1;i<=n;i++)
    		if(!mat[i] && !g[i].empty()) s[++k]=i;
    	if(!k) return ;//perfect!
    	int u=s[random(k)+1];k=0;
    	for(int v:g[u]) if(!mat[v])
    		s[++k]=v;
    	if(k)//random a match node
    	{
    		int v=s[random(k)+1];
    		mat[u]=v;mat[v]=u;return ;
    	}
    	//adjust it
    	for(int v:g[u]) s[++k]=v;
    	int v=s[random(k)+1];
    	mat[mat[v]]=0;
    	mat[u]=v;mat[v]=u;
    }
    signed main()
    {
    	n=read();m=read();srand(time(0));
    	for(int i=1;i<=m;i++)
    	{
    		int u=read(),v=read();
    		g[u].push_back(v);
    		g[v].push_back(u);
    	}
    	while(1.0*clock()/CLOCKS_PER_SEC<=0.95) zxy();
    	for(int i=1;i<=n;i++)
    		if(mat[i]) ans++;
    	printf("%d\n",ans/2);
    	for(int i=1;i<=n;i++)
    		printf("%d ",mat[i]);
    }
    

    隐式匹配问题

    在信息学竞赛中,另有一大类问题与匹配相关,但常常无法转化成常规的一般图最大匹配,我们称其为隐式匹配问题。对于这类问题,前文提及的调整算法常常奏效。尽管复杂度并没有严格的证明,但往往有出色的实际表现。

    CF1168E Xor Permutations

    点此看题

    首先有一个简单的问题转化,我们原来是把 \(p,q\) 匹配到 \(a\),根据异或的特性我们把 \(p\)\(a\) 匹配到 \(q\),那么要求是找出一个排列,使得其和 \(x\) 的异或两两不同

    我们随机一个 \(p\) 中还未匹配的正整数,然后看它和 \(a\) 中哪个位置匹配后的值还没有出现过。设这个位置为 \(y\)(如果有多个随机一个),那么我们把 \((x,y)\) 加入匹配;如果不存在这样的 \(y\) 那么我们随机一个未匹配的位置 \(z\),然后把对应值原来的匹配断开,把 \((x,z)\) 加入匹配中。

    最后说一句,本题的正解当然是构造,有解的充要条件是:\(a\) 的异或和为 \(0\)

    #include <cstdio>
    #include <cstdlib>
    #include <cassert>
    #include <iostream>
    #include <ctime>
    using namespace std;
    const int M = (1<<12)+5;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,sum,ans,a[M],mp[M],mx[M],x[M],vis[M];
    int random(int x)
    {
    	return (rand()*rand()+rand())%x;
    }
    void zxy()
    {
    	m=0;
    	for(int i=0;i<n;i++)
    		if(mp[i]==-1) a[++m]=i;
    	if(!m) return ;
    	int u=a[random(m)+1];m=0;
    	for(int i=0;i<n;i++)
    		if(mx[i]==-1 && !vis[u^x[i]])
    			a[++m]=i;
    	if(m)//random match
    	{
    		int v=a[random(m)+1];
    		mp[u]=v;mx[v]=u;
    		vis[u^x[v]]=1;ans++;
    		return ;
    	}
    	for(int i=0;i<n;i++)
    		if(mx[i]==-1) a[++m]=i;
    	int v=a[random(m)+1];
    	//rip the former match
    	for(int i=0;i<n;i++)
    		if(~mp[i] && (i^x[mp[i]])==(u^x[v]))
    		{
    			mx[mp[i]]=-1;mp[i]=-1;
    			mp[u]=v;mx[v]=u;return ;
    		}
    }
    signed main()
    {
    	n=1<<read();srand(time(0));
    	for(int i=0;i<n;i++) mx[i]=mp[i]=-1;
    	for(int i=0;i<n;i++)
    		sum^=x[i]=read();
    	if(sum) {puts("Fou");return 0;}
    	while(1.0*clock()/CLOCKS_PER_SEC<=0.95) zxy();
    	puts("Shi");
    	for(int i=0;i<n;i++) printf("%d ",mx[i]);
    	puts("");
    	for(int i=0;i<n;i++) printf("%d ",mx[i]^x[i]);
    }
    

    制作团子

    点此看题

    每次我们随机一个未匹配的 \(\tt W\) 色点,并考虑以其为中心的竹签。如果存在一个竹签上不包含匹配点,我们在这样的竹签中随机选择一个添加。否则枚举以它为中心、且只与一根已有竹签冲突的竹签,我们有一半概率用新竹签替换原来的。

    下面给出我的实现,如果每个测试点跑 \(\tt 10s\) 可以在 \(\tt uoj\) 上获得 \(78\) 分,我相信跑更久能获得更多分数,所以这份实现虽然算不上好但是也大体没有问题,仅供参考。

    #include <cstdio>
    #include <vector>
    #include <cstdlib>
    #include <algorithm>
    #include <ctime> 
    using namespace std;
    const int M = 505;
    #define pb push_back
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,k,ans,cnt,o[4],a[M][M],t[M][M];
    int x[M*M],y[M*M];char s[M][M];
    struct node{int x,y,id;};vector<node> v;
    int random(int x)
    {
    	return (rand()*rand()+rand())%x;
    }
    int get(int &x,int &y,int c,int f)//f\in {-1,1}
    {
    	if(c==0) x+=f;
    	if(c==1) y+=f;
    	if(c==2) x+=f,y+=f;
    	if(c==3) x+=f,y-=f;
    	return x>=1 && y>=1 && x<=n && y<=m;
    }
    void clear(int i)
    {
    	int &c=t[x[i]][y[i]];
    	int x1=x[i],y1=y[i],x2=x1,y2=y1;
    	get(x1,y1,c,-1);get(x2,y2,c,1);
    	t[x1][y1]=t[x2][y2]=c=-1;
    	v.pb(node{x[i],y[i],i}); 
    }
    void zxy()
    {
    	if(v.empty()) return ;
    	int len=v.size(),p=random(len);
    	swap(v[p],v[len-1]);
    	int x=v.back().x,y=v.back().y,id=v.back().id;
    	for(int i=0;i<4;i++) o[i]=i;
    	random_shuffle(o,o+4);
    	for(int c=0;c<4;c++)
    	{
    		int i=o[c],x1=x,y1=y,x2=x,y2=y;
    		if(!get(x1,y1,i,-1) || !get(x2,y2,i,1))
    			continue;
    		if((a[x1][y1]^a[x2][y2])==3 &&
    			t[x1][y1]==-1 && t[x2][y2]==-1)
    		//successfully matched
    		{
    			ans++;
    			v.pop_back();
    			t[x1][y1]=t[x2][y2]=id;
    			t[x][y]=i;return ;
    		}
    	}
    	for(int c=0;c<4;c++)
    	{
    		int i=o[c],x1=x,y1=y,x2=x,y2=y;
    		if(!get(x1,y1,i,-1) || !get(x2,y2,i,1))
    			continue;
    		int p=1;
    		if((a[x1][y1]^a[x2][y2])==3
    		&& t[x1][y1]==-1 && p)
    		{
    			v.pop_back();
    			clear(t[x2][y2]);
    			t[x1][y1]=t[x2][y2]=id;
    			t[x][y]=i;return ;
    		}
    		if((a[x1][y1]^a[x2][y2])==3
    		&& t[x2][y2]==-1 && p)
    		{
    			v.pop_back();
    			clear(t[x1][y1]);
    			t[x1][y1]=t[x2][y2]=id;
    			t[x][y]=i;return ;
    		}
    	}
    }
    signed main()
    {
    	freopen("input_01.txt","r",stdin);
    	freopen("output_01.txt","w",stdout);
    	n=read();m=read();srand(time(0));
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%s",s[i]+1);
    		for(int j=1;j<=m;j++)
    		{
    			t[i][j]=-1;
    			if(s[i][j]=='P') a[i][j]=1;
    			if(s[i][j]=='G') a[i][j]=2;
    		}
    	}
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=m;j++) if(!a[i][j])
    		{
    			int s=a[i][j]|a[i-1][j]
    			|a[i+1][j]|a[i][j-1]|a[i][j+1]
    			|a[i-1][j-1]|a[i-1][j+1]
    			|a[i+1][j-1]|a[i+1][j+1];
    			if(s<3) continue;
    			v.pb(node{i,j,++k});
    			x[k]=i;y[k]=j;
    		}
    	while(1.0*clock()/CLOCKS_PER_SEC<=100) zxy();
    	for(int i=1;i<=n;i++,puts(""))
    		for(int j=1;j<=m;j++)
    		{
    			if(a[i][j]!=0)
    			{
    				putchar(s[i][j]);
    				continue;
    			}
    			if(t[i][j]==-1) putchar('W');
    			if(t[i][j]==0) putchar('|');
    			if(t[i][j]==1) putchar('-');
    			if(t[i][j]==2) putchar('\\');
    			if(t[i][j]==3) putchar('/');
    		}
    }
    

    图染色

    我们需要最小化两端点同色的边的数目。当这一数目被减少到 \(0\),我们便得到了合法的染色方案。

    考察这一种调整算法:首先为每个点随机染一种颜色。之后每次随机一个点,考虑固定其余点的颜色不变,该点染不同颜色对同色边数量的贡献,在贡献最少的颜色中等概率选取一个作为该点的颜色。

    有向图哈密顿链

    点此看题

    维护边的一个尽量大的子集,满足只考虑这些边时每个点出入度都不超过 \(1\),且不构成圈。如果子集大小达到 \(n-1\),则找到了一条哈密顿路。

    考虑调整维护子集,按随机顺序考虑边,如果加入后不构成圈,且加入之后所有点度数均仍合法,则加入这条边。否则如果不构成圈,但有一个点度数不合法,则以一半概率加入并把该点相连的与新加入边矛盾的边断掉,使用 \(\tt LCT\) 判断是否成圈。

    最后补充一些实现细节,为了保证随机数强度,建议使用 \(\tt mt19937\);此外的取边的时候建议把当前的所有边 \(\tt random\_shuffle\) 一遍,然后依次尝试能不能加入边集中,一定注意任何条件都不能作为删边的判据,要不然很可能调整不出来。

    现在我的代码可以跑出前 \(9\) 组数据,最后一个点还在跑,做提答题要有一定的耐心,至少要给 \(1\) 分钟跑。

    #include <cstdio>
    #include <vector>
    #include <random>
    #include <cstdlib>
    #include <cassert>
    #include <cstring>
    #include <iostream>
    #include <ctime>
    using namespace std;
    const int M = 500005;
    int read()
    {
    	int x=0,f=1;char c;
    	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
    	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
    	return x*f;
    }
    int n,m,ans,cnt,in[M],out[M],vis[M];
    int zxy,zp[M],p[M],fa[M],ch[M][2],fl[M],st[M];
    struct node{int x,y,id;}a[M],b[M];vector<node> g[M];
    int random(int x)
    {
    	static mt19937 fuck(time(0));
    	return fuck()%x;
    }
    //link-cut-tree
    int chk(int x)
    {
        return ch[fa[x]][1]==x;
    }
    int nrt(int x)
    {
        return ch[fa[x]][0]==x || ch[fa[x]][1]==x;
    }
    void flip(int x)
    {
    	if(!x) return ;
    	swap(ch[x][0],ch[x][1]);fl[x]^=1;
    }
    void down(int x)
    {
        if(!x) return ;
        if(fl[x])
    	{
    		flip(ch[x][0]);
    		flip(ch[x][1]);
    		fl[x]=0;
    	}
    }
    void rotate(int x)
    {
        int y=fa[x],z=fa[y],k=chk(x),w=ch[x][k^1];
        ch[y][k]=w;fa[w]=y;
        if(nrt(y)) ch[z][chk(y)]=x;fa[x]=z;
        ch[x][k^1]=y;fa[y]=x;
    }
    void splay(int x)
    {
        int z=x,t=0;st[++t]=z;
        while(nrt(z)) z=fa[z],st[++t]=z;
        while(t) down(st[t--]);
        while(nrt(x))
        {
        	int y=fa[x];
        	if(nrt(y))
        	{
        		if(chk(x)==chk(y)) rotate(y);
        		else rotate(x);
    		}
    		rotate(x);
    	}
    }
    void access(int x)
    {
    	for(int y=0;x;x=fa[y=x])
    		splay(x),ch[x][1]=y;
    }
    void makert(int x)
    {
    	access(x);splay(x);flip(x);
    }
    void link(int x,int y)
    {
    	makert(x);fa[x]=y;
    }
    void cut(int x,int y)
    {
    	makert(x);access(y);splay(x);
    	ch[x][1]=fa[y]=0;
    }
    int findrt(int x)
    {
    	access(x);splay(x);
    	while(ch[x][0]) down(x),x=ch[x][0];
    	splay(x);return x;
    }
    void work()
    {
    	swap(a[random(m)+1],a[m]);
    	int u=a[m].x,v=a[m].y,id=a[m].id;
    	if(findrt(u)==findrt(v)) return ;
    	if(!out[u] && !in[v])
    	{
    		out[u]=in[v]=id;link(u,v);
    		ans++;m--;return ;
    	}
    	if(out[u] && in[v]) return ;
    	int h=random(2);
    	if(out[u] && h)
    	{
    		int p=b[out[u]].x,q=b[out[u]].y;
    		a[m]=b[out[u]];
    		cut(p,q);out[p]=in[q]=0;
    		out[u]=in[v]=id;link(u,v);
    		return ;
    	}
    	if(h)
    	{
    		int p=b[in[v]].x,q=b[in[v]].y;
    		a[m]=b[in[v]];
    		cut(p,q);out[p]=in[q]=0;
    		out[u]=in[v]=id;link(u,v);
    	}
    }
    void dfs(int u)
    {
    	p[++cnt]=u;
    	for(node t:g[u]) if(!vis[t.id])
    		dfs(t.x);
    }
    signed main()
    {
    	freopen("hamil10.in","r",stdin);
    	freopen("hamil10.out","w",stdout);
    	n=read();m=read();srand(time(0));
    	for(int i=1;i<=10;i++) read();
    	for(int i=1;i<=m;i++)
    	{
    		a[i].x=read();a[i].y=read();
    		a[i].id=i;b[i]=a[i];
    		g[a[i].x].push_back(node{a[i].y,0,i});
    	}
    	while(1.0*clock()/CLOCKS_PER_SEC<=3600) work();
    	for(int i=1;i<=m;i++)
    		vis[a[i].id]=1;
    	for(int i=1;i<=n;i++) if(!in[i])
    	{
    		cnt=0;dfs(i);
    		if(cnt>zxy)c++
    			zxy=cnt,memcpy(zp,p,sizeof zp);
    	}
    	printf("%d\n",zxy);
    	for(int i=1;i<=zxy;i++)
    		printf("%d ",zp[i]);
    }
    
  • 相关阅读:
    Codeforces Round #321 (Div. 2) D. Kefa and Dishes(状压dp)
    51 Nod 1500 苹果曼和树(树形dp)
    UVa 557 汉堡
    POJ 2486 Apple Tree(树形dp)
    Codeforces Round #419 (Div. 2) E. Karen and Supermarket(树形dp)
    Codeforces Round #419 (Div. 2) B. Karen and Coffee(经典前缀和)
    Codeforces Round #419 (Div. 2) A. Karen and Morning(模拟)
    Codeforces Round #390 (Div. 2) D. Fedor and coupons(区间最大交集+优先队列)
    Codeforces Round #390 (Div. 2) C. Vladik and chat(dp)
    Codeforces Round #390 (Div. 2) A. Lesha and array splitting
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/15933902.html
Copyright © 2020-2023  润新知