bzoj 2824: [AHOI2012]铁盘整理

题目描述

输入输出格式

输入格式:

共两行。第一行为铁盘个数N(1<=N<=50),第二行为N个不同的正整数,分别为从上到下的铁盘的半径R。(1<=R<=100)

输出格式:

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

输入输出样例

输入样例#1:
5
2 4 3 5 1
输出样例#1:
5
此题乍一看数据范围极小,自然以为是水题,便想用bfs直接水过去,结果很满意的得了10分,以下是暴力程序:

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define inf 999999999
#define For(i,a,b) for(i=a;i<=b;++i)
#define rep(i,a,b) for(i=a;i>=b;--i)
#define mm(a,b) memset(a,b,sizeof(a))
#define ll long long
using namespace std;
int read(){
    int sum=0,flag=1;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')flag=-1;c=getchar();}
    while(c>='0'&&c<='9')sum=sum*10+c-'0',c=getchar();
    return sum*flag;
}
int maxx(int x,int y){
    if(x<y)return y;
    return x;
}
int minn(int x,int y){
    if(x<y)return x;
    return y;
}
int abss(int x){
    if(x>=0)return x;
    return -x;
}
const int maxn = 100010;
int a[51],b[51];
struct node{
    int s[51],t;
};
int n,cnt;
node tmp;
void bfs(){
    int i,j;
    node T;
    memcpy(T.s,a,sizeof(a));
    T.t=0;
    queue<node>q;
    q.push(T);
    while(!q.empty()){
        T=q.front();
        q.pop();
        int jicun=n;
        while(T.s[jicun]==b[jicun])jicun--;
        For(i,2,jicun){
            memcpy(tmp.s,T.s,sizeof(T.s));
            int k=i>>1;
            For(j,1,k){//直接翻转
                int ch=tmp.s[j];
                tmp.s[j]=tmp.s[i-j+1];
                tmp.s[i-j+1]=ch;
            }
                tmp.t=T.t+1;
                if(memcmp(tmp.s,b,sizeof(b))==0){//如果到达目标状态则结束
                    printf("%d\n",T.t+1);
                    return ;
                }
                q.push(tmp);
        }
    }
}
int main(){
    int i,j;
    n=read();
    For(i,1,n){
        a[i]=read();
        b[i]=a[i];
    }
    sort(b+1,b+n+1);
    if(memcmp(a,b,sizeof(b))==0){
        printf("1\n");
        return 0;
    }
    bfs();
    return 0;
}
我发现是状态太多的原因,于是想用trie判重,但是数组小一点便re,大一点就mle,还是十分,仅贴一个程序作为纪念:

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define inf 999999999
#define For(i,a,b) for(i=a;i<=b;++i)
#define rep(i,a,b) for(i=a;i>=b;--i)
#define mm(a,b) memset(a,b,sizeof(a))
#define ll long long
using namespace std;
int read(){
    int sum=0,flag=1;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')flag=-1;c=getchar();}
    while(c>='0'&&c<='9')sum=sum*10+c-'0',c=getchar();
    return sum*flag;
}
int maxx(int x,int y){
    if(x<y)return y;
    return x;
}
int minn(int x,int y){
    if(x<y)return x;
    return y;
}
int abss(int x){
    if(x>=0)return x;
    return -x;
}
const int maxn = 100010;
int a[51],b[51];
struct node{
    int s[51],t;
};
int n,cnt;
int trie[2000000][51];
node tmp;
int pd(){//字典树部分
    int i,u=0,flag=0;
    For(i,1,n){
        if(!trie[u][tmp.s[i]]){
            flag=1;
            trie[u][tmp.s[i]]=++cnt;
        }
        u=trie[u][tmp.s[i]];
    }
    if(flag)return 1;
    return 0;
}
void bfs(){
    int i,j;
    node T;
    memcpy(T.s,a,sizeof(a));
    T.t=0;
    queue<node>q;
    q.push(T);
    while(!q.empty()){
        T=q.front();
        q.pop();
        For(i,2,n){
            memcpy(tmp.s,T.s,sizeof(T.s));
            int k=i>>1;
            For(j,1,k){
                int ch=tmp.s[j];
                tmp.s[j]=tmp.s[i-j+1];
                tmp.s[i-j+1]=ch;
            }
            if(pd()){//判重
                tmp.t=T.t+1;
                if(memcmp(tmp.s,b,sizeof(b))==0){
                    printf("%d\n",T.t+1);
                    return ;
                }
                q.push(tmp);
            }
        }
    }
}
int main(){
    int i,j;
    n=read();
    For(i,1,n){
        a[i]=read();
        b[i]=a[i];
    }
    sort(b+1,b+n+1);
    if(memcmp(a,b,sizeof(b))==0){
        printf("1\n");
        return 0;
    }
    bfs();
    return 0;
}
我开始认真对待这道题,一些题解里说最多可以只翻2*n次但都没有解释清楚,但身为蒟蒻的我一时半会儿想不通,以下就写出证明过程以供其他蒟蒻参考:

每一次确定最末尾的那个数,步骤是第一步,把最末尾未确定的数换到第一个位置,接下来就可以换到最后了,接下来一样的步骤,举个例子:

对于 2 4 3 5 1,最开始要确定最后一位,也就是要把最后一位变成5,于是先把5放到第一位,->5 3 4 2 1,再换到最后1 2 4 3 5,之后5就不管他了

只剩下1 2 4 3,于是一样的->4 2 1 3->3 1 2 4,还有3个数,3 1 2->2 1 3->1 2 3于是就排好了,算下来,最坏情况只需要2*n-1步

于是就有了第一个剪枝(大于2*n-1的情况可以直接减掉),然而有了这个剪枝还是没有什么用,我们还要利用另外一个原理:若两个数在目前位置上相邻,但在目标位置上不相邻,则因为是翻转,因此至少需要一步把这两个数分开,因此从当前排列到目标排列最少就需要出现这种情况的总和。待会代码里还会解释:

#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<queue>
#define inf 999999999
#define For(i,a,b) for(i=a;i<=b;++i)
#define rep(i,a,b) for(i=a;i>=b;--i)
#define mm(a,b) memset(a,b,sizeof(a))
#define ll long long
using namespace std;
int read(){
    int sum=0,flag=1;
    char c=getchar();
    while(c<'0'||c>'9'){if(c=='-')flag=-1;c=getchar();}
    while(c>='0'&&c<='9')sum=sum*10+c-'0',c=getchar();
    return sum*flag;
}
int maxx(int x,int y){
    if(x<y)return y;
    return x;
}
int minn(int x,int y){
    if(x<y)return x;
    return y;
}
int abss(int x){
    if(x>=0)return x;
    return -x;
}
const int maxn = 100010;
struct node{
	int value,pos,nowpos;
};
node a[maxn];
bool cmp(node c,node d){
	return c.value<d.value;
}
int ans,n;
void dfs(int sum,int total){//sum是已走过的步数,total是当前排列到达目标状态的预计步数
	if(!total&&a[1].nowpos==1){ans=minn(ans,sum);return;}//若目标步数为0,且第一个位置上是对的,这是为了预防如5 4 3 2 1这种情况的出现
	int i,j;
	For(i,2,n){//枚举翻前i位
		int zong=total;
		if(i<n){//如果i位置与i+1位置相邻且预计位置不相同,则可以通过这步操作分开他们,同时预计步数减少,但要考虑换过来后第1个数和第i+1个数的位置关系
			zong-=(abss(a[i].nowpos-a[i+1].nowpos)!=1)-(abss(a[1].nowpos-a[i+1].nowpos)!=1);
		}
		if(zong+sum<ans){//若当前步数加上预计步数大于已搜到的,就剪掉
			int mid=i/2;
			For(j,1,mid){
				int tmp=a[j].nowpos;
				a[j].nowpos=a[i-j+1].nowpos;
				a[i-j+1].nowpos=tmp;
			}
			dfs(sum+1,zong);
			For(j,1,mid){//回溯
				int tmp=a[j].nowpos;
				a[j].nowpos=a[i-j+1].nowpos;
				a[i-j+1].nowpos=tmp;
			}
		}
	}
}
int main(){
	int i,j;
	n=read();
	For(i,1,n){
		a[i].value=read();
		a[i].pos=i;
	}
	sort(a+1,a+n+1,cmp);
	For(i,1,n){
		a[a[i].pos].nowpos=i;//把当前位置的目标位置记录
	}
	int sum=0;
	ans=n*2-1;//至多搜n*2-1次
	For(i,2,n){
		sum+=abss(a[i].nowpos-a[i-1].nowpos)!=1;//如果当前排列中位置相邻但目标排列中并不相邻则预计步数加1
	}
	dfs(0,sum);
	printf("%d\n",ans);
	return 0;
} 
剪枝万岁!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值