搜索专题(练习题)

T1题面

首先我们很容易发现一个操作序列是否合法与操作序列的顺序是无关的,选定操作之后加上阶乘即可。

我们从小到大dfs,对于第i次操作我们把序列分成2^(n-i)段,每段长度2^i。

我们(用check函数)找到序列中不是连续递增的段,如果这样的段超过2个,这当然不可能,直接退出。

没有这样的段就不用操作。

有一段的话判断一下交换前后一半之后是否满足要求。

有两段的话和一段也很像。

#include<bits/stdc++.h>
using namespace std;
namespace program{
    long long n,bin[30],fac[30],a[50005];
    long long ans;
    inline bool check(long long pos,long long k){
        for(long long i=1;i<bin[k];i++)
            if(a[pos+i]^a[pos+i-1]+1)
                return 0;
        return 1;
    }//判断当前这段是否为严格上升序列实则返回1,不是则返回0; 
    inline void Swap(long long pos1,long long pos2,long long k){
        for(long long i=0;i<bin[k];i++)
            swap(a[pos1+i],a[pos2+i]);
    }//交换过程,一次换一段 
    inline void init(){
        bin[0]=1;
        for(long long i=1;i<=13;i++)
            bin[i]=bin[i-1]*2;
        fac[0]=1;
        for(long long i=1;i<=13;i++)
            fac[i]=fac[i-1]*i;
        cin>>n;
        for(long long i=1;i<=bin[n];i++)
            cin>>a[i];
    }//预处理 
    inline void dfs(long long k,long long now){//k表示当前序列分为2^(n-k)段,每段长度是2^k
        if(k==n+1){
            ans+=fac[now];//fac是阶乘 
            return;
        }
        long long pos1=0,pos2=0;//pos1表示第一段的头指针,pos2表示第二段的头指针 
        for(long long i=1;i<=bin[n];i+=bin[k]){
            if(!check(i,k)){
                if(!pos1){
                    pos1=i;//找到第一段 
                }else if(!pos2){
                    pos2=i;//找到第二段 
                }else{
                    return;//有三段了不可能直接跳掉 
                }
            }
        }
        if(!pos1&&!pos2){
            dfs(k+1,now);//一段都没有的话直接下一个 
        }else if(pos1&&!pos2){
            Swap(pos1,pos1+bin[k-1],k-1);
            dfs(k+1,now+1);
            Swap(pos1,pos1+bin[k-1],k-1);//只有一段的话直接把当前这段从中间分开,前后交换一下 
        }else{
            for(long long x=0;x<=1;x++){
                for(long long y=0;y<=1;y++){//如果有两段,那么正好有四种情况:
                //设序列1为A,序列2为B,可以是A前与B前,A前与B后,A后与B,A后与B后 共4种 
                //必定有一种是最优的 且可以是两段都严格上升 
                    Swap(pos1+x*bin[k-1],pos2+y*bin[k-1],k-1);
                    if(check(pos1,k)&&check(pos2,k)){//两段都已经严格上升是正确情况所以可以break
                        dfs(k+1,now+1);
                        Swap(pos1+x*bin[k-1],pos2+y*bin[k-1],k-1);
                        break;//退出 
                    }
                    Swap(pos1+x*bin[k-1],pos2+y*bin[k-1],k-1);//当前情况错误返回 
                }
            }
        }
    }
    inline void work(){
        init();
        dfs(0,0);
        cout<<ans<<'\n';
    }
}
int main(){
    program::work();
    return 0;
}

T2题面

这应该都看得出来时最大团的模板题吧

我太菜了只好用随机化搜索

详见注释

#include<bits/stdc++.h>
#define V (to[i])
#define N 60
using namespace std;
namespace program{
    int tot=0,head[N*N*2],to[N*N*2],Next[N*N*2];
    int n,b[N],ans,vis[N];
    template<class T>
    T read(){
        T s=0;
        int ch;
        while(!isdigit(ch=getchar()));
        do
            s=s*10+(ch^48);
        while(isdigit(ch=getchar()));
        return s;
    }
    inline void add(int x,int y){
        tot+=1;
        Next[tot]=head[x];
        to[tot]=y;
        head[x]=tot;
    }
    inline void init(){
        int a,bb;
        n=read<int>();
        while(~scanf("%d%d",&a,&bb)){
            add(a,bb);
            add(bb,a);
        }//双向连边 
        for(int i=1;i<=n;i++){
            b[i]=i;
        }//用来随机搜索的数组 
    }
    inline void dfs(int x,int sum){//sum记录当前搜到的团中节点个数   x表示当前搜到第x个点
        if(x==n+1){//已经搜完了记录答案退出 
            ans=max(ans,sum);
            return;
        }
        if(vis[b[x]]==sum){//如果当前的点访问数量等于团中节点数量,
        //说明他和每个节点都联通计入答案并且遍历与他相连的点否则答案不变 
            for(int i=head[b[x]];i;i=Next[i]){
                vis[V]+=1;//V已经宏定义为to[i] 
            }
            dfs(x+1,sum+1);//答案加一 
        }else{
            dfs(x+1,sum);//答案不变 
        }
    }
    inline void work(){
        init();
        ans=0;
        for(int i=1;i<=1000;i++){
            memset(vis,0,sizeof vis);//清空 
            random_shuffle(b+1,b+n+1);//随机打乱 
            dfs(1,0);
        }
        cout<<ans<<'\n';
    }
}
int main(){
    program::work();
    return 0;
}

T3题面

折半搜索

每个数有不选、选、选成阶乘三种方法。3^n 枚举前一半后一半,合并信息即可。

