您的位置:首页 > 运维架构

【正解】Openjudge 至少有多少只恼人的大青蛙 (dfs 剪枝 好题)

2017-05-18 10:44 549 查看

题目大意

http://cxsjsx.openjudge.cn/2015finalpractice/43/

讲真这题的大意我真的真的概括不来,各位看官老爷还是直接在原题目上看吧,顺便给出一个提交的链接。

题解

作为一道擅长屎题 向往屎题 热爱屎题的选手,不得不说,这道题确是一道搜索好(shi)题,所考察的搜索剪枝相关的知识点也是非常的全面。没有比较就没有差距,首先我放上一份无脑而又暴力的代码上来。

暴力代码 (哇这代码真他妈丑)

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

struct Point { //存储稻田中被糟蹋的坐标
int x,y;
Point() {}
Point(int _x,int _y):x(_x),y(_y) {}
bool operator < (const Point &b) const {
if(x!=b.x) return x<b.x;
return y<b.y;
}
}p[1005];

const int maxn=55;
int n,m,k;
int Map[maxn][maxn];
int sum;

inline bool inrange(const int &x,const int &y) {
if(x<1 || y<1 || x>n || y>m) return false;
return true;
}

int lit;
bool dfs(int dep) {
if(dep==lit) {
if(!sum) return true;
else return false;
}
register int i,j,x,y,u,v;
for(i=1;i<=k;++i) if(Map[p[i].x][p[i].y])
for(j=i+1;j<=k;++j) if(i!=j && Map[p[j].x][p[j].y]) { //枚举路径上的两个相邻的点
x=p[i].x, y=p[i].y;
u=p[j].x, v=p[j].y;
register int p1,p2,dx=x-u,dy=y-v;

int cnt=0, flag=true;
for(p1=u,p2=v;flag && inrange(p1,p2);p1+=dx,p2+=dy)
if(Map[p1][p2]) cnt++;
else flag=false;
if(!flag) continue;
for(p1=u-dx,p2=v-dy;flag && inrange(p1,p2);p1-=dx,p2-=dy)
if(Map[p1][p2]) cnt++;
else flag=false;
if(!flag || cnt<3) continue; //如果这两个点的路径上有空位或不足三个点则跳过

for(p1=u,p2=v;inrange(p1,p2);p1+=dx,p2+=dy) Map[p1][p2]--;
for(p1=u-dx,p2=v-dy;inrange(p1,p2);p1-=dx,p2-=dy) Map[p1][p2]--;
sum-=cnt; //处理该条路径

if(dfs(dep+1)) return true;

for(p1=u,p2=v;inrange(p1,p2);p1+=dx,p2+=dy) Map[p1][p2]++;
for(p1=u-dx,p2=v-dy;inrange(p1,p2);p1-=dx,p2-=dy) Map[p1][p2]++;
sum+=cnt; //还原该条路径
}
return false;
}

void work() {
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
Map[i][j]=0;
sum=0;
scanf("%d",&k);
for(int x,y,v,i=1;i<=k;i++) {
scanf("%d%d%d",&x,&y,&v);
p[i]=Point(x,y);
Map[x][y]+=v; //记录各个稻田上各个点被糟蹋的次数
sum+=v; //记录各个稻田总共被糟蹋的次数
}
sort(p+1,p+1+k);

lit=1;
while(lit<=14) { //由于青蛙数有限制,所以选用迭代加深搜索限制搜索深度
int flag=dfs(0);
if(flag) {
printf("%d\n",lit);
return;
} else lit++;
}
return;
}

int main() {
#ifndef ONLINE_JUDGE
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
#endif // ONLINE_JUDGE
int cas;
scanf("%d",&cas);
for(int i=0;i<cas;i++)
work();

return 0;
}


可以看出,这个搜索算法的确有些地方有可取之处,比如先将点排序后再进行搜索、使用迭代加深搜索限制搜索深度等,但是这个方法也有一个很大的缺点,就是每次想要确定下一只青蛙的路径的时候都要重新枚举一遍所有的路径上的相邻点对。再加上搜索函数的调用次数之多,时间复杂度是根本承受不了的。

但是经过简单的分析可以发现,每次搜索的时候重新枚举一遍所有路径上的相邻点对的坏处有很多:

首先,由于枚举的是两个相邻的点,因此每条路径在同一层搜索中可能被枚举多次

其次,每次枚举出的两个相邻的点,并不一定在一条合法的路径上,这里分为两种情况,1.路径上的其他点并没有被糟蹋过(即路径上存在空点),2.路径在稻田内并没有三个以上的合法点(即路径不合法)。

