NOIP 模擬 6 寶藏
阿新 • • 發佈:2021-06-11
題解
這道題是 \(NOIP\;\;2017\) 的原題 ,讓我見識到了什麼是真正的 \(dfs\)
考場上想出來要狀壓了,\(n\) 那麼小,肯定是壓 \(n\) 那一位,然後層第轉移,但是想了半天,一堆細節沒搞懂,拿了個暴力分跑了。
後來看了題解才知道這竟然可以 \(dfs\) 搜尋,雖然說剪支很噁心,但是那 \(70\) 短小的暴力太爽了
對於此題,由於我們更新某一條邊不僅和終點有關,也和起點有關,所以我們在列舉終點時也要列舉它是有哪個點轉移而來的
\(70\) 分 \(CODE:\)
Code
#include<bits/stdc++.h> #define ri register int #define p(i) ++i using namespace std; namespace IO{ char buf[1<<21],*p1=buf,*p2=buf; #define gc() p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++ inline int read() { ri x=0,f=1;char ch=gc(); while(ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=gc();} while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+(ch^48);ch=gc();} return x*f; } } using IO::read; namespace nanfeng{ #define cmax(x,y) ((x)>(y)?(x):(y)) #define cmin(x,y) ((x)>(y)?(y):(x)) #define FI FILE *IN #define FO FILE *OUT static const int N=14,INF=1e9+7; int G[N][N],visn[N],n,m,ans=INT_MAX,tmp; void dfs(int x) { if (x==n) {ans=cmin(ans,tmp);return;} if (tmp>ans) return;//小小的優化 for (ri i(1);i<=n;p(i)) {//要更新i if (visn[i]) continue; for (ri j(1);j<=n;p(j)) {//從j轉移而來 if (!visn[j]||G[j][i]>INF||i==j) continue; tmp+=visn[j]*G[j][i];visn[i]=visn[j]+1; dfs(x+1); tmp-=visn[j]*G[j][i];visn[i]=0;//回溯 } } } inline int main() { // FI=freopen("nanfeng.in","r",stdin); // FO=freopen("nanfeng.out","w",stdout); n=read(),m=read(); memset(G,127,sizeof(G));//初值要賦成最大 for (ri i(1);i<=m;p(i)) { int u=read(),v=read(),w=read(); G[u][v]=G[v][u]=cmin(G[u][v],w); } for (ri i(1);i<=n;p(i)) visn[i]=1,dfs(1),visn[i]=0;//記得根也要回溯 printf("%d\n",ans); return 0; } } int main() {return nanfeng::main();}
好了我們考慮剪枝
首先我們最容易想到的就是最優性剪枝,就像是一個估價函式,很好想
然後我們發現,每一個點它至少需要擴充套件一條邊,所以我們可以對每個點的所有的出邊進行由小到大排序
且題目中說了“兩個已經被挖掘過的寶藏屋之間的道路無需再開發”,所以我們可以放心大膽得把每一種情況更新,不用考慮什麼後效性
\(AC\kern 0.5emCODE:\)
Code
#include<bits/stdc++.h> #define ri register int #define p(i) ++i using namespace std; namespace IO{ char buf[1<<21],*p1=buf,*p2=buf; #define gc() p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++ inline int read() { ri x=0,f=1;char ch=gc(); while(ch<'0'||ch>'9') {if (ch=='-') f=-1;ch=gc();} while(ch>='0'&&ch<='9') {x=(x<<1)+(x<<3)+(ch^48);ch=gc();} return x*f; } } using IO::read; namespace nanfeng{ #define cmax(x,y) ((x)>(y)?(x):(y)) #define cmin(x,y) ((x)>(y)?(y):(x)) #define FI FILE *IN #define FO FILE *OUT #undef bool static const int N=14,INF=1e9+7; int G[N][N],visn[N],d[N],out[N][N],viso[N],tot,cnt,n,m,ans=INT_MAX,tmp,p; inline bool cmp(int x,int y) {return G[p][x]<G[p][y];} void dfs(int num,int x) { for (ri i(num);i<=cnt;p(i)) { if (tot+tmp*visn[viso[i]]>=ans) return;//如果答案直接小於估價值,直接此方案不合法 for (ri j(x);j<=d[viso[i]];p(j)) {//要從我們的最優開始搜 if (visn[out[viso[i]][j]]) continue;//搜過的點不搜 viso[p(cnt)]=out[viso[i]][j]; tmp-=G[viso[cnt]][out[viso[cnt]][1]]; tot+=visn[viso[i]]*G[viso[i]][viso[cnt]]; visn[viso[cnt]]=visn[viso[i]]+1; dfs(i,j+1);//我們要從j+1繼續搜,因為前面的已經搜過了 tmp+=G[viso[cnt]][out[viso[cnt]][1]]; tot-=visn[viso[i]]*G[viso[i]][viso[cnt]]; visn[viso[cnt--]]=0;//回溯 } x=1;//x要初始為一,因為更換初始點時,他的出邊也要從一開始 } if (cnt==n) {ans=cmin(ans,tot);return;} } inline int main() { // FI=freopen("nanfeng.in","r",stdin); // FO=freopen("nanfeng.out","w",stdout); n=read(),m=read(); memset(G,127,sizeof(G)); for (ri i(1);i<=m;p(i)) { int u=read(),v=read(),w=read(); if (G[u][v]<w) continue; if (G[u][v]>INF) out[out[u][p(d[u])]=v][p(d[v])]=u;//只有一條邊第一次輸入時才能加上 G[u][v]=G[v][u]=w; } for (ri i(1);i<=n;p(i)) { p=i; sort(out[i]+1,out[i]+d[i]+1,cmp);//排序 tmp+=G[i][out[i][1]]; } for (ri i(1);i<=n;p(i)) { tot=0;//估價值 viso[cnt=visn[i]=1]=i;//記錄一下選的點 tmp-=G[i][out[i][1]];//把根節點的最有性減去 visn[i]=1; dfs(1,1); visn[i]=0; tmp+=G[i][out[i][1]];//回溯 } printf("%d\n",ans); return 0; } } int main() {return nanfeng::main();}
複雜度 \(\mathcal O(n!/\text{玄學})\)