【Luogu 2534】[AHOI2012] 铁盘整理(IDA*搜索)

死亡人口诈尸

题目

题目描述

在训练中,一些臂力训练器材是少不了的,小龙在练习的时候发现举重器械上的铁盘放置的非常混乱,并没有按照从轻到重的顺序摆放,这样非常不利于循序渐进的锻炼。他打算利用一个非常省力气的办法来整理这些铁盘,即每次都拿起最上面的若干个圆盘并利用器械的力量上下翻转,这样翻转若干次以后,铁盘将会按照从小到大的顺序排列好。那么你能不能帮小龙确定,最少翻转几次就可以使铁盘按从小到大排序呢?

例如:下面的铁盘经过如图所示的以下几个步骤的翻转后变为从小到大排列。
在这里插入图片描述

输入格式

共两行。第一行为铁盘个数 N N N 1 ≤ N ≤ 16 1 \leq N \leq 16 1N16),第二行为 N N N 个不同的正整数,分别为从上到下的铁盘的半径 R R R 1 ≤ R ≤ 100 1 \leq R \leq 100 1R100)。

输出格式

一个正整数,表示使铁盘从小到大有序需要的最少翻转次数。

输入输出样例

输入 #1
5
2 4 3 5 1
输出 #1
5

题目传送门

思路

  • 首先,我们要注意的一点是:铁盘大小并不是按照 1 ∼ n 1\sim n 1n 的顺序排列的。
  • 所以我们要先离散化一下,保证最后得到的数列为 1 , 2 , 3 … n 1,2,3 \ldots n 1,2,3n
  • 然后看这数据量,应该是个搜索。
  • 所以……

爆搜,走人

#include <bits/stdc++.h>
#define rep(i, a, b) for (int i = a; i <= b; i++)
#define per(i, a, b) for (int i = a; i >= b; i--)
#define mem(a, x) memset(a, x, sizeof a)
#define pb push_back
#define umap unordered_map
#define pqueue priority_queue
//#define int long long

using namespace std;
typedef long long ll;
int n, a[20], b[20];
bool flg;

template <typename _T>
void rd(_T &x) {
    int f = 1; x = 0;
    char s = getchar();
    while (s > '9' || s < '0') {if (s == '-') f = -1; s = getchar();}
    while (s >= '0' && s <= '9') x = (x<<3)+(x<<1)+(s-'0'), s = getchar();
    x *= f;
}

bool check() {
    rep(i, 2, n) if (a[i] < a[i-1]) return false;
    return true;
}

void search(int step, int pre, int maxn) {
    if (check()) {flg = 1; return ;}
    if (step >= maxn) return ;
    rep(i, 1, n) {
        if (i == pre) continue;
        reverse(a+1, a+1+i);
        search(step+1, i, maxn);
        reverse(a+1, a+1+i);
    }
    return ;
}

int main() {
    rd(n);
    rep(i, 1, n) rd(a[i]), b[i] = a[i];
	sort(b+1, b+1+n);
    rep(i, 1, n) a[i] = lower_bound(b+1, b+1+n, a[i])-b;
    rep(i, 1, INT_MAX) {
        flg = 0;
        search(0, 0, i);
        if (flg == 1) {
            printf("%d\n", i);
            return 0;
        }
    }
    return 0;
}

10 10 10 分,亲测,不谢…
在这里插入图片描述


我们考虑 I D A ∗ IDA* IDA
当当前的翻转次数 + + + 估价函数值 > > > 允许搜索的最大步数时,直接 return
那么本题的估价函数是什么呢?

  • 首先,每次能拿起的是最上面的若干个圆盘。
  • 那么显然,一次翻转最多只能改变一对相邻数的差
  • 因此对于一个序列,有多少对相邻的数差不为 1 1 1,就至少要翻转多少次。
  • 需要注意的是,我们需令 a n + 1 = n + 1 a_{n+1}=n+1 an+1=n+1,这样才能使翻转结束后的方向正确。

那么本题的估价函数就出来了:

int f() {
    int ret = 0;
    rep(i, 1, n) ret += abs(a[i+1]-a[i]) != 1;
    return ret;
}

代码

很好码吧…

#include <bits/stdc++.h>
#define rep(i, a, b) for (int i = a; i <= b; i++)
#define per(i, a, b) for (int i = a; i >= b; i--)
#define mem(a, x) memset(a, x, sizeof a)
#define pb push_back
#define umap unordered_map
#define pqueue priority_queue
//#define int long long

using namespace std;
typedef long long ll;
int n, a[20], b[20];
bool flg;

template <typename _T>
void rd(_T &x) {
    int f = 1; x = 0;
    char s = getchar();
    while (s > '9' || s < '0') {if (s == '-') f = -1; s = getchar();}
    while (s >= '0' && s <= '9') x = (x<<3)+(x<<1)+(s-'0'), s = getchar();
    x *= f;
}

int f() {
    int ret = 0;
    rep(i, 1, n) ret += abs(a[i+1]-a[i]) != 1;
    return ret;
}

void search(int step, int pre, int maxn) {
    if (flg || step+f() > maxn) return ;
    if (f() == 0) {flg = 1; return ;}
    if (step >= maxn) return ;
    rep(i, 1, n) {
        if (i == pre) continue;
        reverse(a+1, a+1+i);
        search(step+1, i, maxn);
        reverse(a+1, a+1+i);
    }
    return ;
}

int main() {
    rd(n); a[n+1] = n+1;
    rep(i, 1, n) rd(a[i]), b[i] = a[i];
    sort(b+1, b+1+n);
    rep(i, 1, n) a[i] = lower_bound(b+1, b+1+n, a[i])-b;
    rep(i, 1, INT_MAX) {
        flg = 0;
        search(0, 0, i);
        if (flg == 1) {
            printf("%d\n", i);
            return 0;
        }
    }
    return 0;
}

©️2020 CSDN 皮肤主题: 1024 设计师:上身试试 返回首页