《算法竞赛进阶指南》2.3剪枝

  • Post author:
  • Post category:其他




167. 木棒

乔治拿来一组等长的木棒,将它们随机地砍断,使得每一节木棍的长度都不超过50个长度单位。

然后他又想把这些木棍恢复到为裁截前的状态,但忘记了初始时有多少木棒以及木棒的初始长度。

请你设计一个程序,帮助乔治计算木棒的可能最小长度。

每一节木棍的长度都用大于零的整数表示。

注意: 数据中可能包含长度大于50的木棒,请在处理时忽略这些木棒。


输入格式


输入包含多组数据,每组数据包括两行。

第一行是一个不超过64的整数,表示砍断之后共有多少节木棍。

第二行是截断以后,所得到的各节木棍的长度。

在最后一组数据之后,是一个零。


输出格式


为每组数据,分别输出原始木棒的可能最小长度,每组数据占一行。


输入样例:


9

5 2 1 5 2 1 5 2 1

4

1 2 3 4

0


输出样例:


6

5

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 64;

int n, sum, length; //木棒个数 木棒总和 当前枚举的长度
int sticks[N];
bool st[N];//当前木棍有没有被使用过

bool dfs(int u, int cur, int 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]) continue;//当前木棍已经用掉 继续
        int l = sticks[i];
        if(cur + l > length) continue;
        
        st[i] = true;
        if(dfs(u, cur + l, i + 1)) return true;
        st[i] = false; //恢复现场
        
        //剪枝2 放第一根木棒失败 放最后一根失败
        if(!cur) return false;
        if(cur + l == length) return false;
        
        //剪枝1 跳过所有相等的木棍
        int j = i;
        while(j < n && sticks[j] == l) j ++;
        i = j - 1;
    }
    return false;
}

int main()
{
    while(cin >> n, n)
    {
        sum = 0, length = 0;
        for(int i = 0; i < n; i++)
        {
            int l = 0;
            cin >> l;
            sticks[i] = l;
            
            if(l > 50) continue;
            sum += l;
            length = max(length, l);
        }
        
        sort(sticks, sticks + n);
        reverse(sticks, sticks + n);//从小到大排序
        
        memset(st, false, sizeof st);
        for(int i = 0; i < n; i ++)
            if(sticks[i] > 50)
                st[i] = true;
        
        while(length)//从小到大枚举length 
        {
            if(sum % length == 0 && dfs(0, 0, 0)) 
            {
                cout << length << endl;
                break;
            }
            length ++;
        }
    }
    return 0;
}



168. 生日蛋糕

7月17日是Mr.W的生日,ACM-THU为此要制作一个体积为Nπ的M层生日蛋糕,每层都是一个圆柱体。

设从下往上数第i层蛋糕是半径为Ri, 高度为Hi的圆柱。

当i < M时,要求Ri> Ri+1且Hi > Hi+1。

由于要在蛋糕上抹奶油,为尽可能节约经费,我们希望蛋糕外表面(最下一层的下底面除外)的面积Q最小。

令Q = Sπ,请编程对给出的N和M,找出蛋糕的制作方案(适当的Ri和Hi的值),使S最小。

除Q外,以上所有数据皆为正整数 。


输入格式


输入包含两行,第一行为整数N(N <= 10000),表示待制作的蛋糕的体积为Nπ。

第二行为整数M(M <= 20),表示蛋糕的层数为M。


输出格式


输出仅一行,是一个正整数S(若无解则S = 0)。


数据范围


1≤N≤10000,

1≤M≤20


输入样例:


100

2


输出样例:


68

#include <iostream>
#include <algorithm>
#include <cmath>

using namespace std;

const int N = 25, INF = 1e9;

int n, m;
int R[N], H[N];
int minv[N], mins[N]; //前i层体积最小 前i层最小侧面积
int ans = INF;

void dfs(int u, int v, int s)//u层数
{
    //剪枝
    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(n == v) ans = s;
        return;
    }
    
    for(int r = min((int)sqrt(n - v), R[u + 1] - 1); r >= u; r--)
        for(int h = min((n - v) / r / r, H[u + 1] - 1); 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;// v = π * r * r * h
        mins[i] = mins[i - 1] + 2 * i * i;// s = π * 2 * r * h
    }
    
    R[m + 1] = H[m + 1] = INF; //哨兵
    dfs(m, 0, 0);
    
    cout << ans << endl;
    return 0;
}



169. 数独2

请你将一个16×16的数独填写完整,使得每行、每列、每个4×4十六宫格内字母A~P均恰好出现一次。

保证每个输入只有唯一解决方案。

数独2.jpg


输入格式


输入包含多组测试用例。

每组测试用例包括16行,每行一组字符串,共16个字符串。

第i个字符串表示数独的第i行。

字符串包含字符可能为字母A~P或”-“(表示等待填充)。

测试用例之间用单个空行分隔,输入至文件结尾处终止。


输出格式


对于每个测试用例,均要求保持与输入相同的格式,将填充完成后的数独输出。

