初步轉化
按題意,我們暴力枚舉這\(m\)個信號站的排列順序,時間復雜度\(O((m!)\cdot n)\)。
容易發現,題目給出的這個長度為\(n\)的序列\(S\)具體是什么不重要,重要的是,每一對信號站\(i,j\),在\(S\)里作為相鄰的位置,出現了多少次。也就是有多少個位置\(p\) (\(1\leq p<n\))滿足\(S_p=i,S_{p+1}=j\)。我們記這個數量為\(\text{cnt}[i][j]\)。
對於任意\(i\neq j\),\(\text{cnt}[i][j]\)就表示要從信號站\(i\),向信號站\(j\),進行多少次傳遞。設兩個信號站的位置,分別為\(\text{pos}[i],\text{pos}[j]\),則它們會對答案產生的代價就是:
做了這步轉化后,如果還是暴力枚舉這\(m\)個信號站的排列順序,時間復雜度優化為\(O(n+(m!)\cdot m^2)\)。至此我們復雜度的瓶頸與\(n\)無關了。
朴素DP
接下來,結合\(m\)的大小,容易想到狀壓DP。此處就有兩種狀態設計的思路:
- 設\(dp[s]\)表示考慮了前\(|s|\)個數,放在了\(s\)里的這些位置上。
- 設\(dp[s]\)表示考慮了前\(|s|\)個位置,填了\(s\)里的這些數。
發現,第一種狀態設計是無法轉移的。所以我們選擇第二種。事實上,考試時我就一直在想第一種,把自己搞自閉了。其實,按位置考慮、按數值考慮,這都是常見的方法,所以要學會靈活變通,一個不行就想想另一個。
我們選擇了第二種狀態設計:設\(dp[s]\)表示考慮了前\(|s|\)個位置,填了\(s\)里的這些數。
那考慮轉移。首先要枚舉在新加入的位置,也就是在第\(\text{pos}=|s|+1\)個位置,填哪個數。假設我們填\(i\)。考慮填\(i\)會新增哪些代價。它會分別和:前面的數(也就是\(s\)里的數)、后面的數(也就是不在\(s\)里,但也不等於\(i\)的數)產生代價。而且每種代價都有兩個方向。具體來說:
- 對於一個前面的數\(j\),從\(i\)到\(j\)產生的代價是:\(\text{pos}\cdot k\cdot \text{cnt}[i][j]\)。
- 對於一個前面的數\(j\),從\(j\)到\(i\)產生的代價是:\(\text{pos}\cdot\text{cnt}[j][i]\)。
- 對於一個后面的數\(j\),從\(i\)到\(j\)產生的代價是:\(-\text{pos}\cdot \text{cnt}[i][j]\)。
- 對於一個后面的數\(j\),從\(j\)到\(i\)產生的代價是:\(\text{pos}\cdot k\cdot \text{cnt}[j][i]\)。
所以我們也可以寫出式子:
如果枚舉\(i\),再枚舉\(j\),DP的時間復雜度\(O(2^mm^2)\)。期望得70分。
優化時間
我們繼續優化。考慮只枚舉\(i\)。把\(\text{pos}\)前的系數(也就是所有\(j\)的貢獻之和),預處理出來,不妨記為:\(\text{cost}(s,i)\)。那么上述的轉移式,也可以改寫為:\(dp[s+i]\leftarrow dp[s]+\text{pos}\cdot \text{cost}(s,i)\)。
考慮預處理\(\text{cost}(s,i)\)。首先,根據定義,\(i\notin s\)。
我們考慮,\(\text{cost}(s,i)\),可以從“\(s\)去掉某個數”的狀態,轉移過來。我們不妨就去掉\(j=\text{lowbit}(s)\)(也就是二進制下最低的,為\(1\)的位)。那么,\(\text{cost}(s,i)=\text{cost}(s-j,i)+(k\cdot \text{cnt}[i][j]+\text{cnt}[j][i])-(-\text{cnt}[i][j]+k\cdot \text{cnt}[j][i])\)。
這樣轉移是\(O(1)\)的。所以預處理的時間復雜度為\(O(2^mm)\),DP的時間復雜度也降為\(O(2^mm)\)。總時間復雜度\(O(n+2^mm)\)。但是\(\text{cost}\)數組會占用\(O(2^mm)\)的空間,這無法承受。所以還要繼續優化空間。
優化空間
這題優化空間的方法很多。我講一種比較好想的。想了解更多方法,可以閱讀這篇文章。
發現\(\text{cost}\)和\(\text{dp}\)的轉移,都是按集合從小到大進行的。所以我們不妨就按這個順序,一邊DP,一邊求\(\text{cost}\)。
對每個\(s\),我們把\(\text{cost}(s,\dots)\)視為一個大小為\(m\)的數組。當前的\(s\),我們先做DP轉移,再拿\(\text{cost}(s\dots)\)數組去更新所有\(\text{cost}(s'\dots )\)。發現更新完之后,\(\text{cost}(s,\dots)\)這個大小為\(m\)的數組,就可以不要了!
如果能及時地把\(\text{cost}(s,\dots)\)這個數組,以某種方式“刪除”掉(不再占用空間)。那么,在整個DP的過程中,同一時刻,我們存儲的最多只有兩種大小的\(s\)。因為\(\text{cost}(s,\dots)\)只會轉移到比它恰好大\(1\)的\(s'\)。那么空間的極限,就是\({23\choose 12}+{23\choose 11}\)個大小為\(m\)的數組,約等於\(237\text{MB}\),完全可以了。
那么怎么實現這個“刪除”呢?一種方法是用滾動數組。按大小枚舉所有集合。每處理完一種大小,就把數組“滾一次”。另一種方法,是直接利用\(\texttt{stl}\)里的\(\texttt{queue}\),向BFS一樣,每次取出隊首的\(s\),\(\texttt{pop}\)掉就行。
空間復雜度,\(\displaystyle O\left(2^m+m\cdot{m\choose\frac{m}{2}}\right)\)。前面分析過,大約\(237\text{MB}\),可以通過本題。
參考代碼(在LOJ查看):
//problem:LOJ3302
#include <bits/stdc++.h>
using namespace std;
#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fi first
#define se second
#define SZ(x) ((int)(x).size())
typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;
inline void ckmin(int& x,int y){x=(x<y?x:y);}
const int MAXN=1e5,MAXM=23;
const int INF=1e9;
int n,m,K,arr[MAXN+5],cnt[MAXM][MAXM];
int id[1<<MAXM],bitcnt[1<<MAXM];
int dp[1<<MAXM];
struct State{
int s;
int v[MAXM];
};
int main() {
//freopen("transfer.in","r",stdin);
//freopen("transfer.out","w",stdout);
cin>>n>>m>>K;
for(int i=1;i<=n;++i){
cin>>arr[i];
arr[i]--;
}
for(int i=1;i<n;++i){
cnt[arr[i]][arr[i+1]]++;
}
queue<State>q;
State s;
s.s=0;
for(int i=0;i<m;++i){
s.v[i]=0;
for(int j=0;j<m;++j)if(j!=i){
s.v[i]-=cnt[i][j];
s.v[i]+=K*cnt[j][i];
}
}
q.push(s);
int S=(1<<m)-1;
for(int i=0;i<m;++i)id[1<<i]=i;
for(int i=1;i<=S;++i){
bitcnt[i]=bitcnt[i>>1]+(i&1);
dp[i]=INF;
}
while(!q.empty()){
State curs=q.front();q.pop();
assert(dp[curs.s]<INF);
int p=bitcnt[curs.s]+1;
for(int t=S^curs.s;t;t-=(t&(-t))){
//枚舉curs.s的補集里的一個點i
int i=id[t&(-t)];
int new_dp=dp[curs.s];
int new_s=(curs.s|(1<<i));
new_dp+=p*curs.v[i];
ckmin(dp[new_s],new_dp);
}
for(int i=0;i<m;++i){
if((curs.s>>i)&1)break;
State new_state;
new_state.s=(curs.s|(1<<i));
for(int t=S^new_state.s;t;t-=(t&(-t))){
//枚舉new_state.s的補集里的一個點j
int j=id[t&(-t)];
new_state.v[j]=curs.v[j]
+(K*cnt[j][i]+cnt[i][j])
-(-cnt[j][i]+K*cnt[i][j]);
}
q.push(new_state);
}
}
cout<<dp[S]<<endl;
return 0;
}