杭电入门班5-8专题整理

  1. 题目:1001 数塔
  2. 思路:DP初步,由下向上得到每个结点的最大权值,时间复杂度是O(n^2)。
  3. 代码如下:
#include<bits/stdc++.h>
using namespace std;
int dp[110][110];
int main()
{
    int n;
    cin>>n;
    while(n--)
    {
        int t;
        cin>>t;
        for(int i=1;i<=t;i++)
        {
            for(int j=1;j<=i;j++)
            {
                cin>>dp[i][j];
            }
        }
        for(int i=t-1;i>=1;i--)
        {
            for(int j=0;j<=i;j++)
            {
                dp[i][j]=dp[i][j]+max(dp[i+1][j],dp[i+1][j+1]);
            }
        }
        printf("%d\n",dp[1][1]);
    }
    return 0;
}
  1. 题目:1002 最少拦截系统
  2. 思路:DP初步,要求最少需要配置多少套拦截系统,按顺序依次贪心也可以做(虽然还是不能明确证明其正确性,证明:先对样例模拟一下,有感觉确实是贪心最优就是全局最优。反证法:如果并不采取每次都是最优的拦截,那么一定就会需要更多的拦截系统来拦截本应贪心的部分)。
  3. DP思路:若后面的数有大于前面的数的,那么后面的那个数肯定需要另一个拦截系统,这样的话,我们要求的就是最少的非上升子序列数,从而转化为求最长上升子序列的长度。怎么求呢???动态规划的思想,将每个数所在位置的其所在“上升子序列中的长度都设为DP[i]=1”,接下去在双重循环中更新第i个数(第i个数不影响前面的i-1个数的dp)。
  4. 代码如下:
#include<bits/stdc++.h>
using namespace std;
int num[100010];
int dp[10010];
int main()
{
    int n;
    while(cin>>n)
    {
        memset(dp,0,sizeof(dp));
        for(int i=0;i<n;i++)
        {
            cin>>num[i];
            dp[i]=1;
        }
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<i;j++)
            {
                if(num[i]>num[j])
                {
                    dp[i]=max(dp[i],dp[j]+1);
                }
            }
        }
        sort(dp,dp+n,greater<int>());
        cout<<dp[0]<<endl;
    }
}
  1. 题目:1003 搬寝室
  2. 题意:DP初步,给定包含n个数表示n件行李的重量,要搬k次行李,每次搬2件,并使得这两件行李的重量差的平方尽可能小,给出搬k次之后所消耗的最少体力。
  3. DP思路:DP初步,看来确实快忘记DP算法了,看了杭电的视频来回忆。是这样的:DP问题要把大问题分解为小问题,n个物品 搬k次---->n个物品搬1次---->2…3…4…5…6…个物品搬1次,由此过程,就可以模拟出DP公式,尽管一开始并不知道整个大问题怎么实现,但知道每个小问题怎么实现后,并且每个小问题都覆盖了所有情况并且无重复,这样整个大问题的求解思路也就清晰了。
  4. 思路详解:设数组DP[1005][2005],1005表示搬的趟数,2005表示搬的第几个物品。n个物品搬1次:dp[1][i]=min(dp[1][i-1],dp[0][i-2]+(w[i]-w[i-1])^2),n个物品搬2次: dp[2][i]=min(dp[2][i-1],dp[1][i-2]+(w[i]-w[i-1])^2)…搬k次: dp[1][i]=min(dp[k][i-1],dp[k-1][i-2]+(w[i]-w[i-1])^2)
    同时,要注意初始化dp时,初始化为一个大数,因为我们要求的是所耗体力的最小值,同时还要注意初始化dp[0][0-n]整个数组为0,因为第0次搬运所耗体力为0,这样,在循环中用DP就AC了。
  5. 代码如下:
#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n,k;
    int dp[1005][2005];
    int w[2010];
    while(scanf("%d%d",&n,&k)!=EOF)
    {
        memset(dp,1000000,sizeof(dp));
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&w[i]);
        }
        for(int j=0;j<=n;j++)
        {
            dp[0][j]=0;
        }
        sort(w+1,w+1+n);
        for(int i=1;i<=k;i++)
        {
            for(int j=2*i;j<=n;j++)
            {
                dp[i][j]=min(dp[i][j-1],dp[i-1][j-2]+(w[j]-w[j-1])*(w[j]-w[j-1]));
            }
        }
        printf("%d\n",dp[k][n]);
    }
    return 0;
}
  1. 题目:1005 丑数
  2. 题意:DP初步+打表(其实一开始比较难看出来是DP而且这种打表的方式也很奇怪,思维难度高,难想出来,但明白思路之后就轻松了)
  3. DP思路:并非以往的用一个式子就可以做出来,这里DP要用到多个变量控制。核心是:用DP思路打表时,第i个符合题意的数要利用前面已经算出的所有数(因为有且只有它们只包含2,3,5,7四个因子)去乘(2,3,5,7)得到最小的数作为第i个数。
  4. 思路详解:设p1,p2,p3,p4初值都为1,a[1]=1;接下去 a[++k]=min(min(2a[p1],3a[p2]),min(5a[p3],7a[p4]));同时用得到的a[k]这个数与之前的这四个数作比较,相等则更新对应的p,使其+1,对应到下一个最小的数。(这里再多解释一下(2a[p1],3a[p2],5a[p3],7a[p4])代表的意思,这些数就代表着可能的所有使第i个数最小的所有情况,做到了剪枝的操作。)
  5. 代码如下:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[6000];
int main()
{
    a[1]=1;
    ll k=1;
    ll p1=1,p2=1,p3=1,p4=1;
    while(k<6000)
    {
        a[++k]=min(min(2*a[p1],3*a[p2]),min(5*a[p3],7*a[p4]));
        if(a[k]==2*a[p1])p1++;
        if(a[k]==3*a[p2])p2++;
        if(a[k]==5*a[p3])p3++;
        if(a[k]==7*a[p4])p4++;
    }
    int n;
    while(cin>>n)
    {
        if(n==0)break;
        printf("%lld\n",a[n]);
    }
    return 0;
}

  1. 题目:1006 Bone Collector
  2. 思路:典型的背包问题,容量为V的背包要放入骨头,每块骨头都有权值,要求使得背包能够装的石头的总权值尽可能大。双重循环,第一遍遍历每一块石头(和顺序无关,不用排序),第二遍对整个体积由大到小进行遍历(为什么由大到小进行遍历呢??反过来想一下,如果是有小到大遍历,那么对于公式:dp[j]=max(dp[j],dp[j-c[i]]+w[i]);随着j的不断增大,如果c[i]==5,j由0–>100,那么j = =10时,之前j= =5所进行的更改已经用过此物品了,所以接下去会重复利用此物品–》这就拓展到完全背包了)(反而从V---->c[i]遍历,就可以使得只利用一次该物品,这才是标准的0/1背包)
  3. 代码如下:
#include<bits/stdc++.h>
using namespace std;
int dp[1001],w[1000],c[1000];
int main()
{
    int num,n,V,i,j;
    scanf("%d",&num);
    while(num--)
    {
        scanf("%d%d",&n,&V);
        for(i=1;i<=n;i++)scanf("%d",&w[i]);
        for(i=1;i<=n;i++)scanf("%d",&c[i]);
        memset(dp,0,sizeof(dp));
        for(i=1;i<=n;i++)
            for(j=V;j>=c[i];j--)
            dp[j]=max(dp[j],dp[j-c[i]]+w[i]);
        printf("%d\n",dp[V]);
    }
}
  1. 题目:1008 珍惜现在,感恩生活
  2. 思路:典型的背包问题,既不是0/1背包也不是完全背包,每个物品都赋予一定的数量,其可以转化为0/1背包,就是再用一个循环来处理数组num[]。
  3. 代码如下:
#include<bits/stdc++.h>
using namespace std;
int V,m;
int c[210],v[210],num[210];//cost,value;
int dp[210];
int main()
{
    int t;
    cin>>t;
    while(t--)
    {
        cin>>V>>m;
        for(int i=1;i<=m;i++)
        {
            scanf("%d%d%d",&c[i],&v[i],&num[i]);
        }
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=m;i++)
        {
            for(int j=V;j>=0;j--)
            {
                for(int k=1;k<=num[i];k++)
                {
                    if(k*c[i]<=j)
                    {
                        dp[j]=max(dp[j],dp[j-k*c[i]]+v[i]*k);
                    }
                }
            }
        }
        printf("%d\n",dp[V]);
    }
    return 0;
}

  1. 题目:1009 寒冰王座
  2. 思路:典型的背包问题,转化为完全背包,有两个物品,设其value与重量相等,这样的话最后装到体积为V的背包中的DP[V]与V只差就是答案。
  3. 代码如下:
#include<bits/stdc++.h>
using namespace std;
int dp[10010];
int main()
{
    int t;
    cin>>t;
    int v[2]={150,200};
    int c[2]={150,200};
    while(t--)
    {
        int vv;
        cin>>vv;
        memset(dp,0,sizeof(dp));
        for(int i=0;i<=1;i++)
        {
            for(int j=c[i];j<=vv;j++)
            {
                dp[j]=max((dp[j-c[i]]+v[i]),dp[j]);
            }
        }
        printf("%d\n",vv-dp[vv]);
    }
}

  1. 题目:1010 减肥记
  2. 思路:典型的背包问题,完全背包,在0/1背包问题的第二层循环中改一下j的顺序即可。
  3. 代码如下:
#include<bits/stdc++.h>
using namespace std;
int dp[100010];
int c[110],v[110];
int main()
{
    int n,W;
    while(cin>>n)
    {
        memset(dp,0,sizeof(dp));
        for(int i=1;i<=n;i++)
        {
            scanf("%d%d",&v[i],&c[i]);
        }
        scanf("%d",&W);
        for(int i=1;i<=n;i++)
        {
            for(int j=c[i];j<=W;j++)
            {
                dp[j]=max(dp[j],dp[j-c[i]]+v[i]);
            }
        }
        printf("%d\n",dp[W]);
    }
    return 0;
}

  1. 题目:1011 畅通工程续
  2. 思路:最短路径初步,基础的Dijkstra模板,练习PAT的时候写了好多好多基础的Dijkstra了。
  3. 代码如下:
#include<bits/stdc++.h>
using namespace std;
int G[210][210];
bool vis[210];
int dist[210];
const int inf=0x3fffffff;
int st,en;
int n,m;
void Dijkstra(int start)
{
    fill(dist,dist+210,inf);
    fill(vis,vis+210,false);
    dist[start]=0;
    for(int i=0;i<n;i++)
    {
        int u=-1,MIN=inf;
        for(int j=0;j<n;j++)
        {
            if(vis[j]==false&&dist[j]<MIN)
            {
                u=j;
                MIN=dist[j];
            }
        }
        if(u==-1)return;
        vis[u]=true;
        for(int v=0;v<n;v++)
        {
            if(vis[v]==false)
            {
                if(dist[u]+G[u][v]<dist[v])
                {
                    dist[v]=dist[u]+G[u][v];
                }
            }
        }
    }
}
int main()
{
    int a,b,dis;
    while(scanf("%d%d",&n,&m)!=EOF)
    {
        fill(G[0],G[0]+210*210,inf);
        for(int i=0;i<m;i++)
        {
            scanf("%d%d%d",&a,&b,&dis);
            G[a][b]=G[b][a]=min(dis,G[a][b]);
        }
        scanf("%d%d",&st,&en);
        Dijkstra(st);
        if(dist[en]!=inf)
            printf("%d\n",dist[en]);
        else
            printf("-1\n");
    }
    return 0;
}
  1. 题目:1014 A strange lift
  2. 思路:BFS初步。给定初始状态和状态转移条件,通过在BFS中队列的应用,用VIS数组进行剪枝,便可以遍历全部的情况,或到达终点,break输出,或队列为空依旧不能到达终点,则输出-1.
  3. 代码如下:
#include<bits/stdc++.h>
using namespace std;
bool vis[210];
struct node
{
    int k;
    int steps;
}Floor[410];
int n,st,en;
int BFS()
{
    queue<int>qu;
    int cur,next;
    Floor[st].steps=1;
    vis[st]=true;
    qu.push(st);
    int flag=1;
    while(!qu.empty())
    {
        cur=qu.front();
        qu.pop();
        if(cur==en)
        {
            printf("%d\n",Floor[cur].steps-1);
            flag=0;
            break;
        }
        if(Floor[cur].k+cur<=n)
        {
            if(vis[Floor[cur].k+cur]==false)
            {
                Floor[Floor[cur].k+cur].steps=Floor[cur].steps+1;
                vis[Floor[cur].k+cur]=true;
                qu.push(Floor[cur].k+cur);
            }
        }
        if(cur-Floor[cur].k>=1)
        {
            if(vis[cur-Floor[cur].k]==false)
            {
                Floor[cur-Floor[cur].k].steps=Floor[cur].steps+1;
                vis[cur-Floor[cur].k]=true;
                qu.push(cur-Floor[cur].k);
            }
        }
    }
    if(flag==1)
        printf("-1\n");
}
int main()
{
    while(scanf("%d",&n)!=EOF)
    {
        if(n==0)break;
        fill(vis,vis+210,false);
        scanf("%d%d",&st,&en);
        for(int i=1;i<=n;i++)
        {
            cin>>Floor[i].k;
        }
        BFS();
    }
}
  1. 题目:1015 非常可乐
  2. 思路:BFS初步。三个杯子中可乐的体积是状态,通过不断地倒可乐进行状态转移,用的思维简单但代码量长的六种情况写的状态转移,第一次出现的状态标记为true,若到达两杯体积相同一杯为空则输出倒可乐的次数,若队列为空依旧没有到达则输出NO。
  3. 代码如下:(没有用函数,确实该练一下用函数要怎么写,省赛之后再来补用函数的代码)
