暑期练习(一) DFS

刷题(一) DFS

1.HDU-1518 https://vjudge.net/problem/HDU-1518
Given a set of sticks of various lengths, is it possible to join them end-to-end to form a square?
Input
The first line of input contains N, the number of test cases. Each test case begins with an integer 4 <= M <= 20, the number of sticks. M integers follow; each gives the length of a stick - an integer between 1 and 10,000.
Output
For each case, output a line containing “yes” if is is possible to form a square; otherwise output “no”.
Sample Input
3
4 1 1 1 1
5 10 20 30 40 50
8 1 7 2 6 4 4 3 5
Sample Output
yes
no
yes

经典DFS+剪枝
题意就是好多棍子,看能不能拼成正方形。主要注意的有几点:
1.所有棍子都要用到,不能剩余
2.输入已经保证大于4根棍子了。所以无需判断可能小于3根棍子的情况
3.棍长的总数首先要是4的倍数,才能进行。否则直接输出 “no”
4.当前面前提满足以后,再满足3 根棍子拼好,就完工了。最后一根一定能拼好。
解法就是DFS------->深度优先搜索。DFS的思路就是一个图沿着一条路走下去,当走不下去的时候就回溯到上一结点再去走没有走过的岔路。
换算成数据结构的话,就要有一个“标记”来标记每个结点是否走过。DFS具体的实现方式,常见的一种就是:循环里面嵌套递归,这也算是一个DFS的框架。而剩下的要补充的“题眼”(也就是关键的地方)是要转移的状态。

//主要思路就是DFS搜索边长,不够边长就拼接为边长
#include
#include
#include
#include
using namespace std;

const int N=25;
int n,t,a[N],len;
bool vis[N];

bool cmp(int a,int b)  //用于sort从大到小排序
{
    return a>b;
}
bool dfs(int count,int start,int goal)  //count满足条件的边数,start是遍历起始,goal是拼接所需的下一个边长即目标
{
    if(count==3) return true;  //因为sum%4=0,故找到3条边一定满足
    
    for(int i=start;i<t;i++)
    {
        if(!vis[i])
        {
            vis[i]=true;
            if(a[i]==goal)   //如果满足了当前目标,count+1,从头开始继续寻找,goal重新变为ll即边长
            {
                if(dfs(count+1,0,len)) return true;
            }
            else if(a[i]<goal)  //如果比当前所需边长小,那么goal变为能与当前边拼接从而满足要求的边
            {
                if(dfs(count,i+1,goal-a[i])) return true;
            }
            vis[i]=false;
        }
    }
    return false;
}
int main()
{
    cin>>n;
    while(n--)
    {
        int sum=0;
        cin>>t;
        for(int i=0;i<t;i++)
        {
            scanf("%d",&a[i]);
            sum=sum+a[i];
        }
        len=sum/4;  //长方形边长
        sort(a,a+t,cmp);  //从小到大排序,第一个就是最大的了,便于之后操作

        memset(vis,0,sizeof(vis));  //每次开始前记得要清除vis

        if(t<=3||sum%4!=0)  //边数小于3,或者边长综合不是4的倍数,直接no
            printf("no\n");
        else if(dfs(0,0,len)) printf("yes\n");
        else printf("no\n");
    }
    getchar();getchar();
    return 0;
}

2.HDU 1459 非常可乐
大家一定觉的运动以后喝可乐是一件很惬意的事情,但是seeyou却不这么认为。因为每次当seeyou买了可乐以后,阿牛就要求和seeyou一起分享这一瓶可乐,而且一定要喝的和seeyou一样多。但seeyou的手中只有两个杯子,它们的容量分别是N 毫升和M 毫升 可乐的体积为S (S<101)毫升 (正好装满一瓶) ,它们三个之间可以相互倒可乐 (都是没有刻度的,且 S==N+M,101>S>0,N>0,M>0) 。聪明的ACMER你们说他们能平分吗?如果能请输出倒可乐的最少的次数,如果不能输出"NO"。
Input
三个整数 : S 可乐的体积 , N 和 M是两个杯子的容量,以"0 0 0"结束。
Output
如果能平分的话请输出最少要倒的次数,否则输出"NO"。
Sample Input
7 4 3
4 1 3
0 0 0
Sample Output
NO
3

#include
#include
#include
#include
using namespace std;

const int N=110;
int s,n,m,count,ans;
bool vis[N][N][N];
bool flag;

