算法集锦(普通版)

1 dp

1.1最长上升子序列(O(nlogn)时间复杂度)

dp[i]=max(dp[k])+1;(0<=k<i,a[i]>a[k])

多加一个数组c[i],用于记录最长上升子序列长为i的序列中,最小的最后一个数字。这样查找max(dp[k])时只用找c[i]数组了,而c[i]数组递增,可以用二分查找查询。


链接:hdu 1025 Constructing Roads In JGShining's Kingdom 最长上升子序列(nlogn)

代码:

void LongestIncreasingSubsequence(int n,int tt)
{
    int i,j,k;
    c[1]=e[0].y;
    t=1;
    for(i=1;i<n;i++)
    {
        if(e[i].y<c[1])c[1]=e[i].y;
        else if(e[i].y>c[t])c[++t]=e[i].y;
        else
        {
            k=lower_bound(c+1,c+t+1,e[i].y)-c;
            //c[k]=min(c[k],e[i].y);
            c[k]=e[i].y;//lower_bound保证了e[i].y<=c[k]
        }
    }
    if(t==1)printf("Case %d:\nMy king, at most %d road can be built.\n\n",tt,t);
    else printf("Case %d:\nMy king, at most %d roads can be built.\n\n",tt,t);
}

1.2 约瑟夫环

标准的约瑟夫环是f[n]=(f[n-1]+k)%n。

变形:变成第一个删除的数是m。那么我们开始的位置就是m-k+1,最后剩余的数时(m-k+1+f[n])%n(可能为负的,需要变换回来)

其中k表示是要删除第k个,n表示总人数,编号从0~n-1。


2.凸包

int ConvexHull(Point *p,Point *ch,int n)//求凸包
{
    sort(p,p+n);
    int i,m=0,k;
    for(i=0;i<n;i++)
    {
        while(m>1&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<=0)m--;
        ch[m++]=p[i];
    }
    k=m;
    for(i=n-2;i>=0;i--)
    {
        while(m>k&&Cross(ch[m-1]-ch[m-2],p[i]-ch[m-2])<=0)m--;
        ch[m++]=p[i];
    }
    if(n>1)m--;
    return m;
}


2.1.求凸包上最远点对

void solve(int m)//用旋转卡壳法求最长点对  
{  
    if(m==2)  
    {  
        printf("%d\n",dis(ch[0],ch[1]));  
        return;  
    }  
    int i,j,k;  
    i=j=0;  
    for(k=0;k<m;k++)  
    {  
        if(ch[i].x>ch[k].x)i=k;  
        if(ch[j].x<ch[k].x)j=k;  
    }  
    int res=0,si=i,sj=j;  
    //printf("%d %d\n",i,j);  
    while(i!=sj||j!=si)  
    {  
        res=max(res,dis(ch[i],ch[j]));  
        if(Cross(ch[(i+1)%m]-ch[i],ch[(j+1)%m]-ch[j])<0)i=(i+1)%m;  
        else j=(j+1)%m;  
    }  
    printf("%d\n",res);  
}

2.2.求两个凸包最近距离

void solve(Point *ch1,int t1,Point *ch2,int t2)
{
    int i,j,k;
    i=j=0;
    for(k=0;k<t1;k++)
        if(dcmp(ch1[k].x-ch1[i].x)<0)i=k;
        else if(dcmp(ch1[k].x-ch1[i].x)==0&&dcmp(ch1[k].y-ch1[i].y)>0)i=k;
    for(k=0;k<t2;k++)
        if(dcmp(ch2[k].x-ch2[j].x)>0)j=k;
        else if(dcmp(ch2[k].x-ch2[j].x)==0&&dcmp(ch2[k].y-ch2[j].y)<0)j=k;
    int si=i,sj=j;
    //printf("%d %d\n",i,j);
    double ans=INF;
    do
    {
        //printf("%d %d\n",i,j);
        k=dcmp(Cross(ch1[(i+1)%t1]-ch1[i],ch2[(j+1)%t2]-ch2[j]));
        if(k==0)
        {
            ans=min(ans,DisOfTwoSegment(ch1[i],ch1[(i+1)%t1],ch2[j],ch2[(j+1)%t2]));
            i=(i+1)%t1;
            j=(j+1)%t2;
            //if(i==si||j==sj)break;
        }
        else if(k<0)
        {
            ans=min(ans,DistanceToSegment(ch2[j],ch1[i],ch1[(i+1)%t1]));
            i=(i+1)%t1;
        }
        else
        {
            ans=min(ans,DistanceToSegment(ch1[i],ch2[j],ch2[(j+1)%t2]));
            j=(j+1)%t2;
        }
    }while(i!=si||j!=sj);
    printf("%.5f ",ans);
}

