P3959 宝藏
P3959 寶藏
題目描述
參與考古挖掘的小明得到了一份藏寶圖,藏寶圖上標(biāo)出了 nn 個(gè)深埋在地下的寶藏屋, 也給出了這 nn 個(gè)寶藏屋之間可供開發(fā)的 mm 條道路和它們的長度。
小明決心親自前往挖掘所有寶藏屋中的寶藏。但是,每個(gè)寶藏屋距離地面都很遠(yuǎn), 也就是說,從地面打通一條到某個(gè)寶藏屋的道路是很困難的,而開發(fā)寶藏屋之間的道路 則相對(duì)容易很多。
小明的決心感動(dòng)了考古挖掘的贊助商,贊助商決定免費(fèi)贊助他打通一條從地面到某 個(gè)寶藏屋的通道,通往哪個(gè)寶藏屋則由小明來決定。
在此基礎(chǔ)上,小明還需要考慮如何開鑿寶藏屋之間的道路。已經(jīng)開鑿出的道路可以 任意通行不消耗代價(jià)。每開鑿出一條新道路,小明就會(huì)與考古隊(duì)一起挖掘出由該條道路 所能到達(dá)的寶藏屋的寶藏。另外,小明不想開發(fā)無用道路,即兩個(gè)已經(jīng)被挖掘過的寶藏 屋之間的道路無需再開發(fā)。
新開發(fā)一條道路的代價(jià)是:
[mathrm{L} imes mathrm{K}
]
L代表這條道路的長度,K代表從贊助商幫你打通的寶藏屋到這條道路起點(diǎn)的寶藏屋所經(jīng)過的 寶藏屋的數(shù)量(包括贊助商幫你打通的寶藏屋和這條道路起點(diǎn)的寶藏屋) 。
請(qǐng)你編寫程序?yàn)樾∶鬟x定由贊助商打通的寶藏屋和之后開鑿的道路,使得工程總代 價(jià)最小,并輸出這個(gè)最小值。
錯(cuò)誤日志: 錯(cuò)太多次了。。寫總結(jié)寫下面好了
Solution
經(jīng)由很多人討論得出, 此題靠譜正解為搜索
搜索啊, 毒瘤啊
那么現(xiàn)在談?wù)勥@題怎么剪枝
首先最基本的最優(yōu)化剪枝, 包含兩點(diǎn):
最優(yōu)化剪枝
當(dāng)搜索到某處發(fā)現(xiàn)此時(shí)記錄的答案已經(jīng)沒有記錄的最優(yōu)答案優(yōu), 結(jié)束此次搜索
設(shè)計(jì)一個(gè)估價(jià), 為當(dāng)前狀態(tài)下, 至少需要多少代價(jià)才能到達(dá)目標(biāo)狀態(tài), 當(dāng)估價(jià)值加上現(xiàn)值大于最優(yōu)記錄答案, 結(jié)束此次搜索
啥意思呢? 就是你要設(shè)計(jì)一個(gè)東西, 記為 (g(x)), 且 (g(x) leq) 現(xiàn)實(shí)值
當(dāng)如今 (tot + g(x) geq ans) 說明 現(xiàn)實(shí)值$ + tot < ans$ , 此時(shí)在搜索下去已經(jīng)沒有意義了, 需要返回
設(shè)計(jì)的 (g(x)) 需要容易計(jì)算, 其值越接近現(xiàn)實(shí)值剪枝效率越高
因?yàn)榇祟}中說明了小明建出來的東西是一顆樹, 所以每個(gè)點(diǎn)只會(huì)僅僅被一個(gè)點(diǎn)更新
故我們記錄到這個(gè)點(diǎn) (x) 的最小距離, 記為 (MIN_{x}), 當(dāng)前已決策集合為 (S)
那么估價(jià)函數(shù)就設(shè)為 $$sum_{i
otin S}MIN_{i}$$
其一定小于現(xiàn)實(shí)值沒得洗
每向決策中加入一個(gè)點(diǎn), 就從估價(jià)里拿出這部分, 當(dāng)把點(diǎn)去除時(shí)加回去即可
優(yōu)化搜索順序
把 (dfs) 看做建一棟樓
那么搜索的過程就是不斷建樓, 拆樓的過程
建到最后發(fā)現(xiàn)建得不和自己的心意(邊界), 亦或是建到一半發(fā)現(xiàn)太丑了最后肯定看不得(剪枝)
你會(huì)把樓拆了繼續(xù)堅(jiān)持建下去, 直到嘗試完所有可行的建法為止(搜索結(jié)束)
不斷嘗試 更新 進(jìn)步 超越
這就是固執(zhí)的 (dfs)
人生亦然啊
咳咳扯得有點(diǎn)遠(yuǎn)
說真的老拆樓建樓挺累的
能不能設(shè)計(jì)一個(gè)藍(lán)圖
讓我們能盡可能少拆樓呢?
要拆也是盡量少拆, 然后在這個(gè)基礎(chǔ)上繼續(xù)建
這就是優(yōu)化搜索順序
感性理解了以后, 我們?cè)诖a基礎(chǔ)上認(rèn)識(shí)一下如何在代碼中實(shí)現(xiàn)優(yōu)化搜索順序
簡單來說, 我們需要確定一種搜索順序, 使得重復(fù)搜索的規(guī)模盡可能小
此題中, 我們先確定搜索框架: 枚舉已選中的點(diǎn), 對(duì)于這些點(diǎn)選定還未加入集合的點(diǎn), 加入并更新答案, 進(jìn)一步加深搜索
那么怎么確定搜索順序使其最優(yōu)呢?
通過觀察, 我們可以發(fā)現(xiàn), 當(dāng)確定選中的點(diǎn)后, 我們選未加入集合的點(diǎn), 對(duì)于同一個(gè)點(diǎn), 搜索完后只有兩個(gè)可能:
此點(diǎn)被選中, 此時(shí)選中集合改變
此點(diǎn)沒被選中, 那么這個(gè)點(diǎn)(在這個(gè)集合狀態(tài)下)不可能在被選了
綜上, 我們?cè)O(shè)立兩個(gè)起點(diǎn), (begin1, begin2), 前一個(gè)記錄選中集合內(nèi)點(diǎn)枚舉到了哪個(gè), 后一個(gè)記錄在當(dāng)前集合下枚舉沒入集合的點(diǎn)到了哪個(gè)
新一輪搜索時(shí), 從斷點(diǎn)開始, 大大減少搜索量
Code
#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#include<algorithm>
#define LL long long
#define REP(i, x, y) for(int i = (x);i <= (y);i++)
using namespace std;
int RD(){
int out = 0,flag = 1;char c = getchar();
while(c < '0' || c >'9'){if(c == '-')flag = -1;c = getchar();}
while(c >= '0' && c <= '9'){out = out * 10 + c - '0';c = getchar();}
return flag * out;
}
const int maxn = (1 << 19), INF = 1e9;
int map[19][19];
int from[19];
int num, nr, leave;
int ans = INF;
bool in[19];
int cnt, lev[19];
int vis[19];
void dfs(int tot, int begin1, int begin2){
if(cnt == num){ans = min(ans, tot);return ;}
if(tot >= ans)return ;
REP(i, begin1, cnt){
int u = vis[i];
if(lev[u] * leave + tot >= ans)return ;
REP(v, begin2, num){
if(!in[v] && map[u][v] != INF){
vis[++cnt] = v;
in[v] = 1, lev[v] = lev[u] + 1;
leave -= map[from[v]][v];
dfs(tot + map[u][v] * lev[u], i, v + 1);
cnt--;
in[v] = 0, lev[v] = 0;
leave += map[from[v]][v];
}
}
begin2 = 1;
}
}
int main(){
num = RD(), nr = RD();
REP(i, 1, num)REP(j, 1, num)map[i][j] = INF;
REP(i, 1, nr){
int u = RD(), v = RD(), dis = RD();
map[u][v] = map[v][u] = min(map[u][v], dis);
}
REP(i, 1, num){
int minn = INF;
REP(j, 1, num){
if(map[j][i] < minn){
minn = map[j][i];
from[i] = j;//找一個(gè)最短的來自點(diǎn)作為最優(yōu)預(yù)算
}
}
leave += minn;
}
REP(i, 1, num){
in[i - 1] = 0, lev[i - 1] = 0;
in[i] = 1, lev[i] = 1;
leave -= map[from[i]][i];
vis[cnt = 1] = i;
dfs(0, 1, 1);
leave += map[from[i]][i];
}
printf("%d
", ans);
return 0;
}
總結(jié)
- 上一篇: CXF 客服端调用报错
- 下一篇: NoticeBoard