算法竞赛入门经典(第2版)—第七章(暴力求解)

零碎知识整理
  • next_permutation是STL中的函数。从字典序最小排列开始,求字典序下一个排列。
sort(a, a+n);
do{
}while(next_permutation(a, a+n);
  • memcmp(a, b, sizeof(b)),比较b和a的大小。返回值小于0时,表示a<b;返回值大于0时,表示a>b,返回值等于0时,表示a==b。
  • memcpy(a, b, sizeof(b)),将b赋值给a。
题目
725 - Division

题目链接:725 - Division

  • 题目大意:输入正整数n,按从小到大的顺序输出所有形如abcde/fghij=n的表达式,其中a—j恰好为数字0—9的一个排列(可以有0前导) 2=<n<=79。
  • 思路:暴力枚举。枚举分子a范围为[1234,99999/n],则分母b为a*n,然后判断组成a和b的数字有没有重复。

代码:

#include <iostream>
#include <cstdio>
#include <set>
using namespace std;

//判断a和b的数字是否充分
bool judge(int a, int b)
{
    set<int> Set;
    int t;
    if(a<10000) Set.insert(0);
    while(a)
    {
        t = a%10;
        a /= 10;
        Set.insert(t);
    }
    if(Set.size()<5) return 0;
    while(b)
    {
        t = b%10;
        b /= 10;
        Set.insert(t);
    }
    if(Set.size()<10) return 0;
    else              return 1;
}
int main()
{
    int N, kase = 0;
    while(cin >> N && N)
    {
        if(kase++) printf("\n");
        int flag = 0;
        for(int j=1234; j<=99999/N; j++)
        {
            if(judge(j, j*N))
            {
                flag = 1;
                if(j<10000)
                    printf("%d / 0%d = %d\n", j*N, j, N);
                else
                    printf("%d / %d = %d\n", j*N, j, N);
            }
        }
        if(!flag) printf("There are no solutions for %d.\n", N);
    }
    return 0;
}
11059 - Maximum Product

题目链接:11059 - Maximum Product

  • 题目大意:输入n个元素组成的序列S,你需要找出一个乘积最大的连续子序列。如果这个最大的乘积不是正数,应输出0(表示无解)。1≤n≤18,-10≤Si≤10。
    样例输入:
    3
    2 4-3
    5
    2 5 -1 2 -1
    样例输出:
    8
    20
  • 思路:暴力枚举。枚举连续序列的起点和终点,结果是long long 类型。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

typedef long long LL;

int main()
{
    //freopen("input.txt", "r", stdin);
    //freopen("output.txt", "w", stdout);
    int N, a[20], kase = 1;
    while(cin >> N)
    {
        LL Max = 0;
        for(int i=0; i<N; i++) cin >> a[i];
        for(int i=0; i<N; i++)
        {
            for(int j=i; j<N; j++)
            {
                LL ans = 1;
                for(int k=i; k<=j; k++)//注意是等于号
                {
                    ans = ans*a[k];
                }
                if(Max<ans) Max = ans;
            }
        }
        printf("Case #%d: The maximum product is %lld.\n\n", kase++, Max);
    }
    return 0;
}
10976 - Fractions Again?!

题目链接:10976 - Fractions Again?!

  • 题目大意:给你一个数k,找到所有的正整数x>=y,使得1/k = 1/x + 1/y成立 。
  • 思路:暴力求解,但是因为x和y的取值范围太大,所以需要缩小其枚举范围。
    推导:x>=y => 1/x<=1/y,所以1/k-1/y<=1/y,所以y<=2k;然后1/k=1/x+1/y => x=(ky)/(y-k)。所以从[k+1, 2k]范围内枚举y使得k*y除以y-k的值为整数即可得到x值。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

int main()
{
    int N, X[10000], Y[10000];
    while(cin >> N)
    {
        int cnt = 0;
        for(int y=N+1; y<=2*N; y++)
        {
            if((N*y)%(y-N)==0)//x为整数
            {
                X[cnt] = (N*y)/(y-N);
                Y[cnt++] = y;
            }
        }
        printf("%d\n", cnt);
        for(int i=0; i<cnt; i++)
            printf("1/%d = 1/%d + 1/%d\n", N, X[i], Y[i]);
    }
    return 0;
}
524 - Prime Ring Problem

题目链接:524 - Prime Ring Problem

  • 题目大意:输入正整数n,把1—n组成一个环,是相邻的两个整数为素数。输出时从整数1开始,逆时针排列。同一个环恰好输出一次,n (0 < n <= 16)
  • 思路:回溯法+dfs。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;

int n, vis[20];
vector<int> v;

//判断是否是素数
bool isPrime(int u)
{
    for(int i=2; i*i<=u; i++)
    {
        if(u%i==0) return 0;
    }
    return 1;
}
//dfs回溯求出所有满足条件的素数环
void dfs()
{
    if(v.size()==n && isPrime(v[0]+v[n-1]))
    {
        for(int i=0; i<n; i++)
        {
            if(i) printf(" ");
            printf("%d", v[i]);
        }
        printf("\n");
    }
    else
    {
        for(int i=2; i<=n; i++)
        {
            if(!vis[i] && isPrime(i+v[v.size()-1]))
            {
                vis[i] = 1;
                v.push_back(i);
                dfs();
                v.pop_back();
                vis[i] = 0;
            }
        }
    }
}
int main()
{
    int kase = 1;
    while(cin >> n)
    {
        //初始化
        v.clear();
        memset(vis, 0, sizeof(vis));
        vis[1] = 1;
        v.push_back(1);
        if(kase>1) printf("\n");
        printf("Case %d:\n", kase++);
        dfs();
    }
    return 0;
}
129 - Krypton Factor

题目链接:129 - Krypton Factor

  • 题目大意:一个字母串里包含有两个相邻的重复子串则称为“水串”,否则为“火串”
    例如AA、ABCABC都是"水串",而D、DC、ABDAD、CBABCBA都是“火串"。
    输入正整数L和n,输出由前L个字母组成的、字典序第n个的"火串"。
  • 思路:dfs+回溯。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;

vector<char> v;
int L, n, Rank;//Rank表示该串的排名

//判断该串是否是重串
int check(vector<char> v)
{
    int len = v.size();
    //i表示判断重复串的长度,j表示串字符的下标
    for(int i=1; i<=len/2; i++)
    {
        int j;
        for(j=len-i*2; j<len-i; j++)
        {
            if(v[j]!=v[j+i]) break;//表明两个串并不重复
        }
        if(j==len-i) return 0;//两个串重复
    }
    return 1;
}

bool dfs()
{
    if(Rank==L) return 1;//表明得到了该串
    //DFS遍历
    for(int i=0; i<n; i++)
    {
        v.push_back(i+'A');
        if(!check(v)) v.pop_back();
        else
        {
            Rank++;
            if(dfs()) return 1;
            v.pop_back();
        }
    }
    return 0;//回溯
}
int main()
{
    while(cin >> L >> n && (L+n))
    {
        v.clear();
        Rank = 0;
        dfs();
        //格式打印输出
        int cnt = 0;
        for(int i=0; i<v.size(); i++)
        {
            if(i && cnt%64==0) printf("\n");
            else if(i && cnt%4==0) printf(" ");
            printf("%c", v[i]);
            cnt++;
        }
        printf("\n");
        printf("%d\n", v.size());
    }
    return 0;
}
140 - Bandwidth

题目链接:140 - Bandwidth

  • 题目大意:给出一个n个节点的图,求该图中相邻的节点在哪一个由这些节点组成的排列中,带宽最小,输出该排列和最小带宽。(各个节点到相邻节点的最远距离,这些最远距离的最大值即为带宽)。
  • 思路:因为结点数目较少,所以可以使用nextpermutation来枚举各个排列,然后再减枝筛选。

代码:

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

const int INF = 1<<29;
string s;
set<int> G[30];//存储相关联元素
set<int> S;//存储所有元素,不重复
int Min, ans[30], c[30], n;//ans是结果数组,c是序列数组

void Init()
{
    Min = INF;
    S.clear();
    for(int i=0; i<30; i++) G[i].clear();
}
void solve()
{
   do
   {
       int cnt = 0, flag = 1;//cnt是该序列对应的带宽
       for(int i=0; i<n; i++)
       {
           int t = c[i], num = G[t].size();//t是元素,num是该元素相关联的元素的个数
           for(int k=0; k<n; k++)
           {
               if(G[t].count(c[k]))
               {
                   int d = abs(i-k);
                   if(d>=Min)//带宽已经超过了之前的
                   {
                       flag = 0;
                       break;
                   }
                   if(cnt<d) cnt = d;//更新带宽
                   num--;//已经计算过一个了
               }
               if(i==k && num>=Min)//还剩num个相关联元素,且num大于之前的带宽
               {
                   flag = 0;
                   break;
               }
           }
           if(!flag) break;
       }
       if(flag && cnt<Min)
       {
           Min = cnt;
           for(int k=0; k<n; k++)
               ans[k] = c[k];
       }
   }while(next_permutation(c, c+n));
}
void Print()
{
    for(int i=0; i<n; i++)
        printf("%c ", ans[i]+'A');
    printf("-> %d\n", Min);
}

int main()
{
    while(cin >> s && s!="#")
    {
        Init();
        int flag = 0, a, b;
        for(int i=0; i<s.size(); i++)
        {
            if(s[i]==':')      flag = 1;
            else if(s[i]==';') flag = 0;
            else if(!flag)
            {
                a = s[i]-'A';
                S.insert(a);
            }
            else if(flag)
            {
                b = s[i]-'A';
                G[a].insert(b);
                G[b].insert(a);
                S.insert(b);
            }
        }
        n = S.size();
        set<int>::iterator it = S.begin();
        for(int i=0; i<n; i++)
        {
            c[i] = *it;
            it++;
        }
        solve();
        Print();
    }
    return 0;
}
1354 - Mobile Computing

题目链接:1354 - Mobile Computing</a

  • 题目大意:给出房间的宽度r和s个挂坠的重量wi,设计一个尽量宽但小于房间宽度的天平。
    如果要使两个重量不同的w1,w2坠在天平两边,就要满足如下等式w1到中点距离为l1=(w2)/(w1+w2),w2到中点距离为l2=(w1)/(w1+w2);
    因为一个天平两臂长度为1(l1+l2=1),而且根据物理学公式可知w1*(l1)=w2*(l2);
  • 思路:
10603 - Fill

题目链接:10603 - Fill

  • 题目大意:三个杯子容量分别为a,b,c,前两个是空的,第三个装满水,目标是量出d升水。
  • 思路:隐式图转换。状态是三个杯子的水量和已经倒的水量。采用bfs遍历,得到目标态,遍历是将一个杯子中的水倒到另一个杯子(杯子水倒完或杯子倒满)。同时还有去重数组,即vis[][]下标表示前两个杯子的容量,来标记状态是否已经访问过。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <queue>
using namespace std;

struct Node
{
    int v[3], dist;
    bool operator < (const Node &A) const
    {
        return dist>A.dist;
    }
};

const int maxn = 205;
int vis[maxn][maxn], cap[3], ans[maxn];

void update_ans(const Node &u)
{
    for(int i=0; i<3; i++)
    {
        int d = u.v[i];
        if(ans[d]<0 || u.dist<ans[d]) ans[d] = u.dist;//表示当倒ans[d]水量时,可以得到d升水
    }
}
void solve(int a, int b, int c, int d)
{
    cap[0] = a, cap[1] = b, cap[2] = c;
    memset(vis, 0, sizeof(vis));
    memset(ans, -1, sizeof(ans));
    priority_queue<Node> q;

    Node start;
    start.dist = 0;
    start.v[0] = 0, start.v[1] = 0, start.v[2] = c;
    q.push(start);
    vis[0][0] = 1;//初始状态

    while(!q.empty())
    {
        Node u = q.top(); q.pop();
        update_ans(u);
        if(ans[d]>=0) break;
        for(int i=0; i<3; i++)//从i向j里面倒水
        {
            for(int j=0; j<3; j++)
            {
                if(i!=j)
                {
                    if(u.v[i]==0 || u.v[j]==cap[j]) continue;
                    int amount = min(cap[j], u.v[i]+u.v[j])-u.v[j];//倒的水量
                    Node u2;
                    memcpy(&u2, &u, sizeof(u));
                    u2.dist = u.dist + amount;//结果水量
                    u2.v[i] -= amount;
                    u2.v[j] += amount;
                    if(!vis[u2.v[0]][u2.v[1]])//该状态没有出现过
                    {
                        vis[u2.v[0]][u2.v[1]] = 1;
                        q.push(u2);
                    }
                }
            }
        }
    }
    while(d>=0)//输出与d最接近的
    {
        if(ans[d]>0)
        {
            printf("%d %d\n", ans[d], d);
            return;
        }
        d--;
    }
}

int main()
{
    int T, a, b, c, d;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d%d%d%d", &a, &b, &c, &d);
        solve(a, b, c, d);
    }
    return 0;
}
208 - Firetruck