2.3判断两个凸包是否相交

//if(ConvexHullIntersection(ch1,t1,ch2,t2)&&ConvexHullIntersection(ch2,t2,ch1,t1))printf("YES\n");else printf("NO\n");
bool ConvexHullIntersection(Point *ch1,int t1,Point *ch2,int t2)//判断凸包是否相交,需要判两次
{
    double angle[maxn],x;
    int i,j,k,m;
    if(t1==1)return true;
    if(t1==2)
    {
        for(i=0;i<t2;i++)
        {
            k=dcmp(Cross(ch1[1]-ch1[0],ch2[i]-ch1[0]));
            if(k==0&&Dot(ch1[1]-ch1[0],ch2[i]-ch1[0])>0)
            {
                if(Length(ch2[i]-ch1[0])<Length(ch1[1]-ch1[0]))break;
            }
        }
        if(i<t2)return false;
        if(t2==2&&SegmentProperIntersection(ch1[0],ch1[1],ch2[0],ch2[1]))return false;//两条线段
        return true;
    }

    angle[0]=0;
    for(i=2;i<t1;i++)
    angle[i-1]=Angle(ch1[1]-ch1[0],ch1[i]-ch1[0]);
    for(i=0;i<t2;i++)
    {
        j=dcmp(Cross(ch1[1]-ch1[0],ch2[i]-ch1[0]));
        if(j<0||(j==0&&Dot(ch1[1]-ch1[0],ch2[i]-ch1[0])<0))continue;//除掉在凸包外以及凸包边线反向上的情况
        j=dcmp(Cross(ch1[t1-1]-ch1[0],ch2[i]-ch1[0]));
        if(j>0||(j==0&&Dot(ch1[t1-1]-ch1[0],ch2[i]-ch1[0])<0))continue;
        x=Angle(ch1[1]-ch1[0],ch2[i]-ch1[0]);
        m=lower_bound(angle,angle+t1-1,x)-angle;
        if(m==0)j=0;
        else j=m-1;
        k=dcmp(Cross(ch1[j+1]-ch2[i],ch1[j+2]-ch2[i]));
        if(k>=0)break;
    }
    if(i<t2)return false;
    return true;
}

3.图论

3.1 最大匹配

定义:给出一个二分图,找一个边数最大的匹配,即选尽量多的边,使得任意两条选中的边均没有公共点。如果所有点都是匹配点,则称这个匹配为完美匹配。

定理:
柯尼希定理:二分图最小点覆盖的点数=最大匹配数。
最小路径覆盖的边数=顶点数n-最大匹配数
最大独立集=最小路径覆盖=顶点数n-最大匹配数

增广路定理:用未盖点表示不与任何匹配边邻接的点,其他点位匹配点,即恰好和一条匹配边临界的点。从未盖点出发,依次经过非匹配边,匹配边,非匹配边,匹配边。。。所得到的路径称为交替路。注意,如果交替路的终点时一个未盖点,则称这条交替路位一条增广路。在增广路中,非匹配边比匹配边多一条。增广路的作用是改进匹配。如果有一条增广路,那么把此路上的匹配边和非匹配边互换,得到的匹配比刚才多一边。反过来,如果找不到增广路,则当前匹配就是最大匹配。

代码:

链接:hdu 1054 Strategic Game 最小顶点覆盖(二分图最大匹配)

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include <vector>
#include<algorithm>
using namespace std;

const int maxn=1510;
int n;
int pre[maxn];//保存各点的匹配点
int vis[maxn];
vector<int> e[maxn];
int find(int u)//判断是否存在增广路,存在返回1
{
    int i,v;
    for(i=0;i<e[u].size();i++)
    {
        v=e[u][i];
        if(vis[v])continue;
        vis[v]=1;
        if(pre[v]==-1||find(pre[v]))//找到未盖点,或者是增广路。
        {
            pre[v]=u;//匹配边和非匹配边交换
            return 1;
        }
    }
    return 0;
}

int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        int i,j,k,a,b,c,m;
        memset(pre,-1,sizeof(pre));
        for(i=0;i<n;i++)e[i].clear();
        for(i=0;i<n;i++)
        {
            scanf("%d:(%d)",&a,&m);
            for(j=0;j<m;j++)
            {
                scanf("%d",&b);
                e[a].push_back(b);
                e[b].push_back(a);
            }
        }
        int ans=0;
        for(i=0;i<n;i++)
        {
            memset(vis,0,sizeof(vis));
            ans+=find(i);
        }
        printf("%d\n",ans/2);
    }
    return 0;
}

