1. 程式人生 > >【Luogu P1935】[國家集訓隊]圈地計劃

【Luogu P1935】[國家集訓隊]圈地計劃

scan 區域 mod 不可 delta 增加 網絡 變化 商業區

題目

最近房地產商 GDOI (Group of Dumbbells Or Idiots) 從 NOI (Nuts Old Idiots) 手中得到了一塊開發土地。

據了解,這塊土地是一塊矩形的區域,可以縱橫劃分為 \(N\times M\) 塊小區域。GDOI 要求將這些區域分為商業區和工業區來開發。

根據不同的地形環境,每塊小區域建造商業區和工業區能取得不同的經濟價值。更具體點,對於第 \(i\) 行第 \(j\) 列的區域,建造商業區將得到 \(A_{ij}\) 收益,建造工業區將得到 \(B_{ij}\) 收益。

另外,不同的區域連在一起可以得到額外的收益,即如果區域\((I,j)\)

相鄰(相鄰是指兩個格子有公共邊)有 \(K\) 塊(顯然 \(K\) 不超過 \(4\))類型不同於\((I,j)\)的區域,則這塊區域能增加 \(k\times C_{ij}\) 收益。

經過 Tiger.S 教授的勘察,收益矩陣 \(A,B,C\) 都已經知道了。你能幫 GDOI 求出一個收益最大的方案麽?

分析

網絡流是不可能網絡流的, 這輩子都不可能網絡流的.

我發現有 \(2^{nm}\) 種解, 然而答案範圍只有 \(1000nm\), 說明有大量的重復解, 那還寫正解?

這道題我用模擬退火, 隨機的是一個矩陣代表每個地方是什麽區域.

每次隨機一個點做一些微小的變化就可以了.

(如果調參調不下去可以試試多退火幾次, 跳出局部最優)

比如我是這樣寫的:

int ans = 0;
for(int i = 0; i < 42; i++)
    ans = std::max(SA(), ans);
printf("%d", ans);

代碼

(不保證每時每刻你提交這份都能 AC)

#include <bits/stdc++.h>

const int kMaxSize = 105, mod = 1e9 + 7;
const double delta = 0.994, sup = 1e17, eps = 1e-17;

bool plan[kMaxSize][kMaxSize];
int n, m, a[kMaxSize][kMaxSize], b[kMaxSize][kMaxSize], c[kMaxSize][kMaxSize];

unsigned sed = time(NULL);
inline unsigned Rand() {
    sed = ((sed * 0x3abcd1ac + 0xabc12ab2) ^ (sed + 0x1230bace)) % mod;
    return sed;
}

int GetIncome() {
    int ans = 0;
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++) {
            if(plan[i][j]) ans += a[i][j];
            else ans += b[i][j];
            if(i - 1 >= 0 && plan[i - 1][j] != plan[i][j]) ans += c[i][j];
            if(j - 1 >= 0 && plan[i][j - 1] != plan[i][j]) ans += c[i][j];
            if(i + 1 < n && plan[i + 1][j] != plan[i][j]) ans += c[i][j];
            if(j + 1 < m && plan[i][j + 1] != plan[i][j]) ans += c[i][j];
        }
    return ans;
}

inline int change(int ans, int x, int y) {
    plan[x][y] ^= 1;
    ans += plan[x][y] ? a[x][y] - b[x][y] : b[x][y] - a[x][y];
    if(x - 1 >= 0) {
        ans += plan[x - 1][y] != plan[x][y] ?
            c[x][y] + c[x - 1][y] : -(c[x][y] + c[x - 1][y]);
    }
    if(y - 1 >= 0) {
        ans += plan[x][y - 1] != plan[x][y] ?
            c[x][y] + c[x][y - 1] : -(c[x][y] + c[x][y - 1]);
    }
    if(x + 1 < n) {
        ans += plan[x + 1][y] != plan[x][y] ?
            c[x][y] + c[x + 1][y] : -(c[x][y] + c[x + 1][y]);
    }
    if(y + 1 < m) {
        ans += plan[x][y + 1] != plan[x][y] ?
            c[x][y] + c[x][y + 1] : -(c[x][y] + c[x][y + 1]);
    }
    return ans;
}

int SA() {
    register int ans, old_ans, new_ans, cnt = 0;
    ans = old_ans = GetIncome();
    for(register double T = sup; T > eps; T *= delta) {
        int x = Rand() % n, y = Rand() % m;
        new_ans = change(old_ans, x, y);
        ans = new_ans > ans ? new_ans : ans;
        if(new_ans > old_ans ||
            Rand() <= exp((new_ans - old_ans) * 1.0 / T) * mod)
            old_ans = new_ans;
        else plan[x][y] ^= 1;
        cnt++;
    }
    return ans;
}

int main() {
    srand(time(NULL));
    scanf("%d%d", &n, &m);
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++)
            scanf("%d", &a[i][j]);
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++)
            scanf("%d", &b[i][j]);
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++)
            scanf("%d", &c[i][j]);
    for(int i = 0; i < n; i++)
        for(int j = 0; j < m; j++)
            plan[i][j] = a[i][j] > b[i][j];
    int ans = 0;
    for(int i = 0; i < 42; i++)
        ans = std::max(SA(), ans);
    printf("%d", ans);
    return 0;
}

結語

不過, Luogu的數據貌似有點水, 這個代碼在一些地方貌似過不了?

【Luogu P1935】[國家集訓隊]圈地計劃