题目链接:208 - Firetruck

  • 题目大意:一个无向图,从一个点到另一个点,按序输出其路径。
  • 思路:细节是在递归回溯之前需要判断是否存在路径。使用dfs判断是否有路径,然后递归回溯,当到达目标点时输出路径。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

const int maxn = 50;
const int INF = 1<<29;
int G[maxn][maxn], n, sum;
int vis[maxn], Max, ans[maxn];

void Init()
{
    sum = 0;
    for(int i=0; i<maxn; i++)
    {
        for(int j=0; j<maxn; j++)
            G[i][j] = INF;
    }
    memset(vis, 0, sizeof(vis));
}
bool dfs(int st)
{
    if(st==n) return 1;
    vis[st] = 1;
    for(int i=1; i<=Max; i++)
    {
        if(!vis[i] && G[st][i]!=INF)
        {
            if(dfs(i)) return 1;
        }
    }
    return 0;
}
void solve(int st, int cur)
{
    if(st==n)
    {
        sum++;//路径数目
        for(int i=0; i<cur; i++)
        {
            if(i) printf(" ");
            printf("%d", ans[i]);
        }
        printf("\n");
    }
    else
    {
        for(int i=1; i<=Max; i++)
        {
            if(vis[i]) continue;
            if(G[st][i]!=INF)
            {
                ans[cur] = i;
                vis[i] = 1;
                solve(i, cur+1);
                vis[i] = 0;
            }
        }
    }
}
int main()
{
    int a, b, kase = 1;
    while(cin >> n)
    {
        Init();
        Max = 0;
        while(cin >> a >> b && (a+b))
        {
            G[a][b] = 1;
            G[b][a] = 1;
            if(Max<a) Max = a;
            if(Max<b) Max = b;
        }
        printf("CASE %d:\n", kase++);
        if(!dfs(1))
        {
            printf("There are %d routes from the firestation to streetcorner %d.\n", 0, n);
            continue;
        }
        memset(vis, 0, sizeof(vis));
        ans[0] = 1;
        vis[1] = 1;
        solve(1, 1);
        printf("There are %d routes from the firestation to streetcorner %d.\n", sum, n);
    }
    return 0;
}
1601 - The Morning after Halloween

