NEUQ实验班讲义001【搜索回顾+贪心初步】

实验班讲义001


在实验班讲课时用的讲义稿子,以后可能会持续更新。

本周题目回顾

题意分析: 一个L层的N*M的地牢,给定起点S和终点E。若有路径,则输出最短的逃脱时间,否则输出Trapped!
解题思路: 用bfs层层遍历,直到找到终点(找不到终点则无法脱出)。
核心代码:

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<queue>
using namespace std;
typedef long long ll;
const int MAXN=30+5;
int mx[6]= {0,0,0,0,1,-1}; //每一列为一种操作;
int my[6]= {0,0,1,-1,0,0};
int mz[6]= {1,-1,0,0,0,0};
char dun[MAXN][MAXN][MAXN];//储存地牢;
int l,r,c;
struct node{
    int x;
    int y;
    int z;//x,y,z记录当前位置
    int step;//记录步数
};
bool judge(node tmp){//判断新得到的状态是否合法
    if(tmp.x>=0&&tmp.x<l&&tmp.y>=0&&tmp.y<r&&tmp.z>=0&&tmp.z<c&&dun[tmp.x][tmp.y][tmp.z]=='.')
        return true;
    return false;
}
node start,endd;
int bfs(node s){//函数返回走到终点的步数,若不能走到,则返回-1
    dun[s.x][s.y][s.z]='#';//标记出发点 表示该点不能再使用
    queue<node>Q;
    Q.push(s);//初始状态入队
    node tmp;
    while(!Q.empty()){//当队列为空,表明所有点都访问过
        node now;
        now=Q.front();//从队列首读取一个点
        Q.pop();//将搜索过的点弹出
        for(int i=0; i<6; i++){
            tmp.x=now.x+mx[i];
            tmp.y=now.y+my[i];
            tmp.z=now.z+mz[i];//尝试向6个方向都走一步
            tmp.step=now.step+1;//走到次点的步数等于相邻的母节点步数+1
            if(tmp.x==endd.x&&tmp.y==endd.y&&tmp.z==endd.z)
                return tmp.step;
            //判断是否走到终点
            if(judge(tmp)){//判断该点是否合法
                dun[tmp.x][tmp.y][tmp.z]='#';//若合法
                Q.push(tmp);
            }
        }
    }
    return -1;
}
int main()
{
    while(~scanf("%d%d%d",&l,&r,&c)&&l&&r&&c){
        for(int i=0; i<l; i++)
            for(int j=0; j<r; j++){
                scanf("%s",dun[i][j]);
                for(int k=0; k<c; k++){
                    if(dun[i][j][k]=='S'){
                        start.x=i;start.y=j;start.z=k;
                        start.step=0;//记录出发点
                    }
                    if(dun[i][j][k]=='E'){
                        endd.x=i;endd.y=j;endd.z=k;//记录终点
                    }
                }
            }
        int ans=bfs(start);
        if(ans!=-1)
            printf("Escaped in %d minute(s).\n",ans);
        else
            printf("Trapped!\n");
    }
}
  • Party Lamps
    题意分析: 给出N盏灯,初始状态全部为开(用1表示)。规定四种操作:

    • 1:改变每盏灯的状态(0->1,1->0)
    • 2:改变标号为奇数的灯的状态(0->1,1->0)
    • 3:改变标号为偶数的灯的状态(0->1,1->0)
    • 4:改变标号为3*K+1的灯的状态(0->1,1->0)

经过C种变换后,用一串01串来代表灯的状态。提前给出一些标号的灯得最终状态,要求按二进制升序输出合法的01串。
解题思路: 由于每种操作进行两次以后,相当于不操作,所以首先令C=4-C%2。然后对初始串进行DFS,记录所有的合法串,最后排序输出。
核心代码:

#include<iostream>
#include<string>
#include<cstdio>
#include<vector>
#include<algorithm>
using namespace std;
const int MAXN=105;
vector<string> res;
int on[MAXN],off[MAXN];
int a[MAXN];
int N,C;
bool judge(){//判断是否可行解
    int i=0;
    while(on[i]!=-1){
        if(!a[on[i]-1])
            return false;
        i++;
    }
    i=0;
    while(off[i]!=-1){
        if(a[off[i]-1])
            return false;
        i++;
    }
    return true;
}
void todo(int x){//按第几个按钮
    if(x==1)
        for(int i=0;i<N;i++)
            a[i]^=1;
    if(x==2)
        for(int i=0;i<N;i+=2)
            a[i]^=1;
    if(x==3)
        for(int i=1;i<N;i+=2)
            a[i]^=1;
    if(x==4)
        for(int i=0;i<N;i+=3)
            a[i]^=1;
}
void dfs(int k){
    if(k>C){//边界条件 
        if(judge()){
            char tmp[110];
            for(int i=0;i<N;i++){//将int型转化为char型  
                tmp[i]=a[i]+'0';
            }
            tmp[N]='\0';
            res.push_back(tmp);//将char数组中的值赋给vector 
        }
        return ;
    }
    else{
        for(int i=1;i<=4;i++){//递归求解 
            todo(i);//按 
            dfs(k+1);
            todo(i);//还原 
        }
    }
}

int main()
{
    cin>>N>>C;
    if(C>4)
    C=4-C%2;//去重 
    for(int j=0;j<N;j++){//初始化 
        a[j]=1;
    }
    int tmp=0,i=0;
    while(cin>>tmp&&tmp!=-1){//记录最后状态 
        on[i]=tmp; 
        i++;
    }
    on[i]=tmp;tmp=0;
    i=0;
    while(cin>>tmp&&tmp!=-1){//同上 
        off[i]=tmp;
        i++;
    }
    off[i]=tmp;
    dfs(1);
    sort(res.begin(),res.end());//排序及输出结果 
    for(i=0;i<res.size();i++){
        if(i==0){
            cout<<res[0]<<endl;
            continue;
            }
        if(res[i]!=res[i-1]) {
            cout<<res[i]<<endl;
        }
    }
    return 0;
}

今日学习

贪心算法(greedy algorithm)

  • 什么是贪心算法?

其实很简单,也很好理解。首先我们引入一个生活中的事件。
假设你有1元,5元,10元,50元的硬币各无限枚,现在要支付108元且保证拿出的硬币数最少,你会怎么做呢?
很显然你会先取两个50元,再取一个5元,三个1元。2*50+1*5+3*1=108.
在这个例子里,第一次取50元就是你的局部最优策略,而贪心算法,就是每次都选取当前的局部最优解。
这段文字用代码来实现如下:

const int v[4]={1,5,10,50};
int ans=0,A;
void solve(){
    ans=0;
    for(int i=3;i>=0;i--){
    int t=A/v[i];
    ans+=t;
    A-=t*v[i];
}

  我们现在已经知道,贪心法就是不断地选取当前的局部最优解。下面我们来考虑一个很经典的贪心问题。

  • 区间问题

    现有n项工作,每项工作的工作时长为一个[S,t]的区间,对于每项工作你可以选择参与与否,但是如果参与就必须完成。此外,参与工作的时间段不能重叠。你的目的是尽可能的参与更多的工作。
    与上道题不同,这道题有几种不同的贪心策略:

    • 在可选工作中,每次都选取开始时间最早的。
    • 在可选工作中,每次都选取结束时间最早的。
    • 在可选工作中,每次都选取用时最短的。
    • 在可选工作中,每次都选取与其他工作重叠最少的的。

仔细思考可知,第二种策略是正确的,其他三种均能取出反例。
第二种策略的简单代码实现如下:

const int MAXN=1e4;
//输入
int N,S[MAXN],T[MAXN];
//用于排序的pair数组
pair <int,int> itv[MAXN];

void solve(){
//为了先比较t,将t存入second
    for(int i=0;i<N;i++){
    itv[i].first=T[i];
    itv[i].second=S[i];
    }
    sort(itv,itv+N);
    int ans=0,t=0;
    for(int i=0;i<N;i++){
    if(t<itv[i].second){
    ans++;
    t=itv[i].first;
    }
}

  经过这个例子我们知道,贪心问题并没有看起来的那么简单粗暴,很多比赛中的贪心问题很难找到合适的策略。所以当你找到一种策略,首先要试着去举反例(比赛中可以让队友帮忙出数据debug),直到找到正确的策略。
又一个硬币问题
  Lethe生活在一个奇怪的国家,这个国家的币制很奇怪,只有3元,6元,7元三种硬币。这个国家没有支付宝和微信,所以日常花销都要用现金结算,Lethe烦透了这种讨厌的币制,所以他想要在每次付钱时拿出尽量少少的硬币,但是当要付的钱很大时,很难得到合适的结果,所以Lethe希望你能用计算机帮他解决这个问题。
似乎是很熟悉的题目呢?试试看这个问题能用贪心解决吗?这个问题留作课后思考。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值