1. 程式人生 > >NOIP2017 題解

NOIP2017 題解

由於 ons lin 如果 can del spa name i++

QAQ……由於沒報上名並沒能親自去,自己切一切題聊以慰藉吧……
可能等到省選的時候我就沒有能力再不看題解自己切省選題了……辣雞HZ毀我青春

D1T1 小凱的疑惑

地球人都會做,懶得寫題解了……

D1T2 時間復雜度

分類討論+遞歸就行了,沒啥思維含量,略。

D1T3 逛公園

這題好勁啊……

D2T1 奶酪

\(O(n^2)\)暴力就行了,水題。

D2T2 寶藏

看到數據範圍一眼\(O^*(3^n)\)狀壓DP,其中\(3^n\)來自枚舉子集的子集。做法好像有很多,比如ryf的做法就比我快了10倍……日漸辣雞的窩
定義\(f_{i,j,S}\)表示以\(i\)為根的子樹,\(i\)在整棵樹裏的深度是\(j\)

,集合\(S\)中的所有點都在這棵子樹中時這棵子樹的最小總代價。
不難寫出轉移方程:
\[f_{i,j,S}=\min_{T\subsetneq S,k\in T}\{f_{k,j+1,T}+f_{i,j,S-T}+j\times w_{i,k}\}\]
(這個轉移方程是在枚舉與\(i\)相鄰的一個點\(k\)並把以\(k\)為根的子樹分出去)
然而你發現這個DP是\(O(n^3 3^n)\)的,並不能跑過去。
考慮優化轉移,如果把轉移方程中與\(S\)無關的部分拿出來並定義一個輔助數組
\[g_{i,j,T}=\min_{k\in T}\{f_{k,j+1,T}+j\times w_{i,k}\}\]

的話,我們就可以把狀態轉移方程改寫成
\[f_{i,j,S}=\min_{T\subsetneq S}\{f_{i,j,S-T}+g_{i,j,T}\}\]
這樣就可以把復雜度降到\(O(n^2 3^n)\)了,由於常數不大,並不需要卡常。

#include<cstdio>
#include<cstring>
#include<algorithm>
#define bit(x) (1<<((x)-1))
using namespace std;
const int INF=0x3f3f3f3f;
int n,m,w[15][15],f[15][15
][(1<<12)+1],g[15][15][(1<<12)+1]; int main(){ scanf("%d%d",&n,&m); memset(w,63,sizeof(w)); memset(f,63,sizeof(f)); memset(g,63,sizeof(g)); while(m--){ int x,y,z; scanf("%d%d%d",&x,&y,&z); w[x][y]=w[y][x]=min(w[x][y],z); } for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)f[i][j][bit(i)]=0; for(int j=n-1;j;j--)for(int s=0;s<(1<<n);s++)for(int i=1;i<=n;i++){ for(int k=1;k<=n;k++)if((bit(k)|s)==s&&w[i][k]<INF) g[i][j][s]=min(g[i][j][s],f[k][j+1][s]+w[i][k]*j); for(int t=s&(s-1);;(--t)&=s){ f[i][j][s]=min(f[i][j][s],f[i][j][s^t]+g[i][j][t]); if(!t)break; } } int ans=INF; for(int i=1;i<=n;i++)ans=min(ans,f[i][1][(1<<n)-1]); printf("%d",ans); return 0; }

D2T3 列隊

這題真是勁啊……比往年的數據結構NB到不知哪兒去了……

NOIP2017 題解