题目链接:1601 - The Morning after Halloween

  • 题目大意:在一张图中,以最少的步数将a,b,c移到对应的A,B,C上去。其中,每个2x2的方格都有障碍并且不能两个小写字母同时占据一个格子。
  • 思路:因为障碍格较多,可以将一个位置的相邻非障碍个存储为关于该位置的一个数组,则需要将二维的位置转换为一维的下标,来降低空间复杂度,然后使用二维数组存储该位置的非障碍相邻位置。将三个点作为状态(不够则补),之后就是可以BFS搜索。

单BFS
代码:

#include<bits/stdc++.h>
using namespace std;
const int maxl = 20;
const int maxn = 150;
int w, h, n;
int deg[maxn];
int G[maxn][5];
int s[3], t[3];
int d[maxn][maxn][maxn];
int x[maxn], y[maxn], id[maxl][maxl];

char maze[maxl][maxl];

int dx[] = {0, 0, 0, -1, 1};
int dy[] = {0, 1, -1, 0, 0};

int ID(int a, int b, int c){
    return (a<<16) | (b<<8) | c;
}

bool conflict(int a, int b, int a2, int b2){
    return a2 == b2 || (a2 == b && b2 == a);
}

int BFS(){
    memset(d, -1, sizeof(d));//d存储到达该状态的步数
    queue<int>q;
    q.push(ID(s[0], s[1], s[2]));//三个鬼的位置作为状态
    d[s[0]][s[1]][s[2]] = 0;
    while(!q.empty()){
        int u = q.front(); q.pop();
        int a = (u>>16) & 0xff, b = (u>>8) & 0xff, c = u & 0xff;
        if(a == t[0] && b == t[1] && c == t[2]) return d[a][b][c];//到达目标状态
        for(int i = 0; i < deg[a]; i++){//a移动
            int a2 = G[a][i];
            for(int j = 0; j < deg[b]; j++){//b移动
                int b2 = G[b][j];
                if(conflict(a, b, a2, b2)) continue;//a和b冲突
                for(int k = 0; k < deg[c]; k++){//c移动
                    int c2 = G[c][k];
                    if(conflict(a, c, a2, c2)) continue;
                    if(conflict(b, c, b2, c2)) continue;
                    if(d[a2][b2][c2] != -1) continue;//如果已经遍历过该状态
                    q.push(ID(a2, b2, c2));
                    d[a2][b2][c2] = d[a][b][c] + 1;
                }
            }
        }
    }
    return -1;
}

