題目傳送門
\(n\) 個人,分別屬於 \(m\) 個組,要求同組的人站在一起。
每個人可以出隊,出隊後留出空位,出隊後的人可以在任意空位歸隊。
求最少出隊人數。
看到 \(m\) 的資料範圍極小,猜測可能是狀壓dp。
但沒有想出來到底是如何狀壓,於是重新審視題意。
可以發現,最後同組的人站在一起,一定會形成組的順序,例如:
111111,222222,333333類似如此,那麼可以全排列列舉組的順序。222222,111111,333333
333333,111111,222222
....
而確定組的順序後,每個組的範圍就確定下來了。
而對於每個人,如果他的位置恰好屬於他組的範圍,他便不用出隊,否則他一定要出隊。
依次檢驗,取最小值,即可得到答案。
複雜度為 \(o(nm!)\),只能通過四個點。
考慮進行優化,可以發現這個 \(o(n)\) 能夠用預處理字首和的方式去掉。
維護 \(s_\) 代表前 \(i\) 人中屬於 \(j\) 組的人數,即可用 \(o(1)\) 的時間計算出一組的範圍內應有多少人出隊。
複雜度降低到 \(o(m!)\),可以通過七個點。
#include #include #include using namespace std;
templatevoid read(t &r)
const int maxn = 1e5 + 10;
const int maxm = 21;
int n,m;
int a[maxn];
int num[maxm];
int order[maxm];
int s[maxn][maxm];
int main()
ans = min(ans,res);
} while (next_permutation(order + 1, order + 1 + m));
printf("%d",ans);
return 0;
}
想要將 \(o(m!)\) 的複雜度降低到 \(o(2^m)\),使用狀壓dp的方法。
有資料範圍的提示,很容易定義出狀態:
狀態 \(s\) 每位代表該組是否已處理完成。
如何轉移呢?可以列舉排在最後的組。
計算出當前佇列的長度,列舉排在最後的組,已知排在最後組的長度,即可確定排在最後組的範圍。
確定組範圍後,即可計算出該範圍內應有多少人出隊。
列舉當前狀態每個可能的最後組,取最小值進行轉移。
dp[status] = min(dp[status],dp[status ^ (1<<(i-1))] + num[i] - (s[r][i] - s[l-1][i]));
實質上,這個狀壓dp也是在列舉順序。
不同於全排列,狀壓轉移時並不知道子狀態中的順序,只知道子狀態已完成的組,而將當前組加在子狀態佇列最後。
或許藉此剪去了部分無用的列舉。
筆者使用了記憶化搜尋的寫法,預處理了每個狀態的佇列長度。
#include #include using namespace std;
templateinline t min(const t &a,const t &b)
const int maxn = 1e5 + 10;
const int maxm = 21;
int n,m;
int a[maxn];
int num[maxm];
int s[maxn][maxm];
int len[1
return dp[status];
}int main()
Luogu P3694 邦邦的大合唱站隊
link n 個偶像排成一列,他們來自 m 個不同的樂隊。每個團隊至少有乙個偶像。現在要求重新安排佇列,使來自同一樂隊的偶像連續的站在一起。重新安排的辦法是,讓若干偶像出列 剩下的偶像不動 然後讓出列的偶像乙個個歸隊到原來的空位,歸隊的位置任意。請問最少讓多少偶像出列?1 le le10 5,m l...
P3694 邦邦的大合唱站隊
bang dream 裡的所有偶像樂隊要一起大合唱,不過在排隊上出了一些問題。n個偶像排成一列,他們來自m個不同的樂隊。每個團隊至少有乙個偶像。現在要求重新安排佇列,使來自同一樂隊的偶像連續的站在一起。重新安排的辦法是,讓若干偶像出列 剩下的偶像不動 然後讓出列的偶像乙個個歸隊到原來的空位,歸隊的位...
題解 LuoGu3694 邦邦的大合唱站隊
原題傳送門 暗示明顯,狀壓dpdp dp狀態設計簡單,當前有多少樂隊已經排好隊為1,其餘為0,壓成二進位制iiid pi dp i dpi 表示狀態i ii的答案 本來還一直在想如何來是最優策略,突然靈機一動 我這不是dpdp dp嘛,那我萬一既然有了最優策略,我還d dd什麼呢 現在,只有最終狀態...