1. 程式人生 > 實用技巧 >P3694 邦邦的大合唱站隊

P3694 邦邦的大合唱站隊

題面

看範圍,N 較大, M 符合狀壓的範圍。

f[S] 表示 編號為L(L是S的一位的團隊)且這一位為1 的團隊已經擺好了.例如1011就是將1,2,4團隊的歌手都擺好的最小值。

我們考慮如何轉移。在Dp轉移中,我們都是通過決策,將未知的狀態由已知的狀態推出,那麼決策顯然就是將一個團隊的歌手擺好需要多擺幾個人

假設現在狀態集合為S,那麼少一個歌手團隊的集合就是S ^ (1 << j-1) ,這個集合我們設為K,那麼f[S] 就由 f[K]轉移。

我們就讓K集合比S集合少的人數累計到答案裡。特別顯然的是,肯定不需要所有人都離開位置,以前就在那個位置上的可以不移動,所以在減去原先就在那個範圍的人。

我們需要預處理很多東西

1.我們需要預處理sum[i]表示i團隊一共有多少人,預處理簡單

2.我們需要預處理1 - j的這些位置i團隊有多少人,q[i][j]

3.我們需要預處理集合S中,某些位是1的歌手的和

完整的方程就是

f[S] = f[K] + sum[j] -(q[j][s[i]] - q[j][s[K]]);

K = S ^ (1 << j-1)

程式碼:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>

using
namespace std; const int N = 1e5 + 10; const int M = 22; int f[1 << M]; int n, m; int a[N], q[M][N]; //字首和,q[M][N];q[i][j] 1 - j 有多少個i團隊的人 //The number of singers who work in the team i that singing in (1 to j) int sum[M]; int S[1 << M]; int main(){ cin >> n >> m; for(int
i = 1;i <= n;++ i) cin >> a[i]; for(int i = 1;i <= n;++ i){ sum[a[i]] ++; q[a[i]][i] = 1; } for(int i = 1;i <= n;++ i){ for(int j = 1;j <= m;++ j) q[j][i] += q[j][i-1]; } for(int i = 1;i < 1 << m;++ i){//O(2^m * m) int k = 0; int tmp = i; while(tmp){ ++ k; if(tmp & 1) S[i] += sum[k]; tmp >>= 1; } } memset(f, 0x3f, sizeof f); f[0] = 0; for(int i = 1;i < 1 << m;++ i){ for(int j = 1;j <= m;++ j){ int k = i ^ (1 << (j - 1)); f[i] = min(f[i], f[k] + sum[j] - (q[j][S[i]] - q[j][S[k]])); } } cout << f[(1<<m)-1]; return 0; }