每个测试用例输出结束后,输出一个空行。


输入样例:


–A—-C—–O-I

-J–A-B-P-CGF-H-

–D–F-I-E—-P-

-G-EL-H—-M-J–

—-E—-C–G—

-I–K-GA-B—E-J

D-GP–J-F—-A–

-E—C-B–DP–O-

E–F-M–D–L-K-A

-C——–O-I-L-

H-P-C–F-A–B—

—G-OD—J—-H

K—J—-H-A-P-L

–B–P–E–K–A-

-H–B–K–FI-C–

–F—C–D–H-N-


输出样例:


FPAHMJECNLBDKOGI

OJMIANBDPKCGFLHE

LNDKGFOIJEAHMBPC

BGCELKHPOFIMAJDN

MFHBELPOACKJGNID

CILNKDGAHBMOPEFJ

DOGPIHJMFNLECAKB

JEKAFCNBGIDPLHOM

EBOFPMIJDGHLNKCA

NCJDHBAEKMOFIGLP

HMPLCGKFIAENBDJO

AKIGNODLBPJCEFMH

KDEMJIFNCHGAOPBL

GLBCDPMHEONKJIAF

PHNOBALKMJFIDCEG

IAFJOECGLDPBHMNK

/*
dance links : 1.精确覆盖 2.重复覆盖
搜索顺序,每次找到一个空格,枚举空格选择哪个字母,然后往下递归。
边界:所有空格都填满

状态的存储:state[x][y],表示x行y列这个格子可以填哪些数(0 - 15) 用15位二进制表示 0 ~ 2 ^ 15 - 1

优化:
1.对于每个空格,如果不能填任何一个字母,无解;如果只能填一个字母,那么直接填上;
2.对于每一行,如果某个字母不能出现在任何一个位置,无解;如果某个字母只有一个位置可以填,则直接填上;
3.对于每一列,与行类似;
4.对于每个16宫格,与行类似;

5.每次选择空格时,选择备选方案最少的格子来填(先选备选方案少,分支少)
*/

#include <iostream>
#include <cstring>
#include <algorithm>

using namespace std;

const int N = 16, M = N * N + 1;

int ones[1 << N]; //存储每一个二进制里面有多少个1 ones[5] = 2 5的二进制里面有两个1
int log[1 << N];
int state[N][N]; //每个空格的所有备选数字
char str[N][N + 1]; //整个状态

int bstate[M][N][N], bstate2[M][N][N]; //备份
char bstr[M][N][N + 1];

inline int lowbit(int x) //返回x的二进制表示里面最后一个1 lowbit(6) = 2
{
    return x & - x;
}

void draw(int x, int y, int c)
{
    str[x][y] = c + 'A';
    for(int i = 0; i < N; i++)
    {
        state[x][i] &= ~(1 << c); //其他位不变,如果当前位是1的话变成0
        state[i][y] &= ~(1 << c);
    }
    //xy所在的九宫格做同样操作,即当前位选了,其他为不能选
    int sx = x / 4 * 4, sy = y / 4 * 4;
    for(int i = 0; i < 4; i++)
        for(int j = 0; j < 4; j++)
            state[sx + i][sy + j] &= ~(1 << c);
            
    state[x][y] = 1 << c;        
}