int main()
{
    // freopen("data.in","r",stdin);
    // freopen("data.out","w",stdout);
    while(scanf("%d%d%d", &w, &h, &n) == 3 && w){
        getchar();
        for(int i = 0; i < h; i++)
            fgets(maze[i], maxl, stdin);
        //将非墙的二维坐标转换为一维下标
        int cnt = 0;
        for(int i = 0; i < h; i++){
            for(int j = 0; j < w; j++){
                if(maze[i][j] != '#'){
                    x[cnt] = i, y[cnt] = j, id[i][j] = cnt;//x,y分别为该下标对应的二维坐标,id是该二维坐标对应的下标
                    if(islower(maze[i][j])) s[maze[i][j] - 'a'] = cnt;//出发点的下标
                    if(isupper(maze[i][j])) t[maze[i][j] - 'A'] = cnt;//目标点的下标
                    cnt++;
                }
            }
        }
        for(int i = 0; i < cnt; i++){
            deg[i] = 0;//表示下标为i的位置相邻的非墙的位置的个数
            for(int j = 0; j < 5; j++){
                int nx = x[i] + dx[j], ny = y[i] + dy[j];
                if(maze[nx][ny] != '#'){ //题目中说了迷宫的最外面是墙 因此不用判断边界
                    G[i][deg[i]++] = id[nx][ny];//G[i]表示下标为i的位置相邻的位置
                }
            }
        }
        //如果鬼的数量小于3个
        if(n <= 2){ deg[cnt] = 1; G[cnt][0] = cnt; s[2] = t[2] = cnt++; }
        if(n <= 1){ deg[cnt] = 1; G[cnt][0] = cnt; s[1] = t[1] = cnt++; }
        printf("%d\n", BFS());
    }
    return 0;
}

