DP(状压专题十)

题意 :N个偶像排成一列,他们来自M个不同的乐队。每个团队至少有一个偶像。现在要求重新安排队列,使来自同一乐队的偶像连续的站在一起。重新安排的办法是,让若干偶像出列(剩下的偶像不动),然后让出列的偶像一个个归队到原来的空位,归队的位置任意。请问最少让多少偶像出列?

>> face <<

Strategy: 枚举每种队伍最终的状态, 并且默认已经排好的队伍是按顺序连续站立的, 这样我们就针对每种状态既定了队伍j的排列区间L:已经排好的队伍的总人数. R: L + 队伍j的总人数, 接下来只有所有不在区间[L,R]的队伍的人数可以对答案做出贡献了, 如何O(1)检查这些人数呢

答曰, 针对每个队伍维护一个前缀和

状态: dp[i] 分组分到状态’i’ 最少需要多少人出列

目标: dp[(1 << n) - 1 ]

边界: memset(dp, oo, sizeof(dp)), 显然,求最少, dp[0] = 0;

合法判断: 默认所有状态合法

转移方程:

d p [ i ∣ 1 &lt; &lt; j ] = m i n ( d p [ ( 1 &lt; &lt; n ) − 1 ] , d p [ i ] + s u m [ n ] [ j ] − s u m [ r ] [ j ] + s u m [ l ] [ j ] ) dp[i | 1 &lt;&lt; j] = min( dp[(1 &lt;&lt; n) - 1 ], dp[i] + sum[n][j] - sum[r][j] + sum[l][j]) dp[i1<<j]=min(dp[(1<<n)1],dp[i]+sum[n][j]sum[r][j]+sum[l][j])

attention: 无

双倍经验: 思路


//authoor : zjx 代码有参考他人题解
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define oo 0x3f3f3f3f
#define ll long long
#define db double
#define all(a) a.begin(), a.end()
#define met(a, b) memset(a, b, sizeof(a))
#define what_is(x) cout << #x << " is " << x << endl
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define _rev(i, a, b) for (int i = (a); i >= (b); --i)
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define lowbit(x) x &(-x)
#define bin(x) cout << #x << " is " << bitset<sizeof(int) * 2>(x) << endl
#define pi acos(-1.0)
using namespace std;

const int maxn = 1e5 + 10;
const int mod = 1e9 + 7;
const db eps = 1e-8;
ll n, m, dp[(1 << 20) + 10];
vector<vector<int>> sum(maxn, vector<int>(22));
int main()
{
	ios::sync_with_stdio(0);
	cin >> m >> n;
	_rep(i, 1, m)
	{
		int home;
		cin >> home;
		_rep(j, 1, n)
			sum[i][j] = sum[i - 1][j];
		sum[i][home]++;
	}
	met(dp, oo);
	dp[0] = 0;

	_rep(i, 0, (1 << n) - 1)
	{
		int l = 0, r;
		_rep(j, 1, n)
		{
			if (i >> j - 1 & 1) //前n个人中有多少人属于乐队j
				l += sum[m][j];
		}

		_rep(j, 1, n)
		{
			if (i >> j - 1 & 1)
				continue;
			r = l + sum[m][j];
			//what_is(sum[m][j] - sum[r][j] + sum[l][j]);
			assert(sum[m][j] - sum[r][j] + sum[l][j] >= 0);
			dp[i | 1 << j - 1] = min(dp[i | 1 << j - 1], dp[i] + sum[m][j] - sum[r][j] + sum[l][j]);
		}
	}
	cout << dp[(1 << n) - 1] << endl;

}

感悟: 挺有意思的一种状态

第一次回顾

  • 这种状态的转移不适合用枚举未知让已知来转移, 因为, 每种转移和已知的左右区间有关, 只有知道了当前状态的左右区间进行转移, 所以这里只有枚举已知转移未知, 因为枚举每一种已知的状态我们都等算出该状态的左右区间

@author:jasonleft 好像和以前写的差不多啊啊哈哈哈哈哈
#include <bits/stdc++.h>
#include <bits/extc++.h>
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define _rev(i, a, b) for (int i = (a); i >= (b); --i)
#define _for(i, a, b) for (int i = (a); i < (b); ++i)
#define _rof(i, a, b) for (int i = (a); i > (b); --i)
#define ll long long
#define db double
#define oo 0x3f3f3f3f
#define eps 0.00001
#define all(x) x.begin(), x.end()
#define met(a, b) memset(a, b, sizeof(a))
#define what_is(x) cerr << #x << " is " << x << endl
#define lowbit(x) x &(-x)
using namespace std;
const int maxn = 1e5 + 5;
const int mod = 9999973;
int sum[maxn][22],dp[1 << 20], n, m;
int main(){
	ios::sync_with_stdio(0);
	cin >> m >> n;//m: people, n: bands
	_rep(i, 1, m){
		int home;
		cin >> home;
		_rep(j, 1, n){
			sum[i][j] = sum[i-1][j];
		}
		sum[i][home]++;
	}
	met(dp, oo);
	dp[0] = 0;
	_for(i, 0, (1 << n)){
		int l = 0, r = 0;
		_rep(j, 1, n){
			if(i >> j - 1 & 1){
				l += sum[m][j];
			}
		}
		_rep(j, 1, n){
			if(!(i >> j - 1 & 1)){
				r = l + sum[m][j];
				dp[i | 1 << j - 1] = min(dp[i | 1 << j - 1], dp[i] + sum[m][j] - sum[r][j] + sum[l][j]);
			}
		}
	}
	cout << dp[(1 << n) - 1] << endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值