3.2 最小树形图/有向图的最小生成树

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <cctype>
using namespace std;

const int INF=1<<30;
const int maxn=500+50;
const int maxm=2000+10;
struct edge{
    int u,v,w;
    edge(int u=0,int v=0,int w=0):u(u),v(v),w(w){}
}e[maxn+maxm];
int sum[maxn],a[maxn],tot,in[maxn],pre[maxn],id[maxn],vis[maxn];
void add(int u,int v,int w)
{
    e[tot++]=edge(u,v,w);
}
int Directed_MST(int root,int numv,int nume)//建有向图的最小生成树,其所有边的权值和酒是答案,复杂度O(VE)
{
    int i,j,k,u,v,ans=0;
    while(true)
    {
        for(i=0;i<numv;i++)in[i]=INF;
        for(i=0;i<nume;i++)
        {
            u=e[i].u;
            v=e[i].v;
            if(e[i].w<in[v]&&u!=v)
            {
                pre[v]=u;
                in[v]=e[i].w;
            }
        }
        for(i=0;i<numv;i++)
        {
            if(i==root)continue;
            if(in[i]==INF)return -1;//无法成树
        }
        //找环,合成一个新的顶点
        int t=0;
        memset(id,-1,sizeof(id));
        memset(vis,-1,sizeof(vis));
        in[root]=0;
        //标记每个环
        for(i=0;i<numv;i++)
        {
            ans+=in[i];
            v=i;
            while(vis[v]!=i&&id[v]==-1&&v!=root)
            {
                vis[v]=i;
                v=pre[v];
            }
            if(v!=root&&id[v]==-1)//存在环,标记相同的id
            {
                for(u=pre[v];u!=v;u=pre[u])
                    id[u]=t;
                id[v]=t++;
            }
        }
        if(t==0)break;//无环
        for(i=0;i<numv;i++)
            if(id[i]==-1)id[i]=t++;
        //缩点,重新标记序号
        for(i=0;i<nume;i++)
        {
            v=e[i].v;
            e[i].u=id[e[i].u];
            e[i].v=id[e[i].v];
            if(e[i].u!=e[i].v)
                e[i].w-=in[v];
        }
        numv=t;
        root=id[root];
    }
    return ans;
}
int main()
{
    int n,m;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        if(n==0&&m==0)break;
        int i,j,k;
        sum[0]=tot=0;
        for(i=0;i<n;i++)
        {
            scanf("%d",&a[i]);
            a[i]++;             //等级从1到a[i]开始
            sum[i+1]=sum[i]+a[i];
        }
        //将所有等级作为一个节点,对于等级i,可以建一条对等级i-1的边,边权为0
        //其中sum[n]为虚拟的跟,指向所有的课程的level0的点。
        for(i=0;i<n;i++)
        {
            for(j=sum[i+1]-1;j>sum[i];j--)add(j,j-1,0);
            add(sum[n],sum[i],0);
        }
        int c,d,l1,l2,money;
        for(i=0;i<m;i++)
        {
            scanf("%d%d%d%d%d",&c,&l1,&d,&l2,&money);
            add(sum[c-1]+l1,sum[d-1]+l2,money);
        }
        printf("%d\n",Directed_MST(sum[n],sum[n]+1,tot));
    }
    return 0;
}


4.数据结构

4.1 字典树

用树的形式像字典那样保存大量的字符串。

代码:

链接:uvalive 3942 Remember the Word 字典树+dp

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <vector>
using namespace std;

#define LL long long
const int maxn=4001*101;
const int maxm=3e5+10;
const int mod=20071027;
struct Trie{
    int ch[maxn][26];
    int val[maxn];
    int sz;
    Trie(){sz=1;memset(ch[0],0,sizeof(ch[0]));}
    int idx(char c){return c-'a';}

    void insert(char *s,int v)
    {
        int i,u=0,n=strlen(s);
        for(i=0;i<n;i++)
        {
            int c=idx(s[i]);
            if(!ch[u][c])
            {
                memset(ch[sz],0,sizeof(ch[sz]));
                val[sz]=0;
                ch[u][c]=sz++;
            }
            u=ch[u][c];
        }
        val[u]=v;
    }
}e;
char str[maxm];
char a[101];
LL dp[maxm];
int main()
{
    int tt=0;
    while(scanf("%s",str)!=EOF)
    {
        int i,j,k,n,m,u;
        scanf("%d",&n);
        memset(e.ch[0],0,sizeof(e.ch[0]));
        e.sz=1;
        for(i=0;i<n;i++)
        {
            scanf("%s",a);
            e.insert(a,1);
        }
        m=strlen(str);
        memset(dp,0,sizeof(dp));
        dp[m]=1;
        for(i=m-1;i>=0;i--)
        {
            u=0;
            for(j=i;j<m;j++)
            {
                int c=e.idx(str[j]);
                if(!e.ch[u][c])break;
                u=e.ch[u][c];
                if(e.val[u])dp[i]+=dp[j+1];
            }
            dp[i]%=mod;
        }
        printf("Case %d: %lld\n",++tt,dp[0]);
    }
    return 0;
}



