此题来源于同学笔试腾讯的一个题。
有
n
n
n张卡牌,第
i
i
i张正面为
a
i
a_i
ai,反面为
b
i
b_i
bi,一开始所有牌都正面朝上,现在可以执行若干操作,每次操作可以选择相邻的牌,交换位置,再翻转它们。求最少的操作次数,使得牌上数字从左到右非降。如果不可做到,输出-1。
n
<
=
18
n<=18
n<=18
传统做法是递归,同学现场递归代码如下。理论复杂度是
n
!
n!
n!,剪剪枝就可以过。
#include <iostream>
#include<string>
#include<vector>
#include<algorithm>
#include<cmath>
#include<unordered_map>
#include<map>
#include<queue>
#include<cmath>
#include<set>
using namespace std;
int n;
int a[20];
int b[20];
int ans = 0x3f3f3f3f;
set<int>ss;
void dfs(int pos, int pre, int sum)
{
if(pos == n){
//cout <<"gg"<<endl;
ans = min(ans,sum);
return ;
}
int i = 0;
set<int>::iterator it,it1;
it1 = ss.begin();
it1++;
for(it = ss.begin(); it !=ss.end();++i){
int tmp = *it;
if(tmp%2==pos%2){
if(a[tmp]>=pre){
ss.erase(it);
dfs(pos+1,a[tmp],sum+i);
ss.insert(tmp);
it = it1;
it1++;
}else{
++it;
++it1;
}
}else{
if(b[tmp]>=pre){
ss.erase(it);
dfs(pos+1,b[tmp],sum+i);
ss.insert(tmp);
it = it1;
it1++;
}else{
++it;
++it1;
}
}
}
}
void solve()
{
scanf("%d", &n);
for(int i = 0; i < n; i++)
scanf("%d", &a[i]);
for(int i = 0; i < n; i++)
scanf("%d", &b[i]);
for(int i = 0; i < n; i++)
ss.insert(i);
dfs(0,-0x3f3f3f3f,0);
if(ans == 0x3f3f3f3f){
printf("-1\n");
}else{
printf("%d\n",ans);
}
}
int main()
{
solve();
return 0;
}
自己写了状压暴力枚举做法,时间复杂度是 n ∗ n ∗ 2 n n*n*2^n n∗n∗2n记录下。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 10010;
/*
由于每种牌最终显示结果只有2种情况,要么正面,要么反面
再结合数据范围n = 18,考虑状压dp,枚举每一种情况,时间为2^n
【状压引入】
对于当前的s = 0100101 (用二进制来看)
那么其表示第2、5、7张牌为反面(因为其对应的bit为1),其余都为正面。
根据当前的s可以推出最终每张牌的显示结果(正/反) ,且它们是已经有序的 (我们记录其状态为s1)
那么现在问题就是判断初始的所有都为正面的牌(我们记录其状态为s0) ,能否通过排序得到最终的状态s1
具体怎么判断?我们可以看每张在s0的位置,和在s1的位置的差值。
如果相差为偶数次,说明在排序过程,他翻转了偶数次,最终显示结果为正面 ;
如果相差为奇数次,说明在排序过程,他翻转了奇数次,最终显示结果为反面 ;
我们要做的,就是根据其差值是奇数还是偶数,来看能否和s1中的相应牌的正、反面 对应,如果不能对应,
说明状态s0到s1是不可达的。
如果s0和s1是可达的,那么我们就更新ans。
*/
int n;
struct node {
int x[2];
}a[20];
struct node2 {
int val,pos;
int p;//0-正 1-反
}b[20];
bool cmp(const node2 &a,const node2 &b) {
return a.val < b.val;
}
int main() {
scanf("%d",&n);
for(int i = 0;i < n;++i)
scanf("%d",&a[i].x[0]);
for(int i = 0;i < n;++i)
scanf("%d",&a[i].x[1]);
//状压
int all = 1<<n;
int ans = -1;//-1表示无解
/*枚举所有情况,0表示正面,1表反面*/
for(int s = 0; s < all;++s) {
for(int i = 0;i < n;++i) {
if(s&(1<<i)) {
b[i].val = a[i].x[1];
b[i].p = 1;//反面
}
else {
b[i].val = a[i].x[0];
b[i].p = 0;//正面
}
b[i].pos = i;
}
sort(b,b+n,cmp);
bool flag = 1;//用于判断排序前和排序后,每个元素的翻转次数是否可以和正/反面匹配
int tmp = 0;
for(int i = 0;i < n;++i) {
int x = abs(i-b[i].pos);
//正面说明翻了偶数次,反面说明翻了奇数次
//翻了多少次,走了多少步
if(x%2 != b[i].p) {//illegal
flag = 0;
break;
}
for(int j = i+1;j < n;++j) {//求逆序对数
if(b[i].val < b[j].val && b[i].pos > b[j].pos)
++tmp;
}
}
if(flag) {
if(ans == -1) ans = tmp;
else if(ans > tmp) ans = tmp;
}
}
printf("%d\n",ans);
}