双向BFS
代码:

#include<bits/stdc++.h>
using namespace std;
const int maxl = 20;
const int maxn = 150;
int w, h, n;
int deg[maxn];
int G[maxn][5];
int s[3], t[3];
int d[2][maxn][maxn][maxn];
int x[maxn], y[maxn], id[maxl][maxl];
int mark = 0;
char maze[maxl][maxl];
int dx[] = {0, 0, 0, -1, 1};
int dy[] = {0, 1, -1, 0, 0};

int ID(int a, int b, int c){
    return (a<<16) | (b<<8) | c;
}

bool conflict(int a, int b, int a2, int b2){
    return a2 == b2 || (a2 == b && b2 == a);
}

void expand(queue<int>& q, int n){
    int u = q.front(); q.pop();
    int a = (u>>16) & 0xff, b = (u>>8) & 0xff, c = u & 0xff;
    if(d[1-n][a][b][c] != -1) mark = d[1-n][a][b][c] + d[n][a][b][c];//表明另一个队列到过同一个地方,则mark值即为答案
    for(int i = 0; i < deg[a]; i++){
        int a2 = G[a][i];
        for(int j = 0; j < deg[b]; j++){
            int b2 = G[b][j];
            if(conflict(a, b, a2, b2)) continue;
            for(int k = 0; k < deg[c]; k++){
                int c2 = G[c][k];
                if(conflict(a, c, a2, c2)) continue;
                if(conflict(b, c, b2, c2)) continue;
                if(d[n][a2][b2][c2] != -1) continue;
                int t = ID(a2, b2, c2);
                q.push(t);
                d[n][a2][b2][c2] = d[n][a][b][c] + 1;
            }
        }
    }
}

