搜索專題(練習題)

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;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章