题解 P2534 【[AHOI2012]铁盘整理】
Mars_Dingdang
2020-10-27 18:02:59
好久没发题解了。为了保住社贡分,水一篇。
本题数据不算大,**IDA** + **dfs** 即可。注意剪枝等细节处理。
## 题目大意
小龙在练习的时候发现举重器械上的铁盘放置的非常混乱,并没有按照从轻到重的顺序摆放,这样非常不利于循序渐进的锻炼。他打算利用一个非常省力气的办法来整理这些铁盘,即每次都拿起最上面的若干个圆盘并利用器械的力量上下翻转,这样翻转若干次以后,铁盘将会按照从小到大的顺序排列好。那么你能不能帮小龙确定,最少翻转几次就可以使铁盘按从小到大排序呢?
简单来说,即求一个长度为 $n$ 且互不相同的数列 $\rm R$,每次将 $[1,i]$ 翻转,求最少翻转几次使得该序列单调递增。
## 大体思路
首先看到数据范围,很容易想到搜索加优化加离散化。
用数组 $b$ 储存排序后的半径,用数组 $a$ 表示 $b$ 中第一次大于等于 $R_i$ 的元素的下标(离散化),用 $\text{lower\_bound}$ 实现即可。
离散化后,设计深搜的优化技巧。优化技巧无非几种:剪枝,迭代加深,预估(可行性剪枝)。对于本题,采用后两者。刚才的离散化有助于设计预估函数:对于一个序列,每次反转 $[1,i]$ 只能改变 $i,i+1$ 的差。因此有几对相邻数的差不为 $1$,就要翻转几次。
代码如下:
```cpp
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
b[i]=a[i];
}//输入
sort(b+1,b+n+1);//排序
for(int i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+n+1,a[i])-b;
}//离散化
a[n+1]=n+1;//翻转(n,n+1)
......
inline int step(){//预估函数
int ret=0;
for(int i=1;i<=n;i++)
ret+=(abs(a[i]-a[i+1])==1?0:1);//计算差不为 1 的个数。
return ret;
}
```
然后码一个暴力 $\rm{dfs+IDA}$,声明标记变量 $flag$。
- 判断预估函数的返回值:若为 $0$ 则表示满足,$flag$ 标为 $1$ 并返回;
- 预估步数已经超过,则直接返回(剪枝);
- 扩展 $n$ 种区间,翻转递归并回溯。
## 完整代码
```cpp
#include<bits/stdc++.h>//抄袭有奖
using namespace std;
const int maxn=1e20;
int n,a[maxn],b[maxn];
bool flag;
inline int step(){
int ret=0;
for(int i=1;i<=n;i++)
ret+=(abs(a[i]-a[i+1])==1?0:1);
return ret;
}//预估函数
inline void dfs(int s,int f,int pre){
if(flag) return ;
int num=step();
if(num==0) {
flag=1;
return ;
}//成功
if(s+num>f) return ;//剪枝
for(int i=1;i<=n;i++){
if(i==pre) continue;//避免重复
reverse(a+1,a+i+1);//翻转
dfs(s+1,f,i);//递归
reverse(a+1,a+i+1);//回溯
}
}
int main(){
scanf("%d",&n);
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b+1,b+n+1);//排序
for(int i=1;i<=n;i++){
a[i]=lower_bound(b+1,b+n+1,a[i])-b;
}
a[n+1]=n+1;//离散化
int i=0;
while(1){
dfs(0,i,0);
if(flag){
printf("%d",i);
return 0;
}
i++;
}//迭代加深
return 0;
}
```
## 后记
介绍部分函数的使用方法:
- `lower_bound(start,end,a)` 返回单调不减数列 $[start,end)$ 中第一个不小于 $a$ 的元素地址。对于单调不增序列,可以添加迭代器 `lower_bound(start,end,a,greater<int>())`。
- `reverse(start,end)` 表示将 $[start,end)$ 翻转。