4.2 KMP算法

用于处理字符串匹配问题。

代码:
失配函数:
在用m长字符串P去匹配n长字符串T时,需要先求T的失配函数f[i],即在匹配到i的时候发现跟T中字符不匹配,如果是朴素算法则是返回到P的开头重新匹配,而KMP算法则用f[i]记录f[i]之前的字符串等于i位置之前的字符串,也就是说已经确定了的部分不需要再匹配,时间复杂度降到了O(n+m)。
KMP算法:
这里是MP算法,KMP算法还需要对失配函数进行优化,不过已经最够时候。

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <vector>
using namespace std;

const int maxn=1e5+10;
char a[maxn],b[maxn],c[30],d[30];
int f[maxn];
void getFail(char *P,int *f)//求失配函数
{
    int m=strlen(P);
    f[0]=f[1]=0;
    for(int i=1;i<m;i++)
    {
        int j=f[i];
        while(j&&P[i]!=P[j])j=f[j];
        f[i+1]=P[i]==P[j]?j+1:0;
    }
}
int find(char *T,char *P,int *f)//KMP算法
{
    int n=strlen(T),m=strlen(P);
    getFail(P,f);
    int j=0;
    for(int i=0;i<n;i++)
    {
        while(j&&P[j]!=T[i])j=f[j];
        if(P[j]==T[i])j++;

        //if(j==m)printf("%d\n",i-m+1);
    }
    return j;
}
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%s%s",c,a);
        int i,j,k,n;
        for(i=0;i<26;i++)
        {
            d[c[i]-'a']='a'+i;
        }
        n=strlen(a);
        for(i=0;i<n;i++)b[i]=d[a[i]-'a'];
        k=find(a+(n+1)/2,b,f);
        //printf("%d\n",k);
        for(i=0;i<n-k;i++)printf("%c",a[i]);
        for(i=0;i<n-k;i++)printf("%c",b[i]);
        printf("\n");
    }
    return 0;
}

4.3 manacher算法

求最长回文子串长度的算法。

回文分为两种:偶数长度和奇数长度。为了简便,我们可以将偶数长度的变成奇数长度的。怎么变,在各个字符之间加个不存在的符号,例如#。举例来说adda,可以变成#a#d#d#a#,即变为奇数的回文;同样的奇数长度的加#后还是奇数长度的最长回文。

接着我们定义dp[i]为以第i个字符为中心的回文半径。由于变成了奇数回文,所以回文半径=(回文长度+1)/2;又由于加了#,所以真实回文长度=改变后的回文半径-1。

我们枚举各个字符位置i,若存在一个i,使得i+dp[i]最大,我们用x=i,。然后分情况讨论:

1)i>x+dp[x]时,dp[i]=1,然后用while(a[i+dp[i]]==a[i-dp[i]])dp[i]++;向两边拓展。

2)i<=x+dp[x]时:由于x是回文,所以a[i]=a[2*i-x],之后就看dp[2*i-x]是否等于x+dp[x]-i了,也就是2*i-x的回文半径是否在x的回文半径内。若不相等,我们很容易证明dp[i]=min(dp[2*i-x],x+dp[x]-i);若相等则还需要用while向左右拓展。

在算法过程中,我们用maxv记录最长的回文半径即可。

注意:拓展的时候有可能越界,所以可以加越界判断,或者是两端字符加上不存在在的不同字符。


代码:

链接:hdu 3068 最长回文 manacher算法

#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <vector>
using namespace std;

const int maxn=11e4+10;
char a[maxn*2];
int dp[maxn*2];
int Manacher(char *a,int *dp,int n)//求最大回文子串长度
{
    int i,j,k,x=0,maxv=0;
    for(i=2;i<2*n+1;i++)
    {
        if(x+dp[x]>i)dp[i]=min(dp[2*x-i],x+dp[x]-i);
        else dp[i]=1;
        while(a[i-dp[i]]==a[i+dp[i]])dp[i]++;
        if(x+dp[x]<i+dp[i])x=i;
        maxv=max(maxv,dp[i]);
    }
    return maxv-1;
}
int main()
{
    while(scanf("%s",a)!=EOF)
    {
        int i,n;
        n=strlen(a);
        for(i=n;i>=0;i--)
        {
            a[2*i+2]=a[i];
            a[2*i+1]='#';
        }
        a[0]='*';//a[2*n+2]='\0',防止在manacher算法的while循环中溢出
        printf("%d\n",Manacher(a,dp,n));
    }
}


