1. 程式人生 > >洛谷P3959 寶藏【狀壓DP】

洛谷P3959 寶藏【狀壓DP】

時空限制 1000ms / 256MB

題目描述

參與考古挖掘的小明得到了一份藏寶圖,藏寶圖上標出了 nn 個深埋在地下的寶藏屋, 也給出了這 nn 個寶藏屋之間可供開發的 mm 條道路和它們的長度。

小明決心親自前往挖掘所有寶藏屋中的寶藏。但是,每個寶藏屋距離地面都很遠, 也就是說,從地面打通一條到某個寶藏屋的道路是很困難的,而開發寶藏屋之間的道路 則相對容易很多。

小明的決心感動了考古挖掘的贊助商,贊助商決定免費贊助他打通一條從地面到某 個寶藏屋的通道,通往哪個寶藏屋則由小明來決定。

在此基礎上,小明還需要考慮如何開鑿寶藏屋之間的道路。已經開鑿出的道路可以 任意通行不消耗代價。每開鑿出一條新道路,小明就會與考古隊一起挖掘出由該條道路 所能到達的寶藏屋的寶藏。另外,小明不想開發無用道路,即兩個已經被挖掘過的寶藏 屋之間的道路無需再開發。

新開發一條道路的代價是:

L×K L代表這條道路的長度,K代表從贊助商幫你打通的寶藏屋到這條道路起點的寶藏屋所經過的 寶藏屋的數量(包括贊助商幫你打通的寶藏屋和這條道路起點的寶藏屋) 。

請你編寫程式為小明選定由贊助商打通的寶藏屋和之後開鑿的道路,使得工程總代 價最小,並輸出這個最小值。

輸入格式:

第一行兩個用空格分離的正整數 n,mn,m,代表寶藏屋的個數和道路數。

接下來 mm 行,每行三個用空格分離的正整數,分別是由一條道路連線的兩個寶藏 屋的編號(編號為 1-n1−n),和這條道路的長度 vv。

輸出格式:

一個正整數,表示最小的總代價。

【資料規模與約定】

對於 20%的資料: 保證輸入是一棵樹,1n8v5000 且所有的v都相等。

對於 40%的資料:1n80m1000v5000 且所有的 v都相等。

對於 70%的資料: 1n80m1000v5000 對於 100%的資料:1n120m1000v50000

NOIP2018賽前兩個月 回首2017爆零的噩夢

題目分析

感覺還是叫狀壓暴搜比較合適 dp[x]表示達到狀態x所需最小化費 dis[u]記錄當前狀態下從起點到u經過的寶藏屋數量

列舉每個點u作為起點

初始化dis[u]=1,dp[1<<(u1)]=0 並從初始狀態1<<(u1)開始深搜

假設當前x所表示的狀態中 所有已到達的點為ui,未到達的點為vi 對於同一個vi,可能同時存在多個個ui可以到達他 怎麼辦?其實就試著從每個uivi搜一次就好了

也就是先令dis[v]=dis[u]+1,同時更新dp[x|(1<<v1)] 然後向下一狀態x|(1<<v1)搜尋 回溯後再把dis[v]改回原來的值

所以說就是加了狀壓的暴搜啊

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
using namespace std;
typedef long long lt;

int read()
{
    int x=0,f=1;
    char ss=getchar();
    while(ss<'0'||ss>'9'){if(ss=='-')f=-1;ss=getchar();}
    while(ss>='0'&&ss<='9'){x=x*10+ss-'0';ss=getchar();}
    return x*f;
}

const int inf=1128481603;
const int maxn=8010;
int n,m;
int rem[20][20];
int dp[maxn],dis[20],ans=1e9;

void dfs(int x)
{
    for(int i=1;i<=n;++i)//列舉當前已挖過的寶藏屋
    if((1<<i-1)&x)
    for(int j=1;j<=n;++j)//列舉還沒有挖過的寶藏屋
    if( rem[i][j]!=inf && ((1<<j-1)&x)==0 )
    {
        if(dp[x|(1<<j-1)]>dp[x]+rem[i][j]*dis[i])//能更新下一狀態最優解才向下搜尋
        {
            int tt=dis[j];
            dis[j]=dis[i]+1;
            dp[x|(1<<j-1)]=dp[x]+rem[i][j]*dis[i];
            dfs(x|(1<<j-1));
            dis[j]=tt;//記得改回來
        }
    }
}

int main()
{
    n=read();m=read();
    memset(rem,67,sizeof(rem));
    for(int i=1;i<=m;++i)
    {
        int u=read(),v=read(),dis=read();
        rem[u][v]=min(rem[u][v],dis);
        rem[v][u]=min(rem[v][u],dis);
    }

    for(int i=1;i<=n;++i)//列舉每個點作起點
    {
        memset(dp,67,sizeof(dp));
        memset(dis,67,sizeof(dis)); 
        dis[i]=1; dp[1<<(i-1)]=0;
        dfs(1<<(i-1));
        ans=min(ans,dp[(1<<n)-1]);
    }
    printf("%lld",ans);
    return 0;
}