2021.8.30+8.31 集训题集

8.30

1.【搜索】Squre

题目:2362 -- Square (poj.org)

分析:dfs+剪枝

#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=25;
int a[N],n,sum;
bool vis[N];
//函数参数:拼到第cnt根,目前这根组合成的长度,遍历起点
bool dfs(int cnt,int len,int x)
{
    //前3根木棒构建完成,因为满足和为边长4倍的条件,无需遍历第4根
    if(cnt==3)
    {
        return true;
    }

    for(int i=x;i<=n;i++)
    {
        if(vis[i]) continue;
        vis[i]=1;
        if(len+a[i]<sum/4)
        {
            if(dfs(cnt,len+a[i],i+1))//往下递归满足条件
                return true;
        }
        else if(len+a[i]==sum/4)//开始找下一根
        {
            if(dfs(cnt+1,0,1))
                return true;
        }
        vis[i]=0;//回溯
    }

    return false;
}

int main()
{
    int t;
    cin>>t;

    while(t--)
    {
        memset(vis,0,sizeof vis);
        cin>>n;
        sum=0;

        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
            sum+=a[i];
        }

        sort(a+1,a+1+n,greater<int>());//剪枝:降序排列

        //因为所有的边都要被用上,边长为sum/4
        if(n<4||sum%4!=0||a[1]>sum/4)//排除不合理情况
        {
            cout<<"no"<<endl;
            continue;
        }

        if(dfs(0,0,1)) cout<<"yes"<<endl;
        else cout<<"no"<<endl;
    }
    return 0;
}

2.【搜索】 电路维修

题目:175. 电路维修 - AcWing题库

分析:双端队列bfs

//本质上是个最短路问题
//题目中给出了一些连边,由于可以旋转边,则将旋转后的边看出边权为1,原来的边看作边权为0
//题意可以转化为:从(0,0)点走到(r,c)点所需要的最短距离
//由于边权是0和1,所以可以用双端队列bfs
#include <bits/stdc++.h>
#define PII pair<int,int>
using namespace std;
const int N=505;
int r,c;
char g[N][N];//记录原题
int d[N][N];//记录到每个坐标的最短距离
//点坐标的移动
int yy[4]={-1,1,1,-1};//
int xx[4]={-1,-1,1,1};////方格坐标的移动,方格坐标都为方格左上角顶点坐标
int y2[4]={-1,0,0,-1};
int x2[4]={-1,-1,0,0};

void bfs()
{
    char op[]="\\/\\/";//从一个节点走四个方向对应的字符
    
    deque<PII> q;//双端队列
    q.push_back({0,0});
    d[0][0]=0;
    
    while(q.size())
    {
        PII t=q.front();
        q.pop_front();
        
        int x=t.first,y=t.second;
        for(int i=0;i<4;i++)
        {
            int nx=x+xx[i],ny=y+yy[i];
            if(nx<0||nx>r||ny<0||ny>c) continue;
            
            int change=0;//判断是否要改变边的方向
            int nnx=x+x2[i],nny=y+y2[i];
            if(g[nnx][nny]!=op[i]) change=1;//需要改变
            
            //更新最短路
            if(d[nx][ny]>d[x][y]+change)
            {
                d[nx][ny]=d[x][y]+change;
                //边权小的加在前面,大的加在后面,保证每次第一个都是最短边权
                if(!change) q.push_front({nx,ny});
                else q.push_back({nx,ny});
            }
        }
    }
}

int main()
{
    int T;
    cin>>T;
    
    while(T--)
    {
        memset(d,0x3f,sizeof d);
        
        cin>>r>>c;
        
        for(int i=0;i<r;i++)
        {
            for(int j=0;j<c;j++)
            {
                cin>>g[i][j];
            }
        }
        
        bfs();
        
        if(d[r][c]==0x3f3f3f3f) cout<<"NO SOLUTION"<<endl;
        else cout<<d[r][c]<<endl;
    }
    
    return 0;
}

3.【搜索】加成序列

