实验班讲义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希望你能用计算机帮他解决这个问题。
似乎是很熟悉的题目呢?试试看这个问题能用贪心解决吗?这个问题留作课后思考。