5.数学

5.1 快速傅里叶变换/FFT

简单的来说就是求用nlogn的时间求得两个多项式乘积的系数。

代码:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#include <cmath>
using namespace std;
#define maxn 400040
#define LL __int64
const double pi=atan(1.0)*4;
struct complex{//复数,重载+,-,*
    double a,b;
    complex(double aa=0,double bb=0){a=aa;b=bb;}
    complex operator +(const complex &e){return complex(a+e.a,b+e.b);}
    complex operator -(const complex &e){return complex(a-e.a,b-e.b);}
    complex operator *(const complex &e){return complex(a*e.a-b*e.b,a*e.b+b*e.a);}
};
void change(complex y[],LL len)
{
    LL i,j,k;
    for(i=1,j=len/2;i<len-1;i++)
    {
        if(i<j)swap(y[i],y[j]);
        k=len/2;
        while(j>=k)
        {
            j-=k;
            k/=2;
        }
        if(j<k)j+=k;
    }
}
//FFT快速傅里叶变换的模板,用以将多项式系数转换成单位根(??应该是),这样得到的两个序列逐个相乘到得就是系数
//序列的卷积,即两个多项式乘积后的系数值
void fft(complex y[],LL len,LL on)
{
    change(y,len);
    LL i,j,k,h;
    for(h=2;h<=len;h<<=1)
    {
        complex wn(cos(-on*2*pi/h),sin(-on*2*pi/h));
        for(j=0;j<len;j+=h)
        {
            complex w(1,0);
            for(LL k=j;k<j+h/2;k++)
            {
                complex u=y[k];
                complex t=w*y[k+h/2];
                y[k]=u+t;
                y[k+h/2]=u-t;
                w=w*wn;
            }
        }
    }
    if(on==-1)
        for(i=0;i<len;i++)
            y[i].a/=len;
}
LL num[maxn],sum[maxn];
complex x[maxn];
LL a[maxn/4];
int main()
{
    LL T;
    scanf("%I64d",&T);
    while(T--)
    {
        LL n,i,j,k,maxa=-1;
        scanf("%I64d",&n);
        memset(num,0,sizeof(num));
        for(i=0;i<n;i++)
        {
            scanf("%I64d",&a[i]);
            //存储个数,用以表示多项式的系数,其中未知数的指数表示长度,这样两个相同多项式乘积的系数表示取两根木棍
            //其和是对应指数的组合个数(多项式相乘,系数相乘,指数相加)
            num[a[i]]++;
            maxa=max(maxa,a[i]);
        }
        sort(a,a+n);
        LL len1,len=1,ans=0;
        len1=maxa+1;
        //两个多项式长n,m,那么乘积长n+m-1,
        //这里求最接近且大于n+m-1的2^k,方便二叉树形式的运用吧
        while(len<2*len1)len<<=1;
        for(i=0;i<len1;i++)
            x[i]=complex(num[i],0);
        for(i=len1;i<len;i++)
            x[i]=complex(0,0);
        fft(x,len,1);//FFT转换成单位根形式
        for(i=0;i<len;i++)
            x[i]=x[i]*x[i];//卷积
        fft(x,len,-1);//结果转换回来
        for(i=0;i<len;i++)
            num[i]=(LL)(x[i].a+0.5);
        len=maxa*2;
        //去掉取形同木棍
        for(i=0;i<n;i++)
            num[a[i]*2]--;
        //去掉两根木棍左右交换的重复
        for(i=1;i<=len;i++)
            num[i]/=2;
        sum[0]=0;
        for(i=1;i<=len;i++)
            sum[i]=sum[i-1]+num[i];
        for(i=0;i<n;i++)
        {
            //两根木棍和大于a[i]的个数,三角形任意两边之和大于第三边,这里取的是较小两边,所以要减去大于a[i]的木棍
            ans+=sum[len]-sum[a[i]];
            //木棍,一根小于a[i],一根大于a[i](这里的大于,小于只是说在i的前后位置)
            ans-=(n-1-i)*i;
            //有根木棍取了自身的
            ans-=n-1;
            //两根木棍都大于a[i]
            ans-=(n-1-i)*(n-2-i)/2;
        }
        LL t=n*(n-1)*(n-2)/6;
        printf("%.7lf\n",(double)ans/t);
    }
    return 0;
}

6 精确覆盖问题和DLX算法