题目:170. 加成序列 - AcWing题库

分析:迭代加深

//迭代加深中的dep是长度,因为长度要最小
#include <bits/stdc++.h>
using namespace std;
const int N=105;
int n,seq[N];
bool vis[N];

bool dfs(int now,int dep)//现在要找的位置,深度
{
    if(now==dep+1)
    {
        return seq[now-1]==n;
    }
    
    memset(vis,0,sizeof vis);//每一次加深都要重新遍历
    
    //剪枝:从大到小遍历已经添加进去的数组
    for(int i=now-1;i>=1;i--)
        for(int j=i;j>=1;j--)
        {
            int tmp=seq[i]+seq[j];
            
            if(tmp<=n&&!vis[tmp]&&tmp>seq[now-1])
            {
                vis[tmp]=1;
                seq[now]=tmp;
                
                if(dfs(now+1,dep)) return true;
            }
        }
        
    return false;
}

int main()
{
    seq[1]=1;
    
    while(cin>>n,n)
    {
        if(n==1)
        {
            cout<<1<<endl;
            continue;
        }
        int dep=2;
        while(!dfs(2,dep))
        {
            dep++;
        }
        
        for(int i=1;i<=dep;i++)
        {
            cout<<seq[i]<<" ";
        }
        cout<<endl;
    }
    
    return 0;
}

4.【搜索】送礼物

题目:171. 送礼物 - AcWing题库

分析:双向dfs

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int N=50;
int n,num;//num记录的是前半部分得到的不同的重量个数
ll w,g[N],half[1<<25],ans=0;//half用来存储前半部分得到的重量

//搜前半部分
void dfs1(int cnt,ll sum,int dep)
{//目前选的礼物,目前的重量,物品数限制
    if(cnt==dep)
    {
        half[num++]=sum;
        return;
    }
    
    if(sum+g[cnt]<=w) dfs1(cnt+1,sum+g[cnt],dep);//
    dfs1(cnt+1,sum,dep);//不选
}

//搜后半部分
void dfs2(int cnt,ll sum,int dep)
{
    if(cnt>=dep)
    {
        int l=0,r=num-1;
        while(l<r)
        {
            int mid=(l+r+1)/2;
            
            if(half[mid]+sum<=w) l=mid;
            else r=mid-1;
        }
        
        ans=max(ans,half[l]+sum);
        return;
    }
    
    if(sum+g[cnt]<=w) dfs2(cnt+1,sum+g[cnt],dep);
    dfs2(cnt+1,sum,dep);
}

int main()
{
    cin>>w>>n;
    
    for(int i=0;i<n;i++) cin>>g[i];
    sort(g,g+n,greater());
    
    int dep=n/2;
    dfs1(0,0,dep);
    //对前一半结果排序,去重(方便后一半的二分)
    sort(half,half+num);
    num=unique(half,half+num)-half;
    
    dfs2(dep,0,n);
    
    cout<<ans;
    
    return 0;
}

5.【最短路】装满的油箱

题目:176. 装满的油箱 - AcWing题库

分析:dijkstra+拆点

//将每一种油量状态拆分成单独的点,统计从起点到终点的最短路径(油钱)
#include <bits/stdc++.h>
using namespace std;
const int N=1005,M=20005,C=105;
int n,m,p[N];
int h[N],e[M],ne[M],idx,w[M];
int d[N][C];//到i节点,剩余j油量的最小油钱
bool vis[N][C];//这个状态是否经历过

struct node//结构体记录状态参量:节点,剩余油量,油钱
{
    int loc,oil,mon;
    bool operator<(const node &a) const
    {
        return mon>a.mon;
    }
};

void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