再者,由于在搜索函数调用次数太多,每一次都枚举一遍并判断路径是否合法显然太浪费时间了。

所以这个搜索就找到了优化的突破口,只要在搜索之前预处理出来所有可能的路径,并保证处理出的所有路径不重不漏就可以了。我们可以很容易地看出确定一条路径需要两个要素,即进入点和偏移量,所以就可以根据这两个要素进行存储和去重。这样在搜索的时候就可以直接枚举合法的路径了。

AC代码

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;

const int maxn=55;
int n,m,k;
int Map[maxn][maxn];

struct Point {
int x,y;
Point() {}
Point(int _x,int _y):x(_x),y(_y) {}
bool operator < (const Point &b) const {
if(x!=b.x) return x<b.x;
return y<b.y;
}
}p[705];

struct Frog {
int x,y,cnt,dx,dy,next;
Frog() {}
Frog(int _x,int _y,int _v,int _dx,int _dy,int nx):x(_x),y(_y),cnt(_v),dx(_dx),dy(_dy),next(nx) {}
bool operator < (const Frog &b) const {
return cnt>b.cnt;
}
}op[705*705];

int tot=0,head[maxn][maxn];
int sum=0;
void add_frog(int x,int y,int v,int dx,int dy) {
for(int i=head[x][y];~i;i=op[i].next) if(op[i].dx==dx && op[i].dy==dy) return;
//用邻接表对于特征相同的元素进行去重
op[++tot]=Frog(x,y,v,dx,dy,head[x][y]), head[x][y]=tot;
return;
}

void init() {
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
Map[i][j]=0, head[i][j]=-1;
tot=0; sum=0;
return;
}

inline bool inrange(const int &x,const int &y) {
if(x<1 || y<1 || x>n || y>m) return false;
return true;
}

int lit;
bool dfs(int dep,int last) {
if(dep==lit) {
if(!sum) return true;
return false;
}
if(op[last].cnt*(lit-dep)<sum) return false; //可行性剪枝,如果无论如何都达不到就剪掉
register int i,x,y,dx,dy;
for(i=last;i<=tot;++i) { //搜索的时候改为枚举本质不同的路径
x=op[i].x, y=op[i].y, dx=op[i].dx, dy=op[i].dy;
bool flag=true;
for(;flag && inrange(x,y);x+=dx,y+=dy)
if(!Map[x][y]) flag=false;
if(!flag) continue; //检验路径是否仍然可用
for(x-=dx,y-=dy;inrange(x,y);x-=dx,y-=dy)
--Map[x][y]; //处理路径
sum-=op[i].cnt;
if(dfs(dep+1,i)) return true;
for(x+=dx,y+=dy;inrange(x,y);x+=dx,y+=dy)
++Map[x][y]; //还原路径
sum+=op[i].cnt;
}
return false;
}

void work() {
scanf("%d%d%d",&n,&m,&k);
init();
for(int cnt,i=1;i<=k;i++) {
scanf("%d%d%d",&p[i].x,&p[i].y,&cnt);
sum+=cnt;
Map[p[i].x][p[i].y]+=cnt; //读入并处理被糟蹋的总数和各个点被糟蹋的次数
}
sort(p+1,p+1+k); //将所有点按横纵坐标排序,达到去重的作用(因为一条路径可能分为正反两向)

register int i,j,x,y,u,v;
for(i=1;i<=k;++i)
for(j=i+1;j<=k;++j) {
x=p[i].x, y=p[i].y, u=p[j].x, v=p[j].y;
register int p1,p2,dx=u-x, dy=v-y;
int flag=true, cnt=0;
for(p1=x-dx,p2=y-dy;flag && inrange(p1,p2);p1-=dx,p2-=dy)
if(Map[p1][p2]) flag=false;
if(!flag) continue;
for(p1=x,p2=y;flag && inrange(p1,p2);p1+=dx,p2+=dy)
if(Map[p1][p2]) cnt++;
else flag=false;
if(!flag || cnt<3) continue; //不合法的路径直接去掉
add_frog(x,y,cnt,dx,dy);
}
sort(op+1,op+1+tot); //优先枚举占点个数多的路径

lit=1;
while(lit<=14) { //仍然选择迭代加深搜索
if(dfs(0,1)) {
printf("%d\n",lit);
return;
} else lit++;
}
}

int main() {
#ifndef ONLINE_JUDGE
freopen("input.txt","r",stdin);
freopen("output.txt","w",stdout);
#endif // ONLINE_JUDGE
int cas;
scanf("%d",&cas);
while(cas--) work();

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息