精确覆盖问题(Exact Cover Problem)。有一些由整数1~n组成的集合S1~Sr,要求选择若干个集合Si,使得1~n的每个整数恰好在一个集合中出现,比如n=7,S1={1,4,7},S2={1,4},S3={4,5,7},S4={3,5,6},S5={2,3,6,7},S6={2,7},则一个精确覆盖为S2,S4,S6,因为{1,4},{3,5,6},{2,7}无重复、无遗漏地包含了1~7的所有整数。
我们可以用一个r行n列的01矩阵表示一个精确覆盖问题,其中第i行第j个元素表示Si是否包含元素j(1表示包含,0表示不包含)。
算法X(Algorithm X)。和普通的回溯法一样,我们可以编写一个递归过程求解精确覆盖问题。每次选择一个没有覆盖的元素,然后选择一个包含它的集合进行覆盖。对应到矩阵中,如果我们删除所有已经选择的行和已覆盖的列,则这个递归过程可以描述为:每次选择一个没有被删除的列,然后枚举该列为1的所有行,尝试删除该行,递归搜索后恢复该行。删除行时,除了需标记“此行已删除”之外,同时还需要把改行中值为1的列删除,恢复时也同样。
舞蹈链(Dancing Links)。删除列并不是一个简单的操作,因为还需要覆盖它的行业一并删除掉(因为每列只能被一行所覆盖)。为了提高效率,我们需要一种恩能够高效支持上述操作的数据结构,它就是Knuth的舞蹈链(Dancing Links)。使用了舞蹈链的算法X通常称为DLX算法。

代码:
1)精确覆盖(DLX)
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <cctype>
using namespace std;

const int maxn=10;
const int maxnode=100;
const int maxr=10;
struct DLX{
    int n,sz;                                       //列数,结点总数
    int S[maxn];                                    //各列结点数

    int row[maxnode],col[maxnode];                  //各结点行列编号
    int L[maxnode],R[maxnode],U[maxnode],D[maxnode];//十字链表

    int ansd,ans[maxr];                             //解

    void init(int n)
    {
        this->n=n;

        //虚拟结点
        for(int i=0;i<=n;i++)
        {
            U[i]=i;D[i]=i;L[i]=i-1;R[i]=i+1;
        }
        R[n]=0;L[0]=n;
        sz=n+1;
        memset(S,0,sizeof(S));
    }
    void addRow(int r,vector<int>columns)
    {
        int first=sz;
        for(int i=0;i<columns.size();i++)
        {
            int c=columns[i];
            L[sz]=sz-1;R[sz]=sz+1;D[sz]=c;U[sz]=U[c];
            D[U[c]]=sz;U[c]=sz;
            row[sz]=r;col[sz]=c;
            S[c]++;sz++;
        }
        R[sz-1]=first;L[first]=sz-1;
    }

    //顺着链表A,遍历除s外的其他元素
    #define FOR(i,A,s) for(int i=A[s];i!=s;i=A[i])

    void remove(int c)
    {
        L[R[c]]=L[c];
        R[L[c]]=R[c];
        FOR(i,D,c)
            FOR(j,R,i){U[D[j]]=U[j];D[U[j]]=D[j];--S[col[j]];}
    }

    void restore(int c)
    {
        FOR(i,U,c)
            FOR(j,L,i){++S[col[j]];U[D[j]]=j;D[U[j]]=j;}
        L[R[c]]=c;
        R[L[c]]=c;
    }

    //d为递归深度
    bool dfs(int d)
    {
        if(R[0]==0)                      //找到解
        {
            ansd=d;                      //记录解得长度
            return true;<span style="white-space:pre">		</span>//只找到一个符合解,但不一定是最短的解
        }
        //找S最小的列c
        int c=R[0];                      //第一个为删除的列
        FOR(i,R,0)if(S[i]<S[c])c=i;

        remove(c);                      //删除第c列
        FOR(i,D,c)                      //用结点i所在行覆盖第c列
        {
            ans[d]=row[i];
            FOR(j,R,i)remove(col[j]);   //删除结点i所在行能覆盖的所有其他列
            if(dfs(d+1))return true;
            FOR(j,L,i)restore(col[j]);  //恢复结点i所在行能覆盖的所有其他列
        }
        restore(c);                     //恢复第c列
        return false;
    }
    bool solve(vector<int>&v)
    {
        v.clear();
        if(!dfs(0))return false;
        for(int i=0;i<ansd;i++)v.push_back(ans[i]);
        return true;
    }
}dlx;
vector<int>row[maxr];
vector<int>v;
int main()
{
    int r,n;
    while(scanf("%d%d",&r,&n)!=EOF)
    {
        if(r==0&&n==0)break;
        int i,j,k,m,x;
        for(i=1;i<=r;i++)row[i].clear();
        dlx.init(n);
        for(i=1;i<=r;i++)
        {
            scanf("%d",&m);
            for(j=0;j<m;j++)
            {
                scanf("%d",&x);
                row[i].push_back(x);
            }
            dlx.addRow(i,row[i]);
        }
        if(!dlx.solve(v))printf("Fail.\n");
        else
        {
            printf("Good.\n");
            for(i=0;i<v.size();i++)
                printf("%d ",v[i]);
            printf("\n");
        }
    }
    return 0;
}
/*
6 7
3 1 4 7
2 1 4
3 4 5 7
3 3 5 6
4 2 3 6 7
2 2 7
*/

