分类
电子学会等级考试

五级复习重点

掌握贪心、分治、深度优先搜索、广度优先搜索、简单的线性动态规划算法。

红与黑 连通性问题:

#include<iostream> 
#include<cstring>
using namespace std; 
int n,m,v[30][30],d[8][2]={{0,1},{1,0},{0,-1},{-1,0}},sx,sy,cnt=0;
char a[30][30];
void dfs(int x,int y){
    for(int i=0;i<=3;i++){
        int nx=x+d[i][0],ny=y+d[i][1];
        if(nx>=1&&nx<=n&&ny>=1&&ny<=m&&a[nx][ny]!='#'&&v[nx][ny]==0){
            v[nx][ny]=1;
            cnt++; //计数
            dfs(nx,ny);
        }
    }
}
int main() { 
    while(cin>>m>>n){ //n行m列 
        if(m==0&&n==0) break;
        memset(a,0,sizeof(a)); //初始化
        memset(v,0,sizeof(v));
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                cin>>a[i][j];
                if(a[i][j]=='@'){
                    sx=i;sy=j;
                }
            } 
        }
        cnt=1; //起点也要算1个
        v[sx][sy]=1;
        dfs(sx,sy);
        cout<<cnt<<endl;
    }
    return 0;
} 

BFS同样可以解决连通性问题。

#include<iostream> 
#include<queue>
#include<cstring>
using namespace std;
int n,m,v[30][30],sx,sy,d[4][2]={{0,1},{0,-1},{-1,0},{1,0}};
char a[30][30]; 
struct point{
    int x,y;
}; 
int bfs(){
    memset(v,0,sizeof(v));  //初始化v数组 
    int ans=0;
    queue<point> q;  //队列 
    point p;p.x=sx;p.y=sy;v[sx][sy]=1;
    q.push(p);  //头结点入队 
    while(!q.empty()){
        point p=q.front();ans++;q.pop();//出队,ans记录出队次数 

        for(int i=0;i<=3;i++){  //扩展上下左右四个方向 
            point pp;
            pp.x=p.x+d[i][0];pp.y=p.y+d[i][1];
            if(pp.x>=1&&pp.x<=n&&pp.y>=1&&pp.y<=m&&a[pp.x][pp.y]=='.'&&v[pp.x][pp.y]==0){
                v[pp.x][pp.y]=1;  //标记 
                q.push(pp);  //入队 
            } 
        }
    }
    return ans;  //走过的结点的数量 
}
int main(){
    while(cin>>m>>n){  //注意行列是反的 
        if(n==0&&m==0) break;
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                cin>>a[i][j];
                if(a[i][j]=='@'){
                    sx=i;sy=j;
                }
            }
        }
        cout<<bfs()<<endl;
    } 
 
    return 0;
}

密室逃脱 多轮广搜:

第一轮,起点(1,1),终点的为值比1大一点的数字;

第二轮,起点是第一轮终点的坐标,终点数字递增;以此类推,直到走到矩阵中最大的数字。每一轮通过BFS计算该轮的最小步数,累加。

#include<iostream> 
#include<queue>
#include<cstring>
using namespace std;
int t,n,m,v[110][110],d[4][2]={{0,1},{0,-1},{-1,0},{1,0}},mx;
int a[110][110]; 
struct point{
    int x,y,step,v;
}b[10010]; 
int bfs(int sx,int sy,int ex,int ey){
    memset(v,0,sizeof(v));  //初始化v数组 
    queue<point> q;  //队列 
    point p;p.x=sx;p.y=sy;p.step=0;v[sx][sy]=1; //起点入队 
    q.push(p);  //头结点入队 
    while(!q.empty()){
        point p=q.front();q.pop();//出队,ans记录出队次数 
        if(p.x==ex&&p.y==ey){
            return p.step;
        }
        for(int i=0;i<=3;i++){  //扩展上下左右四个方向 
            point pp;
            pp.x=p.x+d[i][0];pp.y=p.y+d[i][1];pp.step=p.step+1;
            if(pp.x>=1&&pp.x<=n&&pp.y>=1&&pp.y<=m&&a[pp.x][pp.y]>0&&v[pp.x][pp.y]==0){
                v[pp.x][pp.y]=1;  //标记 
                q.push(pp);  //入队 
            } 
        }
    }
    return -1;  //走不到ex,ey则返回-1 
}
int main(){
    cin>>t;
    while(t--){ 
        cin>>n>>m;
        mx=-1;
        memset(b,0,sizeof(b));  //初始化b数组 
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                cin>>a[i][j];
                b[a[i][j]].x=i;b[a[i][j]].y=j;b[a[i][j]].v=1;
                mx=max(mx,a[i][j]);
            }
        }
        int sx=1,sy=1,ans=0;  //起点为1,1 ,初始走了0步 
        for(int i=2;i<=mx;i++){
            if(b[i].v){//密码数字存在,则作为广搜终点走一次 
                int ex=b[i].x,ey=b[i].y;
                int r=bfs(sx,sy,ex,ey);//广搜多次,从小搜到最大的数字
                if(r==-1){ //只要有一个数字走不到,那就走不通
                    ans=-1;
                    break;
                }else{
                    ans+=r;
                }
                sx=ex;sy=ey; //终点变起点,继续解密下一个数字 
            }
        }
        cout<<ans<<endl;
    } 
    return 0;
}

最小新整数

贪心策略:先消除第一个从前往后单调递增的最高点(最大数字)。