int dijkstra(int vol,int st,int ed)
{
    memset(d,0x3f,sizeof d);
    memset(vis,0,sizeof vis);

    priority_queue<node> heap;
    heap.push({st,0,0});
    d[st][0]=0;

    while(heap.size())
    {
        node new_node=heap.top();
        heap.pop();

        int x=new_node.loc,c=new_node.oil;

        if(x==ed) return d[x][c];//第一个到达终点的状态油钱最少,作为答案返回

        if(vis[x][c]) continue;
        vis[x][c]=1;

        //操作1:买油
        if(c+1<=vol)
        {
            if(d[x][c+1]>d[x][c]+p[x])
            {
                d[x][c+1]=d[x][c]+p[x];
                heap.push({x,c+1,d[x][c+1]});
            }
        }
        //操作2:走路
        for(int i=h[x];i!=-1;i=ne[i])
        {
            int y=e[i];
            if(c>=w[i])
            {
                if(d[y][c-w[i]]>d[x][c])
                {
                    d[y][c-w[i]]=d[x][c];
                    heap.push({y,c-w[i],d[y][c-w[i]]});
                }
            }
        }
    }

    return -1;
}

int main()
{
    memset(h,-1,sizeof h);

    cin>>n>>m;

    for(int i=0;i<n;i++) cin>>p[i];

    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }

    int q;
    cin>>q;

    while(q--)
    {
        int c,s,e;
        cin>>c>>s>>e;

        int ans=dijkstra(c,s,e);
        if(ans==-1) cout<<"impossible"<<endl;
        else cout<<ans<<endl;
    }
    return 0;
}

6.【搜索】生日蛋糕

题目:168. 生日蛋糕 - AcWing题库

分析:dfs+剪枝

//dfs+剪枝
#include <bits/stdc++.h>
using namespace std;
const int N=25;
const int INF=0x3f3f3f3f;
int n,m;
int minv[N],mins[N],R[N],H[N],ans=INF;

void dfs(int layer,int v,int s)//当前层数,已选体积,已选面积
{
    if(layer==0)
    {
        if(v==n) ans=min(ans,s);
        return;
    }
    
    if(v+minv[layer-1]>n) return;//可行性剪枝
    if(s+mins[layer-1]>=ans) return;//最优性剪枝
    if(s+2*(n-v)/R[layer+1]>=ans) return;//最优性剪枝
    
    for(int r=min(R[layer+1]-1,(int)sqrt(n-v));r>=layer;r--)//剪枝:优化搜索顺序,上下界
        for(int h=min(H[layer+1]-1,(n-v)/r/r);h>=layer;h--)
        {
            R[layer]=r,H[layer]=h;
            
            if(layer==m) dfs(layer-1,v+r*r*h,s+2*r*h+r*r);//第一层的顶面积要覆盖
            else dfs(layer-1,v+r*r*h,s+2*r*h);
        }
}

int main()
{
    cin>>n>>m;
    
    for(int i=1;i<=m;i++)
    {
        minv[i]=minv[i-1]+i*i*i;//每层体积下界
        mins[i]=mins[i-1]+2*i*i;//每层面积下界
    }
    
    R[m+1]=H[m+1]=INF;
    
    dfs(m,0,0);
    
    if(ans==INF) cout<<0;
    else cout<<ans;
    
    return 0;
}

8.31

1. 【搜索】噩梦

题目链接:https://www.acwing.com/problem/content/179/

思路:双端bfs,两个人走路,判断能否相遇,中间需要判断与鬼魂的曼哈顿距离

//男孩一次走3步,所以一次需要扩展3步
//vis标记0为未走过,1为男孩走过,2为女孩走过
#include <bits/stdc++.h>
#define PII pair<int,int>
using namespace std;
const int N=805;
int n,m,cnt=0;//cnt记录鬼魂的个数
PII g_st,b_st,ghost[2];//女孩、男孩、鬼魂位置
char g[N][N];
int vis[N][N];
int xx[4]={-1,1,0,0};
int yy[4]={0,0,-1,1};

bool check(int x,int y,int time)//判断这个坐标可不可以走
{
    if(x<1||x>n||y<1||y>m||g[x][y]==X) return false;
    
    for(int i=0;i<cnt;i++)//判断两个鬼魂会不会到当前位置
    {
        if(abs(ghost[i].first-x)+abs(ghost[i].second-y)<=time*2) return false;
    }
    return true;
}