2)精确覆盖(指针+DLX)
题目:ZOJ3209
#include<cstdio>
#define N 505
#define M 1005

int m,n,H,cnt,size[M],ans;
struct Node
{
    int r,c;
    Node *U,*D,*L,*R;
}node[40005],row[N],col[M],head;
void init(int r,int c)
{
    cnt=0;
    head.r=r;
    head.c=c;
    head.L=head.R=head.U=head.D=&head;
    for(int i=0;i<c;i++)
	{
        col[i].r=r;
        col[i].c=i;
        col[i].L=&head;
        col[i].R=head.R;
        col[i].U=col[i].D=col[i].L->R=col[i].R->L=&col[i];
        size[i]=0;
    }
    for(int i=r-1;i>=0;i--)
	{
        row[i].r=i;
        row[i].c=c;
        row[i].U=&head;
        row[i].D=head.D;
        row[i].L=row[i].R=row[i].U->D=row[i].D->U=&row[i];
    }
}
void insert(int r,int c)
{
    Node *p=&node[cnt++];
    p->r=r;
    p->c=c;
    p->R=&row[r];
    p->L=row[r].L;
    p->L->R=p->R->L=p;
    p->U=&col[c];
    p->D=col[c].D;
    p->U->D=p->D->U=p;
    ++size[c];
}
void delLR(Node *p)
{
    p->L->R=p->R;
    p->R->L=p->L;
}
void delUD(Node *p)
{
    p->U->D=p->D;
    p->D->U=p->U;
}
void resumeLR(Node *p)
{p->L->R=p->R->L=p;}
void resumeUD(Node *p)
{p->U->D=p->D->U=p;}
void cover(int c)
{
    if(c==H)
        return;
	Node *R,*C;
    delLR(&col[c]);
    for(C=col[c].D;C!=&col[c];C=C->D)
		for(R=C->L;R!=C;R=R->L)
		{
			--size[R->c];
			delUD(R);
		}
}
void resume(int c)
{
    if(c==H)
        return;
    Node *R,*C;
    for(C=col[c].U;C!=&col[c];C=C->U)
		for(R=C->R;R!=C;R=R->R)
		{
			++size[R->c];
			resumeUD(R);
		}
    resumeLR(&col[c]);
}
void dfs(int k)
{
    if(head.L==&head)
	{
		if(k<ans)
			ans=k;
		return;
	}
	if(k>=ans)
		return;
    int INF=-1u>>1,c=-1;
	Node *p,*rc;
    for(p=head.L;p!=&head;p=p->L)
        if(size[p->c]<INF)
            INF=size[c=p->c];
	if(!INF)
		return;
    cover(c);
    for(p=col[c].D;p!=&col[c];p=p->D)
	{
        for(rc=p->L;rc!=p;rc=rc->L)
            cover(rc->c);
        dfs(k+1);
        for(rc=p->R;rc!=p;rc=rc->R)
            resume(rc->c);
    }
    resume(c);
}
int main()
{
	int t,p;
	scanf("%d",&t);
    while(t--)
	{
		int i,j,k,x0,x1,y0,y1;
		scanf("%d%d%d",&n,&m,&p);
		init(p+1,H=n*m);
		for(k=0;k<p;k++)
		{
			scanf("%d%d%d%d",&x0,&y0,&x1,&y1);
			for(i=x0;i<x1;i++)
				for(j=y0;j<y1;j++)
					insert(k,i*m+j);
		}
		ans=999;
		dfs(0);
		if(ans<999)
			printf("%d\n",ans);
		else
			puts("-1");
	}
}

3)多次覆盖(DLX+A*)
代码:
#include <cstdio>
#include <cstring>
#include <cmath>
#include <cstdlib>
#include <iostream>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <cctype>
#include <ctime>
using namespace std;

const int maxn=100;
const int maxnode=1000;
const int maxr=100;
const int INF=1e9;
struct DLX{
    int n,sz;                                       //列数,结点总数
    int S[maxn];                                    //各列结点数

