原题地址:
UVa-10128
题意描述:
有N个身高互不相同的人排成一排,如果从前看有P人,从后看有R人,那么总共有多少种排列方式?
题目吐槽:
这帮奇葩的人类,身高竟然没有一样高的。都不是一个海平面的人还非要站一起。心疼那个最低的一秒钟。
解决方案:
客卿有三种解决方案:动态规划,记忆化DFS,DFS带剪枝
可谓算法的退步历史啊,看官们向下翻吧。
I 》动态规划
题目吐槽中已经暴露端倪,最低的最尴尬。因为它要想被看到就要站在队列最前面,或者队列的最后面。如果最矮的站在人群中自然是毛都看不见。
由此想到思路,我们从高到低把人放在队列里面,每次只有三种情况:
1.最低的人站在最前面
2.最低的人站在最后面
3.最低的人站在两个之间
在三种情况下分别有以下结果:
1.从前面看到的人数增加一,从后看人数不变
2.从后面看到的人数增加一,从前看人数不变
3.从前从后都看不到这个人,有好多位置可站
我们需要记录的是,现在队列中有几人,从前能看到几人,从后能看到几人。
int f[15][15][15];// f[n][p][r]
其实状态自然是:f[1][1][1]=1;
其分别能推出:f[2][2][1] f[2][1][2]
两种情况
当出现两人时会有“人群中的凹陷”的问题了
然后能推出:
f[2][2][1] --> f[3][3][1] f[3][2][2] f[3][2][1]
f[2][1][2] --> f[3][2][2] f[3][1][3] f[3][1][2]
推广而知:f[n][p][r] --> f[n+1][p+1][r] f[n+1][p][r+1] f[n+1][p][r]
每个位置的数据会贡献给三个位置,由此方式编程即可解决问题。
当然也可以将数据贡献的过程颠倒,即每个点的数据有三个来源。
只是将矮子放入人群中时需要考虑一下多种情况。
从含义上看:f[n][p][r]
的来源有三种f[n-1][p-1][r] f[n-1][p][r-1] f[n-1][p][r]
但是 f[n-1][p][r]
代表站在人群中的数据来源,这一数据应该 乘(n-2) 才是结果
因为f[n-1][p][r]
代表着 在n-1个人的队列中 从前看有 p人 从后看有r人
由于人站进去后看到的人数不变,所以这个矮子有 (n-2)个位置(人与人的空隙)可以站,所以情况数要 乘(n-2)
状态转移方程为:
f[n][p][r]=f[n-1][p-1][r]+f[n-1][p][r-1]+(n-2)*f[n-1][p][r]
于是可以愉快的写程序了,程序一开始先把14以内的数字全部算出来,然后读入题目,O(1)输出。
过后反思,为毛不把人从低到高放进去呢?因为高人不会被当着但是会挡着不知道多少人,不好计算,搜索的话就不是DP了。
贴上自己代码:
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
long long dp[20][20][20];
void core(){
memset(dp,0,sizeof(dp));
dp[1][1][1] = 1;
for (int i = 2; i < 20; i++)
for (int j = 1; j <= i; j++)
for (int k = 1; k <= i; k++)
dp[i][j][k] = dp[i-1][j-1][k]+dp[i-1][j][k-1]+(i-2)*dp[i-1][j][k];
}
int main(){
core();
int T,n,r,l;
cin>>T;
while (T--){
cin>>n>>r>>l;
cout<<dp[n][r][l]<<endl;
}
return 0;
}
II 》记忆化DFS
在纯DFS枚举的过程中会计算出来人数相同但是从前从后看人数不是答案的情况,那么,把他们记下来。万一下次就用到了呢?
然而对于大于10人的数据,DFS的过程时间巨长。。。
测试图如下:
Inter i7-6700HQ 单核2.6GHz 你们自己斟酌吧。。
贴上代码但不可作为测试代码:
#include<iostream>
#include<vector>
#include<ctime>
using namespace std;
long long TT[15][15][15];
vector<bool> flag;
int mmax,mmin,ll,rr;
void core(int k,int &n){
if(k==n){
TT[k][ll][rr]++;
return;
}
int rmax,rmin,rll,rrr;
rmax=mmax;
rmin=mmin;
rll=ll;
rrr=rr;
for(int i=0;i!=n;i++){
if(!flag[i]){
flag[i]=1;
if(i>mmax){
ll++;
mmax=i;
}
if(i<mmin){
rr++;
mmin=i;
}
core(k+1,n);
flag[i]=0;
mmax=rmax;
mmin=rmin;
ll=rll;
rr=rrr;
}
}
}
void build(int n){
mmax=-1;
mmin=999999999;
ll=0;
rr=0;
flag.resize(0,0);
flag.resize(13,0);
core(0,n);
}
int main(){
/*double ti=0;
for(int i=1;i!=14;i++){
ti=(double)clock()/CLOCKS_PER_SEC;
build(i);
cout<<endl<<"Calc: "<<i<<" \t\tCost: "<<(double)clock()/CLOCKS_PER_SEC-ti<<'s'<<endl;
}*/
int T;
cin>>T;
vector<bool> bb;
bb.resize(15,0);
while(T--){
int n,l,r;
cin>>n>>l>>r;
if(bb[n]==0){//如果N人表未计算过 建立
build(n);
bb[n]=1;
}//如果表建立过,直接用!!
cout<<TT[n][l][r]<<endl;
}
return 0;
}
//Designed by wolf
III 》DFS带剪枝
如果从前从后看到的人数已经超过目标人数就没必要再计算了,重点是需要在DFS过程中维护当前的状态。
贴上代码供暴力自检用:
#include<iostream>
#include<vector>
using namespace std;
//vector<int> num;
vector<bool> flag;
int mmax,mmin,ll,rr;
int n,l,r;
long long ans;
void core(int k){
if(k==n){
if(ll==l&&rr==r){
ans++;
}
return;
}
int rmax,rmin,rll,rrr;
rmax=mmax;
rmin=mmin;
rll=ll;
rrr=rr;
for(int i=0;i!=n;i++){
if(!flag[i]){
flag[i]=1;
//num[k]=i;
if(i>mmax){
ll++;
if(ll>l){
ll--;
flag[i]=0;
return;
}
mmax=i;
}
if(i<mmin){
rr++;
if(rr>r){
rr--;
ll=rll;
flag[i]=0;
return;
}
mmin=i;
}
core(k+1);
flag[i]=0;
mmax=rmax;
mmin=rmin;
ll=rll;
rr=rrr;
}
}
}
int main(){
int T;
cin>>T;
while(T--){
cin>>n>>l>>r;
mmax=-1;
mmin=999999999;
ll=0;
rr=0;
//num.resize(0,0);
flag.resize(0,0);
//num.resize(13,0);
flag.resize(13,0);
ans=0;
core(0);
cout<<ans<<endl;
}
return 0;
}
//Designed by wolf
全文完~