int bfs()
{
    queue<PII> qg,qb;
    qg.push(g_st),qb.push(b_st);
    memset(vis,0,sizeof vis);
    
    int time=0;
    while(qg.size()||qb.size())
    {
        time++;
        //男孩
        for(int i=0;i<3;i++)//扩展三次
        {
            int lb=qb.size();
            for(int j=0;j<lb;j++)
            {
                PII t=qb.front();
                qb.pop();
                int x=t.first,y=t.second;
                if(!check(x,y,time)) continue;
                for(int k=0;k<4;k++)
                {
                    int nx=x+xx[k],ny=y+yy[k];
                    if(check(nx,ny,time))
                    {
                        if(vis[nx][ny]==2)//女孩走过
                            return time;
                        if(!vis[nx][ny])
                        {
                            vis[nx][ny]=1;
                            qb.push({nx,ny});
                        }
                    }
                }
            }
        }
        //女孩
        int lg=qg.size();
        for(int j=0;j<lg;j++)
        {
            PII t=qg.front();
            qg.pop();
            int x=t.first,y=t.second;
            if(!check(x,y,time)) continue;
            for(int k=0;k<4;k++)
            {
                int nx=x+xx[k],ny=y+yy[k];
                if(check(nx,ny,time))
                {
                    if(vis[nx][ny]==1)//男孩走过
                        return time;
                    if(!vis[nx][ny])
                    {
                        vis[nx][ny]=2;
                        qg.push({nx,ny});
                    }
                }
            }
        }
    }
    
    return -1;
}

int main()
{
    int T;
    cin>>T;
    
    while(T--)
    {
        cin>>n>>m;
        cnt=0;
        
        for(int i=1;i<=n;i++)
            for(int j=1;j<=m;j++)
            {
                cin>>g[i][j];
                if(g[i][j]==G) g_st={i,j};
                if(g[i][j]==M) b_st={i,j};
                if(g[i][j]==Z) ghost[cnt++]={i,j};
            }
            
        cout<<bfs()<<endl;
    }
    
    return 0;
}

2.【图论】排序

题目链接:https://www.acwing.com/problem/content/345/

思路:Floyd求解传递闭包问题。给出若干对二元关系,判断能否确定所有元素关系,若能,判断是否存在矛盾关系,输出可以推出全部或找出矛盾关系的最小迭代次数。

#include <bits/stdc++.h>
using namespace std;
const int N=30;
int n,m;
bool g[N][N],rel[N][N];//rel[i][j]=1表示i<j,rel[i][j]=0表示ij没有关系
bool vis[N];

int floyd()
{
    memcpy(rel,g,sizeof g);
    for(int k=1;k<=n;k++)//传递关系
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                rel[i][j]|=rel[i][k]&rel[k][j];

    for(int i=1;i<=n;i++) if(rel[i][i]) return 2;
    for(int i=1;i<=n;i++)
        for(int j=1;j<=i-1;j++)
        {
            if(rel[i][j]==0&&rel[j][i]==0) return 0;
        }
    return 1;
}

int main()
{
    while(cin>>n>>m)
    {
        if(n==0&&m==0) break;

        memset(vis,0,sizeof vis);
        memset(g,0,sizeof g);

        int t=0;//记录迭代次数
        int flag=0;//0表示无法确定,1表示找全关系,2表示存在矛盾
        for(int i=1;i<=m;i++)
        {
            char x,y;
            string s;
            cin>>s;
            x=s[0],y=s[2];
            g[(int)(x-A+1)][(int)(y-A+1)]=1;//建立小于关系的有向边
            if(!flag)//当还未找全关系,且没有出现矛盾时继续找
            {
                t=i;
                flag=floyd();
            }
        }

        if(flag==1)
        {
            printf("Sorted sequence determined after %d relations: ",t);
            //按照从小到大的顺序输出答案
            for(int i=1;i<=n;i++)//i表示循环次数
            {
                for(int j=1;j<=n;j++)
                {
                    bool flag1=1;//判断j是否满足条件
                    if(vis[j]) continue;
                    //判断有无未遍历的比j小的,即应该出现在j前,那么j不满足条件
                    for(int k=1;k<=n;k++)
                    {
                        if(vis[k]) continue;
                        if(rel[k][j])
                        {
                            flag1=0;
                            break;
                        }
                    }

                    if(flag1)
                    {
                        vis[j]=1;
                        cout<<(char)(j+A-1);
                        break;
                    }
                }
            }
            printf(".\n");
        }
        else if(flag==2) printf("Inconsistency found after %d relations.\n",t);
        else printf("Sorted sequence cannot be determined.\n");


    }
    return 0;
}