bool dfs(int cnt)
{
    if(!cnt) return true;
    // 枚举过程
    
    int kcnt = cnt;
    memcpy(bstate[kcnt], state, sizeof state);//备份,方便恢复现场
    memcpy(bstr[kcnt], str, sizeof str);
    
    //剪枝1 对于每个空格,如果不能填任何一个字母,无解;如果只能填一个字母,那么直接填上;
    for(int i = 0; i < N; i++)
        for(int j = 0; j < N; j++)
            if(str[i][j] == '-')
            {
                int s = str[i][j];
                if(!s)
                {
                    memcpy(state, bstate[kcnt], sizeof state);//恢复现场
                    memcpy(str, bstr[kcnt], sizeof str);
                    return false;
                }
                
                if(ones[s] == 1)
                {
                    draw(i, j, log[s]);
                    cnt --;
                }
            }
    //剪枝2 对于每一行,如果某个字母不能出现在任何一个位置,无解;如果某个字母只有一个位置可以填,则直接填上;      
    for(int i = 0; i < N; i ++)
    {
        int sor = 0, sand = (1 << N) - 1;//存储所有数字或和与的结果
        int drawn = 0; //当前行格子已经填上的
        
        for(int j = 0; j < N; j ++)
        {
            int s = state[i][j];
            sand &= ~(s & sor); //sand为1表示这一个字母只有一个位置可以填
            sor |= s;
            
            if(str[i][j] != '-') drawn |= state[i][j];
        }
        
        if(sor != (1 << N) - 1) //这一行并集没有16个1 false
        {
            memcpy(state, bstate[kcnt], sizeof state);//恢复现场
            memcpy(str, bstr[kcnt], sizeof str);
            return false;
        }
        
        for(int j = sand; j; j -= lowbit(j))
        {
            int t = lowbit(j);
            if(!(drawn & t))
            {
                for(int k = 0; k < N; k++)
                    if(state[i][k] & t)
                    {
                        draw(i, k, log[t]);
                        cnt --;
                    }
            }
        }
    }
    
    //剪枝3 对于每一列,如果某个字母不能出现在任何一个位置,无解;如果某个字母只有一个位置可以填,则直接填上;      
    for(int i = 0; i < N; i ++)
    {
        int sor = 0, sand = (1 << N) - 1;//存储所有数字或和与的结果
        int drawn = 0; //当前行格子已经填上的
        
        for(int j = 0; j < N; j ++)
        {
            int s = state[j][i];
            sand &= ~(s & sor); //sand为1表示这一个字母只有一个位置可以填
            sor |= s;
            
            if(str[j][i] != '-') drawn |= state[j][i];
        }
        
        if(sor != (1 << N) - 1) //这一行并集没有16个1 false
        {
            memcpy(state, bstate[kcnt], sizeof state);//恢复现场
            memcpy(str, bstr[kcnt], sizeof str);
            return false;
        }
        
        for(int j = sand; j; j -= lowbit(j))
        {
            int t = lowbit(j);
            if(!(drawn & t))
            {
                for(int k = 0; k < N; k++)
                    if(state[k][i] & t)
                    {
                        draw(k, i, log[t]);
                        cnt --;
                    }
            }
        }
    }
    
    //剪枝4 对于每个16宫格,如果某个字母不能出现在任何一个位置,无解;如果某个字母只有一个位置可以填,则直接填上;      
    for(int i = 0; i < N; i ++)
    {
        int sor = 0, sand = (1 << N) - 1;//存储所有数字或和与的结果
        int drawn = 0; //当前行格子已经填上的
        
        for(int j = 0; j < N; j ++)
        {
            int sx = i / 4 * 4, sy = i % 4 * 4;
            int dx = j / 4, dy = j % 4;
            
            int s = state[sx + dx][sy + dy];
            sand &= ~(s & sor); //sand为1表示这一个字母只有一个位置可以填
            sor |= s;
            
            if(str[sx + dx][sy + dy] != '-') drawn |= state[sx + dx][sy + dy];
        }
        
        if(sor != (1 << N) - 1) //这一行并集没有16个1 false
        {
            memcpy(state, bstate[kcnt], sizeof state);//恢复现场
            memcpy(str, bstr[kcnt], sizeof str);
            return false;
        }
        
        for(int j = sand; j; j -= lowbit(j))
        {
            int t = lowbit(j);
            if(!(drawn & t))
            {
                for(int k = 0; k < N; k++)
                {
                    int sx = i / 4 * 4, sy = i % 4 * 4;
                    int dx = k / 4, dy = k % 4;
                    if(state[sx + dx][sy + dy] & t)
                    {
                        draw(sx + dx, sy + dy, log[t]);
                        cnt --;
                    }
                }
            }
        }
    }
    
    if(!cnt) return true;
    //剪枝5 每次选择空格时,选择备选方案最少的格子来填(先选备选方案少,分支少)
    int x, y, s = 100;
    for(int i = 0; i < N; i++)
        for(int j = 0; j < N; j++)
            if(str[i][j] == '-' && ones[state[i][j]] < s)
            {
                s = ones[state[i][j]];
                x = i, y = j;
            }
            
    memcpy(bstate2[kcnt], state, sizeof state);
    for(int i = state[x][y]; i; i -= lowbit(i))
    {
        memcpy(state, bstate2[kcnt], sizeof state);
        draw(x, y, log[lowbit(i)]);
        
        if(dfs(cnt - 1)) return true;
    }
    
    memcpy(state, bstate[kcnt], sizeof state);//恢复现场
    memcpy(str, bstr[kcnt], sizeof str);
    
    return false;
    
}

int main()
{
    for(int i = 0; i < N; i ++) log[1 << i] = i;//初始化
    for(int i = 0; i < 1 << N; i ++)
    {
        for(int j = i; j; j -= lowbit(j)) ones[i] ++; //~i 等价于i >=0  i 等价于 i > 0
    }
    
    while(cin >> str[0])
    {
        for(int i = 1; i < N; i ++) cin >> str[i];
        
        for(int i = 0; i < N; i++) //初始化state
            for(int j = 0; j < N; j ++)
                state[i][j] = (1 << N) - 1;
                
        int cnt = 0; //存储空格个数
        for(int i = 0; i < N; i ++) //遍历已经填的格子
            for(int j = 0; j < N; j++)
                if(str[i][j] != '-')
                {
                    draw(i, j, str[i][j] - 'A'); //更新格子
                }
                else cnt ++;
        dfs(cnt);   
        
        for(int i = 0; i < N; i ++) cout << str[i] << endl;
        cout << endl;
    }
    return 0;
}



版权声明:本文为qq_42549254原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。