void dfs(int x,int y,int z,int count)
{
    if(x==s/2&&y==s/2||x==s/2&&z==s/2||y==s/2&&z==s/2)
    {
        flag=true;
        if(count<ans) ans=count;
        return ;
    }
    //接下来分为6种情况
    //x->y
    if(x>0&&y<n)
    {
        int t=min(x,n-y);
        if(!vis[x-t][y+t][z])
        {
            vis[x-t][y+t][z]=true;
            dfs(x-t,y+t,z,count+1);
            vis[x-t][y+t][z]=false;
        }
    }
    //y->x
    if(y>0&&x<s)
    {
        int t=min(y,s-x);
        if(!vis[x+t][y-t][z])
        {
            vis[x+t][y-t][z]=true;
            dfs(x+t,y-t,z,count+1);
            vis[x+t][y-t][z]=false;
        }
    }
    //x->z
    if(x>0&&z<m)
    {
        int t=min(x,m-z);
        if(!vis[x-t][y][z+t])
        {
            vis[x-t][y][z+t]=true;
            dfs(x-t,y,z+t,count+1);
            vis[x-t][y][z+t]=false;
        }
    }
    //z->x
    if(z>0&&x<s)
    {
        int t=min(z,s-x);
        if(!vis[x+t][y][z-t])
        {
            vis[x+t][y][z-t]=true;
            dfs(x+t,y,z-t,count+1);
            vis[x+t][y][z-t]=false;
        }
    }
    //y->z
    if(y>0&&z<m)
    {
        int t=min(y,m-z);
        if(!vis[x][y-t][z+t])
        {
            vis[x][y-t][z+t]=true;
            dfs(x,y-t,z+t,count+1);
            vis[x][y-t][z+t]=false;
        }
    }
    //z->y
    if(z>0&&y<n)
    {
        int t=min(z,n-y);
        if(!vis[x][y+t][z-t])
        {
            vis[x][y+t][z-t]=true;
            dfs(x,y+t,z-t,count+1);
            vis[x][y+t][z-t]=false;
        }
    }
}

int main()
{
    while(1)
    {
        cin>>s>>n>>m;
        if(s==0&&n==0&&m==0) break;
        if(s%2)
        {
            cout<<"NO"<<endl;  //为奇数则不可能分开
            continue;
        } 
        memset(vis,0,sizeof(vis));
        vis[s][0][0]=1;
        ans=999999999;
        flag=false;
        dfs(s,0,0,0);
        if(flag) cout<<ans<<endl;
        else cout<<"NO"<<endl;
    }
    
    getchar();getchar();
    return 0;
}

3.HDU 1312 red and black
There is a rectangular room, covered with square tiles. Each tile is colored either red or black. A man is standing on a black tile. From a tile, he can move to one of four adjacent tiles. But he can’t move on red tiles, he can move only on black tiles.

Write a program to count the number of black tiles which he can reach by repeating the moves described above.
Input
The input consists of multiple data sets. A data set starts with a line containing two positive integers W and H; W and H are the numbers of tiles in the x- and y- directions, respectively. W and H are not more than 20.

There are H more lines in the data set, each of which includes W characters. Each character represents the color of a tile as follows.

‘.’ - a black tile
‘#’ - a red tile
‘@’ - a man on a black tile(appears exactly once in a data set)
Output
For each data set, your program should output a line which contains the number of tiles he can reach from the initial tile (including itself).

#include
#include
#include
#include
using namespace std;

const int N=22;
char g[N][N];
bool vis[N][N];
int n,m;
int dx[4]={-1,0,1,0},dy[4]={0,1,0,-1};

int dfs(int x,int y)
{
    int count=1;
    vis[x][y]=true;
    for(int i=0;i<4;i++)
    {
        int a=x+dx[i],b=y+dy[i];
        if(a<0||a>=n||b<0||b>=m) continue;
        if(g[a][b]!='.') continue;
        if(vis[a][b]) continue;

        count=count+dfs(a,b);
    }
    return count;
}
int main()
{
    while(cin>>m>>n)
    {
        if(m==0||n==0) break;
        int x,y;
        for(int i=0;i<n;i++)
            for(int j=0;j<m;j++)
            {
                cin>>g[i][j];
                if(g[i][j]=='@')
                {
                    x=i;
                    y=j;
                }
            }
        memset(vis,0,sizeof(vis));
        cout<<dfs(x,y)<<endl;
    }
    
    getchar();getchar();
    return 0;
}

