算法競賽入門經典(第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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章