#include<bits/stdc++.h>
using namespace std;
bool vis[110][110];
struct node
{
    int a,b,c;
    int steps;
}Floor;
int A,B,C;
void BFS()
{
    queue<node>qu;
    node cur,next;
    cur.a=A;cur.b=0;cur.c=0;
    cur.steps=0;
    vis[cur.a][cur.b]=true;
    qu.push(cur);
    int flag=0;
    while(!qu.empty())
    {
        cur=qu.front();
        qu.pop();
        if(((cur.a==cur.b)&&cur.c==0)||((cur.c==cur.b)&&cur.a==0)||((cur.a==cur.c)&&cur.b==0))
        {
            flag=1;
            printf("%d\n",cur.steps);
            break;
        }
next=cur;
        if(next.a>0&&B-next.b>0)//a--b
        {
            if(next.a>=(B-next.b))
            {
                next.a=next.a-(B-next.b);
                next.b=B;
            }
            else if(next.a<(B-next.b))
            {
                next.b=next.b+next.a;
                next.a=0;
            }
            vis[next.a][next.b]=true;
            next.steps=cur.steps+1;
            qu.push(next);
        }
next=cur;
        if(next.b>0&&C-next.c>0)//b--c
        {
            if(next.b>=(C-next.c))
            {
                next.b=next.b-(C-next.c);
                next.c=C;
            }
            else if(next.b<(C-next.c))
            {

                next.c=next.c+next.b;next.b=0;
            }
            if(vis[next.a][next.b]==false)
            {
                next.steps=cur.steps+1;
                vis[next.a][next.b]=true;
                qu.push(next);
            }
        }
next=cur;
        if(next.a>0&&C-next.c>0)//a--c
        {
            if(next.a>=(C-next.c))
            {
                next.a=next.a-(C-next.c);
                next.c=C;
            }
            else if(next.a<(C-next.c))
            {

                next.c=next.c+next.a;next.a=0;
            }
            if(vis[next.a][next.b]==false)
            {
                next.steps=cur.steps+1;
                vis[next.a][next.b]=true;
                qu.push(next);
            }
        }
next=cur;
        if(next.b>0&&A-next.a>0)//b----a
        {
            if(next.b>=(A-next.a))
            {
                next.b=next.b-(A-next.a);
                next.a=A;
            }
            else if(next.b<(A-next.a))
            {
                next.a=next.a+next.b;next.b=0;
            }
            if(vis[next.a][next.b]==false)
            {
                next.steps=cur.steps+1;
                vis[next.a][next.b]=true;
                qu.push(next);
            }
        }
next=cur;
          if(next.c>0&&A-next.a>0)//c----a
        {
            if(next.c>=(A-next.a))
            {
                next.c=next.c-(A-next.a);
                next.a=A;
            }
            else if(next.c<(A-next.a))
            {
                next.a=next.a+next.c;next.c=0;
            }
            if(vis[next.a][next.b]==false)
            {
                next.steps=cur.steps+1;
                vis[next.a][next.b]=true;
                qu.push(next);
            }
        }
next=cur;
            if(next.c>0&&B-next.b>0)//c--b
        {
            if(next.c>=(B-next.b))
            {
                next.c=next.c-(B-next.b);
                next.b=B;
            }
            else if(next.c<(B-next.b))
            {
                next.b=next.b+next.c;next.c=0;
            }
            if(vis[next.a][next.b]==false)
            {
                next.steps=cur.steps+1;
                vis[next.a][next.b]=true;
                qu.push(next);
            }
        }
    }
    if(flag==0)
    {
        printf("NO\n");
    }
}
int main()
{
    while(scanf("%d%d%d",&A,&B,&C)!=EOF)
    {
        if(A==0&&B==0&&C==0)break;
        fill(vis[0],vis[0]+110*110,false);
        BFS();
    }
}

  1. 题目:1016 Knight Moves
  2. 思路:BFS初步。很经典的其实在棋盘中走步问题,对所有的状态转移进行循环处理,用VIS数组剪枝,注意判断边界情况。
  3. 代码如下:(虽然很简单,但好久不写一次AC好快乐哈哈哈哈哈哈哈)