#include<iostream>
#include<string>
using namespace std;
int main(){
    string s;
    int n,t;
    cin>>t;
    while(t--){  
        cin>>s>>n;
        int len=s.size();
        while(n--){
            for(int i=0;s[i]!='\0';i++){ //字符串结束的位置停止 
                if(s[i]>s[i+1]){ //贪心,先消除从前往后单调递增的最高点 
                    s.erase(i,1);
                    break;
                }
            }
        }
        cout<<s<<endl;
    }
     
    return 0;
} 

计算逆序数

归并排序:在合并的过程中,计算逆序对个数并累加起来

#include<iostream>
#include<string>
using namespace std;
int a[100010],n,b[100010],ans=0; 
void merge(int a[],int s,int m,int e,int b[]){ //合并数据
    int l=s,r=m+1,i=s;
    while(l<=m&&r<=e){
        if(a[l]>a[r]){ //在该条件里面,计算逆序对
            ans+=e-r+1; //a[l]与右侧r~e的所有数字组成逆序对
            b[i]=a[l];  //a[l]并入b数组 
            l++;i++;
        }else{
            b[i]=a[r]; //a[r]并入b数组 
            r++;i++;
        } 
    }
    while(l<=m){  //继续并入 
        b[i]=a[l];l++;i++;
    }
    while(r<=e){  //继续并入 
        b[i]=a[r];r++;i++;
    }
     
    for(int i=s;i<=e;i++){
        a[i]=b[i];  //回填到a数组 
    } 
    return; 
}
void msort(int a[],int s,int e,int b[]){
    if(s>=e) return ;
    int m=(s+e)/2;  //分治,前半段s~m,后半段m+1~e 
    msort(a,s,m,b);
    msort(a,m+1,e,b);
    merge(a,s,m,e,b); //合并 
    return ; 
}
int main(){
    while(cin>>n){  
        if(n==0) break;
        for(int i=1;i<=n;i++){
            cin>>a[i];
        }
        ans=0;
        msort(a,1,n,b);  //利用归并排序计算逆序对
        cout<<ans<<endl; 
    }
     
    return 0;
} 
#include<iostream> 
using namespace std;
int n,a[100010],b[100010],ans;
void msort(int s,int e){ //s起点 e终点 
    if(s>=e) return; 
    int mid=(s+e)/2; //一分为二
    msort(s,mid);  //左半部分 排序 
    msort(mid+1,e); //右半部分  排序 
    //合并:左半部分 和 右半部分合并
    int l=s,r=mid+1,i=s;
    while(l<=mid&&r<=e){ //l->mid或者r->e,有一个数组先被合并完了 
        if(a[l]>a[r]){
            ans+=e-r+1; //能够成逆序对的元素 
            b[i]=a[l];
            i++;l++;
        }else{
            b[i]=a[r];
            i++;r++;
        }
    }
    while(l<=mid){ //左半部分还没有合并完 
        b[i]=a[l];
        i++;l++;
    }
    while(r<=e){  //右半部分还没有合并完 
        b[i]=a[r];
        i++;r++;
    }
     
    for(int i=s;i<=e;i++){
        a[i]=b[i]; //将合并后的结果回写到数组a 
    } 
} 
int main(){
    while(cin>>n){
        if(n==0) break;
        for(int i=1;i<=n;i++){
            cin>>a[i];
        }
        ans=0;
        msort(1,n); //将a数组的第1个~第n个元素排序 
        cout<<ans<<endl;
    }
    return 0;
}

圣诞老人的礼物

贪心策略:性价比从高到底排列,然后从前往后选价值最大。

#include<iostream>
#include<iomanip>
#include<algorithm>
using namespace std;
struct node{
    double vi,wi,di; //di为单价
}a[110];
bool cmp(node n1,node n2){
    return n1.di>n2.di; //单价从大到小排列 
}
int n;
double w,tv=0;
int main(){
    cin>>n>>w;
    for(int i=1;i<=n;i++){
        cin>>a[i].vi>>a[i].wi;
        a[i].di=a[i].vi/a[i].wi; //计算单价
    }
    sort(a+1,a+n+1,cmp);
    for(int i=1;i<=n;i++){
        if(a[i].wi>w){
            tv+=w*a[i].di;  //w全部取,可能会拆开取 
            break;
        }else{
            tv+=a[i].vi; //vi全部取走 
            w-=a[i].wi;  //减去相应的容量 
        } 
    } 
    cout<<setprecision(1)<<fixed<<tv;
    return 0;
} 

忍者道具

DFS:每个物品既可以放在一个已有的背包里,也可以开一个新的背包存放。

#include<iostream>
using namespace std;
int a[20],bag[20],ans=20;
int n,w;
void dfs(int x,int sum){  //正在放第x个物品,已经用了sum个背包
    if(sum>=ans) return;  //背包数量大于前面算过的ans,没必要继续计算
    if(x==n+1){ //物品放完,看看背包数量是不是最小的 
        ans=min(ans,sum);
        return;
    }
    for(int i=1; i<=sum;i++){  //尝试放在已有sum个背包里,每个都试试 
        if(bag[i]>=a[x]){  //背包容量够,就可以试着放 
            bag[i]-=a[x];  //放 
            dfs(x+1,sum);     //尝试第x+1个物品 
            bag[i]+=a[x];  //回溯 
        }
    }
    bag[sum+1]-=a[x]; //尝试新开一个背包
    dfs(x+1, sum+1);  //尝试第x+1个物品,已经用了sum+1个背包 
    bag[sum+1]+=a[x];  //回溯 
}
int main(){
    cin>>n>>w;
    for(int i = 1; i <= n; i++){
        cin>>a[i];
        bag[i] = w;   //准备n个背包,每个背包容量是w 
    } 
    dfs(1, 1);   //尝试第一个物品,已经用了1个背包 
    cout<<ans<<endl;  //最少使用背包数量 
    return 0;
}