    int row[maxnode],col[maxnode];                  //各结点行列编号
    int L[maxnode],R[maxnode],U[maxnode],D[maxnode];//十字链表

    int ansd,ans[maxr];                             //解

    int vis[maxn];                                  //A*算法时标记求过的列
    void init(int n)
    {
        this->n=n;

        //虚拟结点
        for(int i=0;i<=n;i++)
        {
            U[i]=i;D[i]=i;L[i]=i-1;R[i]=i+1;
        }
        R[n]=0;L[0]=n;
        sz=n+1;
        memset(S,0,sizeof(S));
        ansd=INF;
    }
    void addRow(int r,vector<int>columns)
    {
        int first=sz;
        for(int i=0;i<columns.size();i++)
        {
            int c=columns[i];
            L[sz]=sz-1;R[sz]=sz+1;D[sz]=c;U[sz]=U[c];
            D[U[c]]=sz;U[c]=sz;
            row[sz]=r;col[sz]=c;
            S[c]++;sz++;
        }
        R[sz-1]=first;L[first]=sz-1;
    }

    //顺着链表A,遍历除s外的其他元素
    #define FOR(i,A,s) for(int i=A[s];i!=s;i=A[i])

    void remove(int c)
    {
        FOR(i,D,c)
        {
            L[R[i]]=L[i];
            R[L[i]]=R[i];
        }
    }

    void restore(int c)
    {
        FOR(i,U,c)
        {
            L[R[i]]=i;
            R[L[i]]=i;
        }
    }
    int A()//A*思想,求出要找到结果至少还要选择的行数
    {
        int i,j,k,res=0;
        memset(vis,0,sizeof(vis));
        FOR(i,R,0)
        {
            if(!vis[i])
            {
                res++;
                vis[i]=1;
                FOR(j,D,i)
                    FOR(k,R,j)
                        vis[col[k]]=1;
            }
        }
        return res;
    }
    //d为递归深度
    void dfs(int d)
    {
        if(R[0]==0)                      //找到解
        {
            ansd=min(ansd,d);                      //记录解得长度
            return ;
        }
        if(d+A()>=ansd)return ;
        //找S最小的列c
        int c=R[0];                      //第一个为删除的列
        FOR(i,R,0)if(S[i]<S[c])c=i;

        //remove(c);                      //删除第c列
        FOR(i,D,c)                      //用结点i所在行覆盖第c列
        {
            remove(i);
            FOR(j,R,i)remove(j);   //删除结点i所在行能覆盖的所有其他列
            dfs(d+1);
            FOR(j,L,i)restore(j);  //恢复结点i所在行能覆盖的所有其他列
            restore(i);
        }
        //restore(c);                     //恢复第c列
    }
    int solve()
    {
        dfs(0);
        if(ansd==INF)return 0;
        return ansd;
    }
}dlx;
vector<int>row[maxr];
vector<int>v;
int c[10][10];
int vis[1025];
int bitcount[1025];
int get(int x)
{
    int ans=0;
    while(x)
    {
        ans+=(x&1);
        x=x>>1;
    }
    return ans;
}
void init()
{
    int i,j,k;
    memset(c,0,sizeof(c));
    c[0][0]=1;
    for(i=1;i<=8;i++)
    {
        c[i][0]=1;
        for(j=1;j<=i;j++)
            c[i][j]=c[i-1][j]+c[i-1][j-1];
    }
    for(i=0;i<(1<<8);i++)
    {
        bitcount[i]=get(i);
    }
}
int main()
{
    freopen("D:\\in.txt","r",stdin);
    freopen("D:\\out.txt","w",stdout);
    double a = clock();
    init();
    int n,m,r;
    while(scanf("%d%d%d",&n,&m,&r)!=EOF)
    {
        int i,j,k,t=0;
        dlx.init(c[n][r]);
        memset(vis,0,sizeof(vis));
        for(i=1,k=0;i<(1<<n);i++)
        {
            if(bitcount[i]==r)vis[i]=++k;
        }
        for(i=1;i<=c[n][m];i++)
        {
            row[i].clear();
        }
        for(i=1;i<(1<<n);i++)
        {
            if(bitcount[i]==m)
            {
                t++;
                for(j=i;j;j=(j-1)&i)
                {
                    if(bitcount[j]==r)
                        row[t].push_back(vis[j]);
                }
                dlx.addRow(t,row[t]);
            }
        }
        printf("%d %d %d:%d\n",n,m,r,dlx.solve());
    }
    double b = clock();
    printf("%lf\n", (b - a) / CLOCKS_PER_SEC); //运行时间
    return 0;
}

7


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值