#include<bits/stdc++.h>
using namespace std;
int vis[10][10];
int st_x,st_y,en_x,en_y;
string st,en;
int zou[8][2]={{1,2},{2,1},{1,-2},{2,-1},{-1,-2},{-2,-1},{-1,2},{-2,1}};
struct node
{
    int x,y;
    int steps;
};
void BFS()
{
    node cur,next;
    fill(vis[0],vis[0]+10*10,false);
    queue<node>qu;
    cur.x=st_x;
    cur.y=st_y;
    cur.steps=0;
    qu.push(cur);
    while(!qu.empty())
    {
        cur=qu.front();
        qu.pop();
        if(cur.x==en_x&&cur.y==en_y)
        {
            printf("To get from ");
            cout<<st;
            printf(" to ");
            cout<<en;
            printf(" takes %d knight moves.\n",cur.steps);
            break;
        }
        for(int i=0;i<8;i++)
        {
            next.x=cur.x+zou[i][0];
            next.y=cur.y+zou[i][1];
            if(next.x>=1&&next.x<=8&&next.y>=1&&next.y<=8&&vis[next.x][next.y]==false)
            {
                next.steps=cur.steps+1;
                vis[next.x][next.y]=true;
                qu.push(next);
            }
        }
    }
}
int main()
{
    while(cin>>st>>en)
    {
         st_x=st[0]-'a'+1;
         st_y=st[1]-'0';
         en_x=en[0]-'a'+1;
         en_y=en[1]-'0';
        BFS();
    }
}

  1. 题目:1017 Rescue
  2. 思路:BFS初步。这里用到了优先队列,需要在结构体里面重载运算符<。也是很经典的带不同权值的BFS问题。
  3. 知识点:优先队列:priority_queue,因为优先队列在每次有元素入队是都会进行一次排序,默认将权值大的数放在前面,因此在结构体中重写运算符<时,写成如下所示。
friend bool operator < (node a,node b)
    {
        return a.steps>b.steps;
        //重写<运算符,优先队列默认由大到小排序
        //这样就改成了由小到大排序
    }
  1. 代码如下:(注意是多组输入,PAT一般不会这样,但省赛的时候也要注意一下)
#include<bits/stdc++.h>
using namespace std;
int zou[4][2]={{0,1},{0,-1},{-1,0},{1,0}};
int vis[210][210];
char m[210][210];
int N,M;
int st_x,st_y,en_x,en_y;
struct node
{
    int x,y;
    int steps;
   friend bool operator < (node a,node b)
    {
        return a.steps>b.steps;//重写<运算符,优先队列默认由大到小排序,这样就改成了由小到大排序
    }
};
void BFS()
{
    fill(vis[0],vis[0]+210*210,false);
    node cur,next;
    cur.x=st_x;
    cur.y=st_y;
    cur.steps=0;
    vis[cur.x][cur.y]=true;
   priority_queue<node>qu;
    qu.push(cur);
    while(!qu.empty())
    {
       cur=qu.top();
        qu.pop();
        if(m[cur.x][cur.y]=='r')
        {
            printf("%d\n",cur.steps);
           return;
        }
        for(int i=0;i<4;i++)
        {
            next.x=cur.x+zou[i][0];
            next.y=cur.y+zou[i][1];
            if(next.x>=1&&next.x<=N&&next.y>=1&&next.y<=M&&vis[next.x][next.y]==false&&m[next.x][next.y]!='#')
            {
                if(m[next.x][next.y]=='.'||m[next.x][next.y]=='r')
                next.steps=cur.steps+1;
                if(m[next.x][next.y]=='x')
                next.steps=cur.steps+2;
                vis[next.x][next.y]=true;
                qu.push(next);
            }
        }
    }
    printf("Poor ANGEL has to stay in the prison all his life.\n");
}
int main()
{
    while(cin>>N>>M)
    {
        getchar();
        for(int i=1;i<=N;i++)
        {
            for(int j=1;j<=M;j++)
            {
                scanf("%c",&m[i][j]);
                if(m[i][j]=='a')
                {
                    st_x=i;st_y=j;
                }
            }
            getchar();
        }
        BFS();
    }
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值