DFS之搜索顺序与剪枝

搜索顺序:

1.https://www.acwing.com/problem/content/1119/

首先,我们考虑一个贪心:

假如说A的倒数K个字符恰好与B的前K个字符重合,那么我们就连接。

也就是说我们一旦匹配就直接相连而不是继续找更长的重合的一段子串。

因此,我们可以先预处理出任意两个字符串重叠的部分,接下来就是DFS了:

AC代码:

#include<bits/stdc++.h>
using namespace std;
int n;
int g[100][100];//i的尾与j的头重叠的最小长度
string w[100];
int u[1000];
int ans;
void dfs(string dd,int last){
    ans=max(ans,(int)dd.size());
    u[last]++;
    for(int i=1;i<=n;i++){
        if(g[last][i]&&u[i]<2){
            dfs(dd+w[i].substr(g[last][i]),i);
        }
    }
    u[last]--;
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>w[i];
    char start;
    cin>>start;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            //if(i==j) continue;
            for(int k=1;k<min(w[i].size(),w[j].size());k++){
                if(w[i].substr(w[i].size()-k)==w[j].substr(0,k)){
                    g[i][j]=k;
                    break;
                }
            }
        }
    }
    for(int i=1;i<=n;i++){
        if(w[i][0]==start){
            dfs(w[i],i);
        }
    }
    cout<<ans;
}

2.https://www.acwing.com/problem/content/1120/

首先,数据范围小,直接考虑爆搜。

那么选什么搜索顺序?我们按照每一个“桶”里放什么元素来DFS。

具体的,我们先看看第一个桶里放什么,当然存在某个时刻,第一个桶再也放不下所有东西了,于是我们就再开一个桶。

这里存在一个选择:当现在的桶还可以放东西时,是否还有必要再开一个桶?

答案是否定的,因为假如再开一桶,把那个东西放到上一个桶中一定也是合法的,因此答案不会更优。

同时注意我们选取东西不是DFS排列而是组合(因此规定从小到大)。

AC代码:

#include<bits/stdc++.h>
using namespace std;
int n;
int a[1010];
int ans=20;
int group[20][20];
int gcd(int a, int b)  // 欧几里得算法
{
    return b ? gcd(b, a % b) : a;
}
int check(int u,int j,int cnt1){
    for(int i=1;i<=cnt1;i++){
        if(gcd(a[group[u][i]],a[j])!=1) return 0;
    }
    return 1;
}
int vis[100];
void dfs(int u,int cnt,int start,int cnt1){
    if(u>=ans) return;
    if(cnt==n){
        ans=u;
        return;
    }
    int f=-1;
    for(int i=start+1;i<=n;i++){
        if(vis[i]==0&&check(u,i,cnt1)){
            f=1;
            vis[i]=1;
            cnt1++;
            group[u][cnt1]=i;
            dfs(u,cnt+1,i,cnt1);
            cnt1--;
            vis[i]=0;
        }
    }
    if(f==-1){
        dfs(u+1,cnt,0,0);
    }
    
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++) cin>>a[i];
    dfs(1,0,0,0);
    cout<<ans;
}

剪枝:

1.https://www.acwing.com/problem/content/167/

枚举每只猫放哪个车即可。

#include<bits/stdc++.h>
using namespace std;
int c[100],n,w;
int ans=20;
int sum[10010];
void dfs(int u,int k){
    if(k>=ans) return;
    if(u==n+1){
        ans=k;
        return;
    }
    for(int i=0;i<k;i++){
        if(sum[i]+c[u]<=w){
            sum[i]+=c[u];
            dfs(u+1,k);
            sum[i]-=c[u];
        }
    }
    sum[k]=c[u];
    dfs(u+1,k+1);
    sum[k]=0;
}
int main(){
    cin>>n>>w;
    for(int i=1;i<=n;i++) cin>>c[i];
    sort(c+1,c+n+1);
    reverse(c+1,c+n+1);
    dfs(1,0);
    cout<<ans<<endl;
}

2.https://www.acwing.com/problem/content/168/

总思路:随意选空格子,看是否可以填。

考虑剪枝:

1。优化搜索顺序:优先选限制多的

2.可行性剪枝:不能与行列以及九宫格重。

3.位运算加速:通过011100.....等九位01串来表示可行状态,求交集即可得到可行方案。

#include<bits/stdc++.h>
using namespace std;
const int N = 9, M = 1 << N;
int ones[M], cmap[M];
int row[N], col[N], cell[3][3];
char str[100];
void init()//一开始都可以选
{
    for (int i = 0; i < N; i ++ )
        row[i] = col[i] = (1 << N) - 1;

    for (int i = 0; i < 3; i ++ )
        for (int j = 0; j < 3; j ++ )
            cell[i][j] = (1 << N) - 1;
}

void draw(int x, int y, int t, bool is_set)
{
    if (is_set) str[x * N + y] = '1' + t;
    else str[x * N + y] = '.';
    int v = 1 << t;
    if (!is_set) v = -v;
    row[x] -= v;
    col[y] -= v;
    cell[x / 3][y / 3] -= v;
}

int lowbit(int x)
{
    return x & -x;
}

int get(int x, int y)
{
    return row[x] & col[y] & cell[x / 3][y / 3];
}

bool dfs(int cnt)
{
    if (!cnt) return true;

    int minv = 10;
    int x, y;
    for (int i = 0; i < N; i ++ )
        for (int j = 0; j < N; j ++ )
            if (str[i * N + j] == '.')
            {
                int state = get(i, j);
                if (ones[state] < minv)
                {
                    minv = ones[state];
                    x = i, y = j;
                }
            }
//找到最优点
    int state = get(x, y);
    for (int i = state; i; i -= lowbit(i))
    {
        int t = cmap[lowbit(i)];
        draw(x, y, t, true);
        if (dfs(cnt - 1)) return true;
        draw(x, y, t, false);
    }

    return false;
}

