题意 :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 < < j ] = m i n ( d p [ ( 1 < < n ) − 1 ] , d p [ i ] + s u m [ n ] [ j ] − s u m [ r ] [ j ] + s u m [ l ] [ j ] ) dp[i | 1 << j] = min( dp[(1 << n) - 1 ], dp[i] + sum[n][j] - sum[r][j] + sum[l][j]) dp[i∣1<<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;
}