题解 P3694 【邦邦的大合唱站队】

天泽龟

2019-08-12 12:57:49

Solution

### ~~题解开始前请允许我高呼一声:BangDream天下第一!~~ BangDream国服开始公测啦,有没有小伙伴一起来玩啊OwO~ --- 回归正题。 首先注意到这是一道偶像多达五个数量级的~~LIVE~~题目,要么贪心要么DP,又注意到虽然N很大,但总乐队数仅有20,可考虑对其状压。 我们可以设状态S为当前已经排列好的乐队编号集合,那么先不管这几个乐队怎么排列,她们的人数总和是一定的(不妨设其为$l[S]$),那她们从第一个位子站起,所占有的区间一定是$[~1~,~l[S]~]$。 那我们枚举站在最后一个位置的乐队(不妨设人数为$num[i]$),就同样可以确定它所对应的区间是$[~l[S]-num[i]+1~,~l[S]~]$,那么原先在这个范围内的,该乐队的偶像不用出列,其他人全部出列就可以了。 其中,$num[i],l[S]$,还有$s[i][j]$表示初始状态下前i个位子中编号为j的乐队的人数,这三个信息都可以由初始化得到。 于是便得到了最终的转移方程,设$f[S]$ 为状态S时所出列的最小人数,那么对于$\forall j\in S$,满足 $$f[S]=min(f[~S~xor~(1<<j)~]+num[j]-s[r][j]+s[l][j]$$ 其中$l,r$表示上文提到的最后一个乐队所对应的区间。 然后就可以愉快的AC辣,上丑陋的代码: ```cpp #include <iostream> #include <cstring> using namespace std; int n,m,x,f[2000000]; //首先枚举人肯定傻逼TLE,数据范围只能用状压 //设f[i]--> 状态I表示从左往右被标记的乐队已排好; //f[i]表示状压为I时所出列最少人数。 int s[100010][30],num[30],sm[2000000]; bool d(int s,int x){ return (s&(1<<(x-1))); } void dfs(int x,int s,bool b) { if (x==m) return; if (b) sm[s|(1<<x)]=sm[s]+num[x+1],dfs(x+1,s|(1<<x),1),dfs(x+1,s|(1<<x),0); else dfs(x+1,s,1),dfs(x+1,s,0); } int main() { cin>>n>>m; for (int i=1;i<=n;i++) { cin>>x; for (int j=1;j<=m;j++) s[i][j]=s[i-1][j]; s[i][x]++; num[x]++; } dfs(0,0,0); dfs(0,0,1); memset(f,0x3f,sizeof(f)); f[0]=0; for (int i=1;i<(1<<m);i++) { for (int j=1;j<=m;j++) if (d(i,j)) { int l=sm[i^(1<<j-1)],r=sm[i]; f[i]=min(f[i],f[i^(1<<(j-1))]+(r-l)-(s[r][j]-s[l][j])); } } cout<<f[(1<<m)-1]<<endl; return 0; } ```