int DBFS(){
    memset(d, -1, sizeof(d));
    queue<int>q[2];
    q[0].push(ID(s[0], s[1], s[2]));
    d[0][s[0]][s[1]][s[2]] = 0;
    q[1].push(ID(t[0], t[1], t[2]));
    d[1][t[0]][t[1]][t[2]] = 0;
    int order = 0;
    mark = 0;
    while(!q[0].empty() && !q[1].empty()){
        order ? expand(q[0], 0) : expand(q[1], 1);//两个队列交替进行
        if(mark) return mark;
        order = !order;
    }
    return -1;
}

int main()
{
    // freopen("data.in","r",stdin);
    // freopen("data.out","w",stdout);
    while(scanf("%d%d%d", &w, &h, &n) == 3 && w){
        getchar();
        for(int i = 0; i < h; i++)
            fgets(maze[i], maxl, stdin);
        //将非墙的二维坐标转换为一维下标
        int cnt = 0;
        for(int i = 0; i < h; i++){
            for(int j = 0; j < w; j++){
                if(maze[i][j] != '#'){
                    x[cnt] = i, y[cnt] = j, id[i][j] = cnt;//x,y分别为该下标对应的二维坐标,id是该二维坐标对应的下标
                    if(islower(maze[i][j])) s[maze[i][j] - 'a'] = cnt;//出发点的下标
                    if(isupper(maze[i][j])) t[maze[i][j] - 'A'] = cnt;//目标点的下标
                    cnt++;
                }
            }
        }
        for(int i = 0; i < cnt; i++){
            deg[i] = 0;//表示下标为i的位置相邻的非墙的位置的个数
            for(int j = 0; j < 5; j++){
                int nx = x[i] + dx[j], ny = y[i] + dy[j];
                if(maze[nx][ny] != '#'){ //题目中说了迷宫的最外面是墙 因此不用判断边界
                    G[i][deg[i]++] = id[nx][ny];//G[i]表示下标为i的位置相邻的位置
                }
            }
        }
        //如果鬼的数量小于3个
        if(n <= 2){ deg[cnt] = 1; G[cnt][0] = cnt; s[2] = t[2] = cnt++; }
        if(n <= 1){ deg[cnt] = 1; G[cnt][0] = cnt; s[1] = t[1] = cnt++; }
        printf("%d\n", DBFS());
    }
    return 0;
}
12325 - Zombie’s Treasure Chest