?? 补充知识点:传递闭包

概念:在交际网络中,给定若干元素和若干对二元关系,且关系具有传递性。“通过传递性推导出尽量多的元素之间的关系”的问题称为传递闭包

解法:建立邻接矩阵rel,其中 rel[i,j]=1 表示i与j有关系,rel[i,j]=0 表示i与j没有关系,rel[i,i] 始终为1。

使用Floyd算法解决传递闭包问题:

bool rel[310][310];
int n,m;
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) rel[i][i]=1;
    for(int i=1;i<=m;i++) 
    {
        int x,y;
        scanf("%d%d",&x,&y);
        rel[x][y]=rel[y][x]=1;
    }
    for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                rel[i][j]|=rel[i][k]&rel[k][j];//k用于传递i与j的关系
}

3. 【图论】通信线路

题目链接:https://www.acwing.com/problem/content/342/

思路:1. 双端队列+bfs 2.分层图最短路

//解法一:双端队列+bfs
#include <bits/stdc++.h>
#define PII pair<int,int>
using namespace std;
const int N=1005,M=20005;
int n,m,k;
int idx,h[N],e[M],ne[M],w[M];
int d[N];
bool vis[N];

void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

bool bfs(int mid)
{
    memset(d,0x3f,sizeof d);
    memset(vis,0,sizeof vis);
    
    deque<PII> dq;
    dq.push_back({0,1});
    d[1]=0;
    
    while(dq.size())
    {
        PII t=dq.front();
        dq.pop_front();
        
        int x=t.second;
        if(vis[x]) continue;
        vis[x]=1;
        
        for(int i=h[x];i!=-1;i=ne[i])
        {
            int y=e[i];
            int flag=w[i]>mid;//边权>预设答案mid的是要免费提供升级服务的,计数为1
            if(d[y]>d[x]+flag)
            {
                d[y]=d[x]+flag;
                if(flag) dq.push_back({d[y],y});
                else dq.push_front({d[y],y});
            }
        }
    }
    
    return d[n]<=k;//判断需要免费服务的有没有超过限制k
}

int main()
{
    memset(h,-1,sizeof h);
    
    cin>>n>>m>>k;
    
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
    }
    
    //二分查找答案
    int l=0,r=1000001;
    while(l<r)
    {
        int mid=(l+r)/2;
        if(bfs(mid)) r=mid;
        else l=mid+1;
    }
    
    if(r==1000001) cout<<-1;
    else cout<<l;
    
    return 0;
}
//解法2:分层图最短路
#include <bits/stdc++.h>
#define PII pair<int,int>
using namespace std;
const int N=1e6+5,M=1e6+5;//分层后数组要开大一些
int n,m,k;
int idx,h[N],e[M],ne[M],w[M];
int d[N];
bool vis[N];

void add(int a,int b,int c)
{
    e[idx]=b,w[idx]=c,ne[idx]=h[a],h[a]=idx++;
}