#include<bits/stdc++.h>
#define N 110
using namespace std;
namespace program{
    typedef pair<long long,long long> P; 
    map<P,long long>a,b;//a前面一半[P(n,k)]记录和为n用了k个!的方法数 ,b记录后面一半 
    long long n,K,S,val[N],fac[N];
    template<class T>
    T read(){
        T s=0;
        long long ch;
        while(!isdigit(ch=getchar()));
        do
            s=s*10+(ch^48);
        while(isdigit(ch=getchar()));
        return s;
    }
    inline void init(){
        fac[0]=fac[1]=1;
        for(long long i=2;i<=20;i++)
            fac[i]=fac[i-1]*i;//阶乘 
        cin>>n>>K>>S;
        a.clear();
        b.clear();
        for(long long i=1;i<=n;i++)
            scanf("%lld",val+i);
    }
    inline void dfs1(long long pos,long long k,long long sum){
        //pos记录当前位置,k记录!数量,sum记录当前的和 
        if(k>K)
            return;
        if(sum>S)
            return;//小剪枝 
        if(pos>n/2){//a是前一半,所以当前位置不能超过n/2
            a[P(sum,k)]+=1;//和为sum,用k个!的方法数+1
            return;
        }
        //一个数有三种情况,选  不选  阶乘 
        dfs1(pos+1,k,sum);//不选 
        dfs1(pos+1,k,sum+val[pos]);//选 
        if(val[pos]<=18){//根据数据范围S<=1e16,可知一定不会到19的阶乘 
            dfs1(pos+1,k+1,sum+fac[val[pos]]);//阶乘 
        }
    }
    inline void dfs2(long long pos,long long k,long long sum){//同上 
        if(k>K)
            return;
        if(sum>S)
            return;//小剪枝 
        if(pos>n){
            b[P(sum,k)]+=1;
            return;
        }
        dfs2(pos+1,k,sum);
        dfs2(pos+1,k,sum+val[pos]);
        if(val[pos]<=18){
            dfs2(pos+1,k+1,sum+fac[val[pos]]);
        }
    }
    inline void work(){
        init();
        dfs1(1,0,0);
        dfs2(n/2+1,0,0);
        long long res=0;
        map<P,long long>::iterator it=a.begin();
        for(it;it!=a.end();it++){
            long long j=(it->first).second;//j记录当前用的!数量 
            for(long long i=0;i+j<=K;i++){
                if(b.count(make_pair(S-(it->first).first,i)))//如果找到b中he加a中和正好为S的情况 
                    res+=it->second*b[make_pair(S-(it->first).first,i)];//加上a的方法数*b的方法数 
            }
        }
        cout<<res<<'\n';
    }
}
int main(){
    program::work();
    return 0;
}

T4题面

这题主要考剪枝

可行性剪枝1:如果当前位置只能向上走和向下走,则返回。(可以证明,走上去就不能再下来了,走下来就不能再上去了)

可行性剪枝2:如果当前位置只能向左走和向右走,则返回。(可以证明,走左边就不能再到右边去了,走右边就不能再到左边去了)

最优性剪枝:如果当前最大影响值比已知答案还要大,则返回。

#include<bits/stdc++.h>
#define N 50
using namespace std;
namespace program{
    long long n,m,k1,k2,p;
    bool limit[N][N];
    long long xx[N],yy[N],res;
    const long long dx[]={0,-1,0,1,0};
    const long long dy[]={0,0,-1,0,1};
    //走的方向不用多说了吧 
    inline void init(){
        memset(xx,0,sizeof xx);
        memset(yy,0,sizeof yy);
        cin>>n>>m>>k1>>k2;
        p=(n*m)/2;
        memset(limit,1,sizeof limit);
        for(long long i=1;i<=n;i++)
            for(long long j=1;j<=m;j++)
                limit[i][j]=0;
        limit[1][m]=1;//初始化把边界外和起点全部标记掉 
        res=99999999999999;
    }
    inline void dfs(long long num,long long x,long long y,long long ans){
    //num记录当前搜索到的点(从1~n*m),x,y记录当前座标,ans记录当前答案 
        long long oo=num%p,ans1=ans;
        if(num<=p){//如果点数还没过半则记录座标 
            xx[oo]=x;
            yy[oo]=y;
        }else{
            ans1=max(ans1,k1*(abs(xx[oo]-x))+k2*(abs(yy[oo]-y)));
            //若已过半,把此次答案与计算出来的结果取最大值 
            if(ans1>res)
                return;//如果当前答案已经超过已知最优解,跳掉 
        }
        if(num==n*m){
            res=min(res,ans1);
            return;//如果已经搜索完了,更新最优解 
        }
        if(limit[x-1][y]==limit[x+1][y]&&limit[x][y+1]==limit[x][y-1])
            if(limit[x-1][y]||limit[x][y+1])
                return;//可行性剪枝
        //若只能上下走或左右走就跳掉 
        for(long long i=1;i<=4;i++){//向四个方向走 
            long long x1=x+dx[i],y1=y+dy[i];
            if(!limit[x1][y1]&&(x1>=1&&x1<=n)&&(y1>=1&&y1<=m)){
                //能走到的点符合条件 
                limit[x][y]=1;//把当前点标记掉 
                dfs(num+1,x1,y1,ans1);//走到下一个店 
                limit[x][y]=0;//回溯 
            }
        }
    }
    inline void work(){
        init();
        dfs(1,1,m,0);//根据题意(从(1,m)出发) 
        cout<<res<<'\n';
        return;
    }
}
int main(){
    program::work();
    return 0;
}

点赞