CSP歷年複賽題-P3956 [NOIP2017 普及組] 棋盤

江城伍月發表於2024-06-07

原題連結:https://www.luogu.com.cn/problem/P3956

題意解讀:計算從(1,1)走到(m,m)的最小花費,有幾個限定:

同色格子可以走,花費為0;

不同色格子可以走,花費為1;

有色格子可以走到無色格子,花費為2,且用將無色格子臨時染色;

無色格子不能走到無色格子。

解題思路:

可以採用DFS來暴搜所有路徑,需要四個狀態:

x:橫座標

y:縱座標

sum:花費的總金幣

use:走到(x,y)是否使用了魔法

從起點開始,列舉上下左右四個位置,判斷是否能走:

1、超出範圍,不能走

2、已經走過,不能走

3、上一步使用了魔法,下一步位置無色,不能走

4、上一步沒有使用魔法,下一步位置無色,可以使用魔法,標記成跟上一步一樣的顏色

5、下一步位置有色,根據上一步的顏色計算花費

注意:對於黃色設定為1,紅色設定為0,無色設定為-1

55分程式碼:

#include <bits/stdc++.h>
using namespace std;

const int M = 105;
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, -1, 0, 1};
int m, n;
int a[M][M];
bool vis[M][M]; //標記是否已走過
int x, y, c;
int ans = INT_MAX;

//sum:花費的金幣  use:是否使用魔法
void dfs(int x, int y, int sum, bool use)
{
    if(x == m && y == m)
    {
        ans = min(ans, sum);
        return;
    }
    for(int i = 0; i < 4; i++)
    {
        int nx = x + dx[i], ny = y + dy[i];
        if(nx < 1 || nx > m || ny < 1 || ny > m || vis[nx][ny]) continue;
        if(a[nx][ny] == -1 && use) continue; //上一步是使用魔法到的且下一步無顏色

        vis[nx][ny] = true;
        if(a[nx][ny] == -1) //下一步無顏色,可以使用魔法
        {
            a[nx][ny] = a[x][y]; //用魔法變顏色,變成和上一步相同的顏色最好
            dfs(nx, ny, sum + 2, true); //使用魔法走到nx,ny
            a[nx][ny] = -1; //恢復
        }
        else //下一步有顏色
        {
            if(a[x][y] == a[nx][ny]) dfs(nx, ny, sum, false); //與上一步顏色相同
            else dfs(nx, ny, sum + 1, false); //與上一步顏色不同
        }
        vis[nx][ny] = false; //恢復
    }
}

int main()
{
    cin >> m >> n;
    memset(a, -1, sizeof(a));
    memset(f, 0x3f, sizeof(f));
    for(int i = 1; i <= n; i++)
    {
        cin >> x >> y >> c;
        a[x][y] = c;
    }

    vis[1][1] = true;
    dfs(1, 1, 0, false);

    if(ans == INT_MAX) cout << -1;
    else cout << ans;

    return 0;
}

由於DFS列舉的所有可能,會導致部分資料超時,需要引入剪枝方法

這裡只需要一種簡單的判斷,當走到(x, y)時,記錄下最少的花費sum,如果下一次再走到(x, y),之前記錄的花費都不超過當前的花費,則沒有必要再繼續DFS,可以提前結束。

只需要引入一個int f[M][M]

當f[x][y] <= sum的時候提前結束,否則就記錄f[x][y] = sum

注意f[x][y]儲存的是更小的sum,因此要初始化為極大值memset(f, 0x3f, sizeof(f))

100分程式碼:

#include <bits/stdc++.h>
using namespace std;

const int M = 105;
int dx[4] = {-1, 0, 1, 0};
int dy[4] = {0, -1, 0, 1};
int m, n;
int a[M][M];
bool vis[M][M]; //標記是否已走過
int x, y, c;
int ans = INT_MAX;
int f[M][M]; //記錄走到i,j時的最小花費,初始化為極大值

//sum:花費的金幣  use:是否使用魔法
void dfs(int x, int y, int sum, bool use)
{
    if(f[x][y] <= sum) return; //如果之前走過x,y,且花費更小,則不用繼續了
    f[x][y] = sum; //儲存更小的花費
    
    if(x == m && y == m)
    {
        ans = min(ans, sum);
        return;
    }
    for(int i = 0; i < 4; i++)
    {
        int nx = x + dx[i], ny = y + dy[i];
        if(nx < 1 || nx > m || ny < 1 || ny > m || vis[nx][ny]) continue;
        if(a[nx][ny] == -1 && use) continue; //上一步是使用魔法到的且下一步無顏色

        vis[nx][ny] = true;
        if(a[nx][ny] == -1) //下一步無顏色,可以使用魔法
        {
            a[nx][ny] = a[x][y]; //用魔法變顏色,變成和上一步相同的顏色最好
            dfs(nx, ny, sum + 2, true); //使用魔法走到nx,ny
            a[nx][ny] = -1; //恢復
        }
        else //下一步有顏色
        {
            if(a[x][y] == a[nx][ny]) dfs(nx, ny, sum, false); //與上一步顏色相同
            else dfs(nx, ny, sum + 1, false); //與上一步顏色不同
        }
        vis[nx][ny] = false; //恢復
    }
}

int main()
{
    cin >> m >> n;
    memset(a, -1, sizeof(a));
    memset(f, 0x3f, sizeof(f));
    for(int i = 1; i <= n; i++)
    {
        cin >> x >> y >> c;
        a[x][y] = c;
    }

    vis[1][1] = true;
    dfs(1, 1, 0, false);

    if(ans == INT_MAX) cout << -1;
    else cout << ans;

    return 0;
}

相關文章