4 .皇后问题 HDU – 2553
在N*N的方格棋盘放置了N个皇后,使得它们不相互攻击(即任意2个皇后不允许处在同一排,同一列,也不允许处在与棋盘边框成45角的斜线上。
你的任务是,对于给定的N,求出有多少种合法的放置方法。
Input
共有若干行,每行一个正整数N≤10,表示棋盘和皇后的数量;如果N=0,表示结束。
Output
共有若干行,每行一个正整数,表示对应输入行的皇后的不同放置数量。
Sample Input
1
8
5
0
Sample Output
1
92
10

#include
#include
#include
#include
using namespace std;
const int N=50;
int n,res,a[N];
bool vis[3][N]; //3个方向,即列,两个斜45度方向上都不能有皇后

void dfs(int row)
{
    if(row==n+1)
    {
        res++;
        return ;
    } 
    for(int i=1;i<=n;i++)  //对于第row行,枚举每一列
    {
        if(vis[0][i]==0&&vis[1][row-i+n]==0&&vis[2][row+i]==0)
        {
            vis[0][i]=vis[1][row-i+n]=vis[2][row+i]=true;
            dfs(row+1);
            vis[0][i]=vis[1][row-i+n]=vis[2][row+i]=false;
        }
    }
        
}
int main()
{
    for(n = 1; n <= 10; n++)  //先打表,把1到10的先算出来,按要求输出即可,不然会超时的
    {
        memset(vis,0,sizeof(vis));
        res = 0;
        dfs(1);
        a[n] = res;
    }
    while(scanf("%d",&n), n)
     {
        printf("%d\n",a[n]);
     }

    getchar();getchar();
    return 0;
}

5.HDU 1175 连连看
Problem Description
“连连看”相信很多人都玩过。没玩过也没关系,下面我给大家介绍一下游戏规则:在一个棋盘中,放了很多的棋子。如果某两个相同的棋子,可以通过一条线连起来(这条线不能经过其它棋子),而且线的转折次数不超过两次,那么这两个棋子就可以在棋盘上消去。不好意思,由于我以前没有玩过连连看,咨询了同学的意见,连线不能从外面绕过去的,但事实上这是错的。现在已经酿成大祸,就只能将错就错了,连线不能从外围绕过。
玩家鼠标先后点击两块棋子,试图将他们消去,然后游戏的后台判断这两个方格能不能消去。现在你的任务就是写这个后台程序。

Input
输入数据有多组。每组数据的第一行有两个正整数n,m(0 注意:询问之间无先后关系,都是针对当前状态的!

Output
每一组输入数据对应一行输出。如果能消去则输出"YES",不能则输出"NO"。

Sample Input
3 4
1 2 3 4
0 0 0 0
4 3 2 1
4
1 1 3 4
1 1 2 4
1 1 3 3
2 1 2 4
3 4
0 1 4 3
0 2 4 1
0 0 0 0
2
1 1 2 4
1 3 2 3
0 0

Sample Output
YES
NO
NO
NO
NO
YES

#include
#include
#include
#include
using namespace std;
const int N=1010;
int g[N][N];
bool vis[N][N];
int n,m,x1,x2,y1,y2,q;
bool flag;
int dx[4]={0,-1,0,1},dy[4]={1,0,-1,0};

void dfs(int x,int y,int dic,int turn)  //dic表示当前方向,turn表示转弯次数
{
    if(turn>2||flag) return; //转弯次数大于就终止
    if(turn==2&&(x-x2)!=0&&(y-y2)!=0) return;//剪枝:判断两次转弯后是否与目标在同一直线上
    if(x==x2&&y==y2&&turn<=2)
    {
        flag=true;
        return ;
    }
    for(int i=0;i<4;i++)
    {
        int a=x+dx[i],b=y+dy[i];
        if(a<1||a>n||b<1||b>m) continue;
        if(vis[a][b]) continue;
        if(g[a][b]==0||(a==x2&&b==y2))
        {
            vis[a][b]=true;
            if(dic==-1||dic==i)  //如果为-1即在起点,或者与当前方向一致
                dfs(a,b,i,turn);
            else     //与当前方向不一致
                dfs(a,b,i,turn+1);

            vis[a][b]=false;
        }
    }
    return ;
}

int main()
{
    while(cin>>n>>m)
    {
        if(n==0&&m==0) break;
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
                scanf("%d",&g[i][j]);
        
        cin>>q;
        for(int i=1;i<=q;i++)
        {
            scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
            memset(vis,0,sizeof(vis));
            flag=false;
            if(g[x1][y1]==g[x2][y2]&&g[x1][y1])
                dfs(x1,y1,-1,0);

            if(flag) printf("YES\n");
            else printf("NO\n");
        }
    }
    getchar();getchar();
    return 0;
}


你可能感兴趣的:(算法)