int main()
{
    for (int i = 0; i < N; i ++ ) cmap[1 << i] = i;
    for (int i = 0; i < 1 << N; i ++ )
        for (int j = 0; j < N; j ++ )
            ones[i] += i >> j & 1;//处理i有多少1

    while (cin >> str, str[0] != 'e')
    {
        init();
        int cnt = 0;//空格子数量
        for (int i = 0, k = 0; i < N; i ++ )
            for (int j = 0; j < N; j ++, k ++ )
                if (str[k] != '.')
                {
                    int t = str[k] - '1';
                    draw(i, j, t, true);//进行跟新
                }
                else cnt ++ ;
        dfs(cnt);
        puts(str);
    }
}

3.https://www.acwing.com/problem/content/169/

整体思路:

先从小到大枚举木棒的长度,然后就是暴力DFS了。

考虑剪枝:

1.长度是sum的约数。

2.先枚举较长的木棍。

3.枚举组合数(下标从小到大)

4.木棒1失败,那么与它等长的也失败。

5.当前的第一个木棍失败,那么就直接回溯:

证明:假如3是某一个棒的第一个失败了,那么它一定存在在其他木棒中(假设4),那么木棒4中3的位置一定可以移到第一位,然后把棒4与3交换一下位置即可得出矛盾。

6.当前的最后一个木棍失败,那么就直接回溯:

证明:假如3是某一个棒的最后一个失败了,那么考虑把它放在其他木棒中(假设4),那么刚才那个木棒的填充最后一个的与放在4的那个交换一下一定也可,这也与前矛盾。

#include<bits/stdc++.h>
using namespace std;
const int N = 70;
int n;
int w[N];
int sum, length;
bool st[N];
bool dfs(int u, int cur, int start)//u:当前枚举的木棒cur:当前枚举的木棒的位置start:下标开始
{
    if (u * length == sum) return true;
    if (cur == length) return dfs(u + 1, 0, 0);
    for (int i = start; i < n; i ++ )
    {
        if (st[i] || cur + w[i] > length) continue;
        st[i] = true;
        if (dfs(u, cur + w[i], i + 1)) return true;
        st[i] = false;
        if (!cur || cur + w[i] == length) return false;//开头和结尾
        int j = i;
        while (j < n && w[j] == w[i]) j ++ ;//跳过重复的
        i = j - 1;
    }
    return false;
}
int main()
{
    while (cin >> n, n)
    {
        memset(st, 0, sizeof st);
        sum = 0;
        for (int i = 0; i < n; i ++ )
        {
            cin >> w[i];
            sum += w[i];
        }
        sort(w, w + n);
        reverse(w, w + n);//顺序优化
        length = 1;
        while (true)
        {
            if (sum % length == 0 && dfs(0, 0, 0))
            {
                cout << length << endl;
                break;
            }
            length ++ ;
        }
    }
}

4.https://www.acwing.com/problem/content/170/

枚举每一层DFS即可,下面考虑剪枝:

我们记最上层为1,可以得到:

S={R_{m}^{2}}+\sum_{i=1}^{m}2*R_{i}^{}*H_{i}^{}

N=\sum_{i=1}^{m}R_i{}^{2}*H_{i}

1.我们自底向上搜,同时先枚举R(因为平方)(从大到小)

2.我们再考虑可行性剪枝:

由于N-V\geqslant R_{u}^{2}*h_{u}

我们可以得到枚举的范围:

u\leqslant R_{u}\leqslant min(N-V,R_{u+1}-1)

u\leqslant H_{u}\leqslant min(H_{u+1}-1,N-V/R_{u}^{2})

同时我们可以估计前几层的最小S/V:

于是需要满足:

V+minV(u)\leqslant n     S+minS(u)\leqslant ans

最后我们考虑对公式的放缩:

S_{1\rightarrow u}=2/R_{u+1}*\sum_{k=1}^{u}R_{k}*H_{k}*R_{u+1}> 2/R_{u+1}*(N-V)

于是我们把这个估计的加上现在的S判断与ans的大小关系即可。

AC代码:

#include<bits/stdc++.h>
using namespace std;
const int N = 25, INF = 1e9;
int n, m;
int minv[N], mins[N];
int R[N], H[N];
int ans = INF;
void dfs(int u, int v, int s)
{
    if (v + minv[u] > n) return;
    if (s + mins[u] >= ans) return;
    if (s + 2 * (n - v) / R[u + 1] >= ans) return;
    if (!u)
    {
        if (v == n) ans = s;
        return;
    }
    for (int r = min(R[u + 1] - 1, (int)sqrt(n - v)); r >= u; r -- )
        for (int h = min(H[u + 1] - 1, (n - v) / r / r); h >= u; h -- )
        {
            int t = 0;
            if (u == m) t = r * r;
            R[u] = r, H[u] = h;
            dfs(u - 1, v + r * r * h, s + 2 * r * h + t);
        }
}

int main()
{
    cin >> n >> m;
    for (int i = 1; i <= m; i ++ )
    {
        minv[i] = minv[i - 1] + i * i * i;
        mins[i] = mins[i - 1] + 2 * i * i;
    }
    R[m + 1] = H[m + 1] = INF;
    dfs(m, 0, 0);
    if (ans == INF) ans = 0;
    cout << ans << endl;
}

  • 31
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值