蜘蛛牌
Time Limit: 10000/5000 MS (Java/Others) Memory Limit: 32768/32768 K (Java/Others)
Total Submission(s): 3761 Accepted Submission(s):
1606
每组数据有一行,10个输入数据,数据的范围是[1,10],分别表示A到10,我们保证每组数据都是合法的。
很久之前...开学初吧困扰自己的一道破题,当时竟然连爆搜都没想到。
这个牌任意移动,所以每次的出发点并不固定,当时一直按照固定的方法来做自然WA了= =,今天写刚开始是个无剪纸爆搜2000+ms。。。。
后来发现剪枝:当总步数大于当前最小步数时,直接return,总算卡进1000ms内了,300+,看榜单都是0ms。。。原来还能用区间dp求解,而且我发现自己把这个搜索想的复杂了。
仔细想想,假设从一个位置移动到另一个位置算一次移动的话,无论怎么操作,最终(不做无意义的移动时)都要移动九次即可,只是总步数不同而已,一开始并没有想到这个!
原先搜索代码:
#include<bits/stdc++.h>
using namespace std;
struct Pock //结构体保存当前位置最小和最大牌面值
{
int up,down;
}a[15];
int s;
int debug=10;
bool pd()
{
int book=0,i;
for(i=1;i<=debug;++i)
if(a[i].up==0&&a[i].down==0) ++book;
return (book==debug-1?1:0);
}
void dfs(int sumn)
{
if(sumn>=s) return; //剪枝1,步数大于最小时直接return
if(pd()){
if(sumn<s) s=sumn;
return;
}
for(int i=1;i<=debug;++i){
if(a[i].up!=0&&a[i].down!=0){
for(int j=1;j<=debug;++j){
if(i==j) continue;
if(a[i].up+1==a[j].down){
int tup=a[i].up;
int tdown=a[i].down;
int jdown=a[j].down;
a[i].up=a[i].down=0;
a[j].down=tdown;
dfs(sumn+abs(i-j));
a[i].up=tup;
a[i].down=tdown;
a[j].down=jdown;
break; //剪枝2,比i位置up值大一的位置只会出现一个,所以搜索完直接break;
}
}
}
}
}
int main()
{
int n,t,i,j,c;
scanf("%d",&t);
while(t--){s=1e9;
for(i=1;i<=debug;++i){
scanf("%d",&c);
a[i].up=c;
a[i].down=c;
}
dfs(0);
printf("%d
",s);
}
return 0;
}
优化后的剪枝:
#include<bits/stdc++.h>
using namespace std;
int a[15],s;
bool vis[15];
void dfs(int cur,int sumn)
{
if(sumn>=s) return;
if(cur==10){
s=sumn;
return;
}
for(int i=1;i<=10;++i){
if(!vis[i]){
vis[i]=1;
for(int j=i+1;j<=10;++j){
if(!vis[j]){
dfs(cur+1,sumn+abs(a[i]-a[j]));
break;
}
}
vis[i]=0;
}
}
}
int main()
{
int i,j,t;
cin>>t;
while(t--){s=1e10,memset(vis,0,sizeof(vis));
for(i=1;i<=10;++i) {cin>>j;a[j]=i;}
dfs(1,0);
cout<<s<<endl;
}
return 0;
}
优化原理:
a[i]即面值为i的牌所在位置,之所以这样写是为了方便搜索,
一:面值为m的牌的位置只可能出现在面值>=m的位置上,因为只能把小牌移动到大牌上!
二:移动次数超过九次说明已经移动完毕!
这个写法简直不能再帅了!!!
DP做法:
我们在合牌的过程中总会把牌分成了最后的两堆,然后将这两堆合并后合并完成!
那么有多少种这样的可能呢?,显然:1-->k,k+1-->10(1<=k<10),不妨用一个二维数组记录这个操作,
dp[i][j]表示将牌面排成i->i+1->i+2......j需要的最少移动距离,那么显然我们需要的答案是dp[1][10];
由此得出状态转移方程: dp[i][j]=MIN(dp[i][k]+dp[k+1][j]+abs(a[k]-a[j]) | (i<=k<j) )
假设i,j表示面值,
显然想求得跨度为n=abs(i-j)的dp[i][j]的话需要跨度为1-->n-1的dp值才可继续递推下去,因此我们应该从跨度由低到高递推即可:
#include<bits/stdc++.h>
using namespace std;
int dp[11][11],a[11];
int main()
{
int t,i,j,k,e;
cin>>t;
while(t--){memset(dp,0,sizeof(dp));
for(i=1;i<=10;++i) cin>>e,a[e]=i;
for(k=1;k<10;++k) //枚举跨度
for(i=1;i+k<=10;++i){e=i+k;int M=999999999;
for(int temp=i;temp<e;++temp)
M=min(M,dp[i][temp]+dp[temp+1][e]+abs(a[temp]-a[e])); //找出最优的分割点
dp[i][e]=M;
}
cout<<dp[1][10]<<endl;
}
return 0;
}