题目链接:12325 - Zombie’s Treasure Chest

  • 题目大意:一个箱子,体积为N
    两种宝物,体积为S1、S2,价值为V1、V2,数量无限
    最多装多少价值的宝物
    数据范围:2^32
  • 思路:数据范围比较大,无法使用动态规划,使用枚举,但是需要缩小枚举范围。首先可以枚举1的数量范围为N/S1,但当S1较小时,范围还是较大,宝物2同理。但是若存在V1S2>V2S1,则表明宝物1的性价比高于宝物2,枚举2使得宝物1的数量尽可能多,2的枚举量不超过S1,所以为min(N/S2, S1-1),当大于时同理。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

typedef long long LL;

int main()
{
#ifdef ONLINE_JUDGE
#else
    freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
#endif
    LL T, N, S[3], V[3], n, kase = 1, idx;
    cin >> T;
    while(T--)
    {
        cin >> N >> S[0] >> V[0] >> S[1] >> V[1];
        if(S[0]*V[1]>S[1]*V[0])//V[0]的性价比低,则枚举0,尽可能拿1
            n = min(N/S[0], S[1]-1), idx = 0;
        else
            n = min(N/S[1], S[0]-1), idx = 1;
        LL value = 0, num, v;
        for(int i=0; i<=n; i++)
        {
            v = 0, num = N;
            num -= S[idx]*i;
            v += V[idx]*i;
            v += num/S[1-idx]*V[1-idx];
            if(v>value) value = v;
        }
        printf("Case #%lld: %lld\n", kase++, value);
    }
    return 0;
}
uva - 11214
  • 题目大意:一个n*m的棋盘,某些格子有标记。用最少的皇后守卫所有带标记的格子。
  • 思路:可以枚举或二分枚举皇后的数目,然后使用dfs来判断是否可以将所有有标记的格子看住。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <vector>
using namespace std;

const int maxn = 15;
char G[maxn][maxn];
vector<int> X;
int vis[5][maxn*10];
int n, m;

void Init()
{
    X.clear();
}
//判断是否将所有带标记的格子看住
bool check()
{
    for(int i=0; i<X.size(); i++)
    {
        int x = X[i]/m, y = X[i]%m;
        if(!vis[0][x] && !vis[1][y] && !vis[2][x+y] && !vis[3][x-y+m]) return 0;
    }
    return 1;
}
bool dfs(int num, int cur, int mid)
{
    if(num==mid)//所有皇后均放着
    {
        if(check()) return 1;
        return 0;
    }
    else
    {
        for(int i=cur; i<n*m; i++)
        {
            int x = i/m, y = i%m;//坐标
            int temp1 = vis[0][x], temp2 = vis[1][y], temp3 = vis[2][x+y], temp4 = vis[3][x-y+m];
            if(temp1==1 && temp2==1 && temp3==1 && temp4==1) continue;//减枝,当所有方向均被看住
            vis[0][x] = 1, vis[1][y] = 1, vis[2][x+y] = 1, vis[3][x-y+m] = 1;
            if(dfs(num+1, i, mid)) return 1;
            vis[0][x] = temp1, vis[1][y] = temp2, vis[2][x+y] = temp3, vis[3][x-y+m] = temp4;
        }
    }
    return 0;
}
int main()
{
#ifdef ONLINE_JUDGE
#else
    freopen("input.txt","r",stdin);
    freopen("output.txt","w",stdout);
#endif
    int kase = 1;
    while(scanf("%d", &n) && n)
    {
        Init();
        scanf("%d", &m);
        getchar();
        for(int i=0; i<n; i++)
        {
            fgets(G[i], 20, stdin);
        }
        for(int i=0; i<n; i++)
        {
            for(int j=0; j<m; j++)
            {
                if(G[i][j]=='X')
                    X.push_back(i*m+j);//存储带标记的格子的位置
            }
        }

        int l = -1, r = 8, mid;
        /*
        for(r=0; ; r++)//枚举
        {
            memset(vis, 0, sizeof(vis));
            if(dfs(0, 0, r)) break;
        }*/
        while(r>l+1)//二分枚举
        {
            memset(vis, 0, sizeof(vis));
            mid = (l+r)/2;
            if(dfs(0, 0, mid)) r = mid;
            else               l = mid;
        }
        printf("Case %d: %d\n", kase++, r);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值