洛谷 P3694 邦邦的大合唱站队 【状压DP】

洛谷 P3694 邦邦的大合唱站队 【状压DP】_第1张图片
数据约定: N ≤ 1 0 5 , M ≤ 20 N \leq 10^5, M \leq 20 N105,M20

思路

对于最终排好的状态,如果我们枚举排在最后一位的团队编号 j j j,可以发现:如果这个团队总共有 x x x 人的话,那么 [ n − x + 1 , n ] [n - x + 1, n] [nx+1,n] 一定都是团队 j j j 的人,那么 [ 1 , n − x ] [1,n - x] [1,nx] 就是一个子状态

我们可以定义 d p [ S ] dp[S] dp[S] 为已经排好队的团队集合需要的最少出队人数(这个排好队是指从 1 1 1 号位开始排,不在集合里的那些团队先不管,都放在后面),那么对于当前状态 S S S,我们枚举最后一个团队 j j j,利用子状态转移即可。提前预处理每个团队的前缀和就可以达到 O ( m 2 m ) O(m2^m) O(m2m) 的复杂度

#include
#define fore(i,l,r)	for(int i=(int)(l);i<(int)(r);++i)
#define fi first
#define se second
#define endl '\n'
#define ull unsigned long long
#define ALL(v) v.begin(), v.end()
#define Debug(x, ed) std::cerr << #x << " = " << x << ed;

const int INF=0x3f3f3f3f;
const long long INFLL=1e18;

typedef long long ll;

const int N = 100050;

int dp[1 << 20];
int sum[21][N];
int a[N];
int num[1 << 20]; //状态i下排好的人数

int main(){
    std::ios::sync_with_stdio(false);
    std::cin.tie(nullptr);
    std::cout.tie(nullptr);
    int n, m;
    std::cin >> n >> m;
    fore(i, 1, n + 1){
        std::cin >> a[i];
        --a[i];
        fore(j, 0, m) sum[j][i] = sum[j][i - 1];
        ++sum[a[i]][i];
    }
    fore(S, 1, 1 << m)
        fore(j, 0, m)
            if(S >> j & 1)
                num[S] += sum[j][n];
    memset(dp, INF, sizeof(dp));
    dp[0] = 0;
    fore(S, 1, 1 << m)
        fore(j, 0, m)
            if(S >> j & 1){
                int l = num[S - (1 << j)], r = num[S];
                dp[S] = std::min(dp[S], dp[S - (1 << j)] + sum[j][n] - sum[j][r] + sum[j][l]);
            }

    std::cout << dp[(1 << m) - 1];
    return 0;
}

你可能感兴趣的:(c++,动态规划,算法)