P3694 邦邦的大合唱站队

这道题跟 P3092 [USACO13NOV]No Change 很像,比较妙的状压dp

首先M<=20,由这一部分可以从状压入手

首先令dp[i]为状态为 i 时,最少的出队人数

 我们知道了 i 的状态,让 i  中的所有队都尝试放在最后排

现在我们要让 j 放在最后排,那么我们已经知道了dp[i^(1<<j)]的最优值,那么 i 和 i^(1<<j) 排的长度是可以预处理出来的

令line[i]为状态为i时,排的长度,所以第j队要排入区间 [ line[i^(1<<j)]+1,line[i] ],而这个区间内的第j队的人是不用出来的,其他人则要全部出来.

区间 [ line[i^(1<<j)]+1,line[i] ]:总人数 line[i]-line[i^(1<<j)],第j队人数 (sum[ line[i] ][j]-sum[ line[i^(1<<j)] ][j])

则状态转移:dp[i] = min( dp[i],dp[i^(1<<j)]+line[i]-line[i^(1<<j)]-(sum[ line[i] ][j]-sum[ line[i^(1<<j)] ][j])); 

最后答案便是dp[(1<<m)-1]

Code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mp make_pair
#define pb push_back   
#define popb pop_back  
#define fi first
#define se second
const int N=22;
const int M=1e5+10;
//const int inf=0x3f3f3f3f;     
//const ll INF=0x3ffffffffffff;
int T,n,m,dp[1<<N],sum[M][N],num[N],line[1<<N];
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
    return x*f;
}
int main()
{
//    freopen("","r",stdin);
//    freopen("","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=n;i++)
    {
        int x=read()-1;
        num[x]++;
        for(int j=0;j<m;j++) sum[i][j]=sum[i-1][j];
        sum[i][x]++;
    }
    for(int i=0;i<(1<<m);i++)
        for(int j=0;j<m;j++)
            if(i&(1<<j)) line[i]+=num[j];
    memset(dp,0x3f,sizeof(dp));
    dp[0]=0;
    for(int i=0;i<(1<<m);i++)
    {
        for(int j=0;j<m;j++)
        {
            if(! (i&(1<<j)) ) continue;
            dp[i]=min(dp[i],dp[i^(1<<j)]+ (line[i]-line[i^(1<<j)])-(sum[line[i]][j]-sum[line[i^(1<<j)]][j]));
        }
    }
    printf("%d",dp[(1<<m)-1]);
    return 0;
}

 

posted @ 2023-03-27 15:58  QAQ啥也不会  阅读(3)  评论(0编辑  收藏  举报