void dijkstra()//跑一个正常的dijkstra
{
    memset(d,0x3f,sizeof d);
    memset(vis,0,sizeof vis);
    
    priority_queue<PII,vector<PII>,greater<PII>> heap;
    heap.push({0,1});
    d[1]=0;
    
    while(heap.size())
    {
        PII t=heap.top();
        heap.pop();
        
        int x=t.second;
        if(vis[x]) continue;
        vis[x]=1;
        
        for(int i=h[x];i!=-1;i=ne[i])
        {
            int y=e[i];
            if(d[y]>max(d[x],w[i]))
            {
                d[y]=max(d[x],w[i]);
                heap.push({d[y],y});
            }
        }
    }
}

int main()
{
    memset(h,-1,sizeof h);
    
    cin>>n>>m>>k;
    
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(a,b,c),add(b,a,c);
        
        for(int i=1;i<=k;i++)//给每一层连边
        {
            //i-1和i层之间连边
            add(a+(i-1)*n,b+i*n,0);
            add(b+(i-1)*n,a+i*n,0);
            //i层内连边
            add(a+i*n,b+i*n,c);
            add(b+i*n,a+i*n,c);
        }
    }
    
    dijkstra();
    
    if(d[n+k*n]==0x3f3f3f3f) cout<<-1;
    else cout<<d[n+k*n];
    
    return 0;
}

4.【图论】最优贸易

题目链接:https://www.acwing.com/problem/content/343/

思路:建两个图,进行正向和反向两次SPFA,题目要求找到一条1到n的路径,使路径中先后存在两点p和q,q的权值-p的权值最大。正向以1为起点,记录1到节点x的路径中能经过的权值最小点p(由于不满足贪心,不能用dijkstra),反向以n为起点,记录x到n中权值最大点q,最后遍历所有节点,更新答案。

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5,M=1e6+5;
int n,m,price[N];
int h[N],rh[N],e[M],ne[M],idx;
int dmin[N],dmax[N];//到i节点的最小,最大权值
bool vis[N];

void add(int *h,int a,int b)//建两个图只需要换个表头
{
    e[idx]=b,ne[idx]=h[a],h[a]=idx++;
}

//为什么不能用dijkstra呢?
//因为dijkstra基于贪心算法,当某节点弹出堆时,它的最小值已经确定,不会再次被更新
//对于本题,是要求起点到x节点中经过的最小值,此时是最小值并不能保证弹出队列后不会更新为更小值

void spfa(int *d,int st,int *h,bool flag)//记录权值的数组,开始节点,表头,求最小还是最大
{
    if(flag) memset(d,0x3f,sizeof dmin);
    memset(vis,0,sizeof vis);
    
    queue<int> q;
    q.push(st);
    d[st]=price[st];
    vis[st]=1;
    
    while(q.size())
    {
        int x=q.front();
        q.pop();
        vis[x]=false;
        
        for(int i=h[x];i!=-1;i=ne[i])
        {
            int y=e[i];
            if(flag&&d[y]>min(d[x],price[y])||!flag&&d[y]<max(d[x],price[y]))
            {
                if(flag) d[y]=min(d[x],price[y]);
                else d[y]=max(d[x],price[y]);
                
                if(!vis[y])
                {
                    vis[y]=1;
                    q.push(y);
                }
            }
        }
    }
}

int main()
{
    memset(h,-1,sizeof h);
    memset(rh,-1,sizeof rh);
    
    cin>>n>>m;
    
    for(int i=1;i<=n;i++) cin>>price[i];
    
    while(m--)
    {
        int a,b,c;
        cin>>a>>b>>c;
        add(h,a,b),add(rh,b,a);//连正向边和反向边
        if(c==2) add(h,b,a),add(rh,a,b);
    }
    
    spfa(dmin,1,h,true);//正向求最小边权
    spfa(dmax,n,rh,false);//反向求最大边权
    
    int ans=0;
    for(int i=1;i<=n;i++)
    {
        ans=max(ans,dmax[i]-dmin[i]);
    }
    cout<<ans;
    
    return 0;
}

Bye~

2021.8.30+8.31 集训题集

上一篇:better-scroll在Pad中点击事件失效问题


下一篇:系统集成项目管理工程师高频考点(第四章)