2020 百度之星 初赛一 前6题

补题地址

update:
2020.7.21 :t3添加转移函数 t4完善思路,修改代码小bug
2020.7.23 :添加t5题解链接
2020.7.24 :添加t6题解链接
为节省篇幅,前三题的代码我放到了github上

T1 Drink

Problem Description
我们有 nn 种不同的饮料,每种饮料有无限多瓶,第 ii 种饮料一瓶提供 x[i]x[i] 毫升的水分,包含 y[i]y[i] 卡路里。

现在我们需要选择一种饮料一直喝,直到补充了至少 mm 毫升的水分,我们想使得摄入的卡路里总和最小。请求出这个最小值。

一旦打开一瓶饮料,就一定要喝完。
Input

第一行一个整数 test(1 ≤ \le test ≤ \le 100)表示数据组数。
对于每组数据,第一行两个整数 n,m(1≤n≤100,1≤m≤10000)。
接下来 nn 行,每行两个整数 x[i], y[i](1 ≤ \le x[i], y[i] ≤ \le 100)x[i],y[i])。

注意只能选择一种饮料喝!(所以不是完全背包)
饮料种类不多,遍历就可以了
代码:https://github.com/dq116/-/blob/master/Drink.cpp

T2 GPA

Problem Description
小沃沃一共参加了 4 门考试,每门考试满分 100 分,最低 0 分,分数是整数。
给定四门考试的总分,请问在最优情况下,四门课绩点的和最高是多少?

分数与绩点之间的对应关系如下:

95~100 4.3

90~94 4.0

85~89 3.7

80~84 3.3

75~79 3.0

70~74 2.7

67~69 2.3

65~66 2.0

62~64 1.7
60~61 1.0

0~59 0
Input
第一行一个正整数 test(1≤test≤401) 表示数据组数。 接下来 test 行,每行一个正整数 x 表示四门考试的总分
(0≤x≤400)。

典型的多重背包问题
给个区间就是糊弄人嘞,绩点到手要那么高分就没用了,直接取区间下限就行。
当然数据量较小,直接枚举就行。
代码:https://github.com/dq116/-/blob/master/GPA.cpp
(我直接套的这篇博客的多重背包的板子)

T3 Dec

Problem Description
初始有 a,b 两个正整数,每次可以从中选一个大于 1 的数减 1,最后两个都会减到 1,我们想知道在过程中两个数互质的次数最多是多少。

Input
第一行一个正整数 test(1≤test≤1000000) 表示数据组数。

接下来 test 行,每行两个正整数 a,b(1≤a,b≤1000)

dp O(n^2)预处理,O(1)查询
用 f[i][j]表示第一个数字从 i开始减,第二个数字从 jj开始减的情况下最多有多少对互质的数字。
f[i][j]=max(f[i-1][j],f[i][j-1])+t
其中当i与j互质时t=1否则t=0;
(输入输出不要用cin,cout!会TLE)
(别试着找数学规律了,我试过了,没啥规律)
代码:https://github.com/dq116/-/blob/master/Dec.cpp

T4 Civilization

Problem Description
这是一个回合制游戏,每一回合开始前会进行上一回合的结算。

有一张 n∗n 的棋盘,我们出生在一个初始位置 (x,y),现在我们要选择一个位置建设城市。

你的人物每回合可以移动到距离你曼哈顿距离不超过 2 的位置,移动完成后可以选择是否建立城市。

建立城市后,你的人物消失,成为一个人口为 1 的城市,这个人口要下回合才可以工作。如果不移动,直接在 (x,y) 建城,第 1回合就可以开始工作。

对于城市的每个居民,你可以安排他到距离城市曼哈顿距离小于等于 3
的位置进行工作,此居民可以瞬间到达该位置,每个位置最多安排一个居民,失业的人口不会生产任何食物。

注意,城市位置上必须有一个居民在工作。

结算按照如下顺序:

  1. 如果位置 (i,j) 上有一个工作居民,则获得 a[i][j] 的食物。
  2. 如果当前城市人口为 i,且食物达到 8∗ i 2 i^2 i2 时,你获得一个新的居民,下一回合可以进行操作。

当结算后城市总人口达到 9 游戏结束。

初始食物数量为 0,人口上涨不会导致之前积累的食物消失。输出最少几个回合能让游戏结束。

Input
第一行一个正整数 test(1≤test≤10) 表示数据组数。

对于每组数据,第一行三个正整数 n(2≤n≤500),x(1≤x≤n),y(1≤y≤n),分别表示棋盘大小和起始位置。

接下来 n 行,每行 n 个整数,第 i 行第 j 列的整数表示 a[i][j] (1≤a[i][j]≤3)。

注意在开始建城之前我们可以移动多次!
地图太小,没地方给人工作就有人失业了。
建好城后我们优先让人去食物最多的地方工作(我用的优先队列)
在哪建城我们可以遍历一下可能的地点(把那由近及远的一层层的可能的地点想像成一颗树,初始位置为树根,每下一层代表建城前多移动一次可能到的地点,根到节点的距离代表移动次数)
(遍历图的话理论上用dfs的也可以,得多加个判断,处理不当就得不到最短路了。bfs的遍历过程,就相当于一层层地遍历树,自然路径也是最短的)

代码(注释挺详细的)

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
const int n_max=500,population_max=9,a3_num=24,a2_num=12;
int test;
int n,x,y;

int g[n_max+1][n_max+1];
int a3[a3_num],b3[a3_num],a2[a2_num],b2[a2_num];//移动曼哈顿距离3和2的移动方案

void init()//计算移动方案
{
    
    
    int step=3;
    int counter=0;
    for(int i=-step;i<=step;i++)
    {
    
    
        for(int j=-step;j<=step;j++)
        {
    
    
            if(abs(i)+abs(j)<=step && (i!=0 || j!=0)) //排除不移动:i==0 && j==0的情况
            {
    
    a3[counter]=i;
            b3[counter]=j;
            //cout<<i<<" "<<j<<endl;
            counter++;
            }
        }
    }
    //cout<<counter<<endl;
    step=2;
    counter=0;
    for(int i=-step;i<=step;i++)
    {
    
    
        for(int j=-step;j<=step;j++)
        {
    
    
            if(abs(i)+abs(j)<=step  && (i!=0 || j!=0))
            {
    
    
                a2[counter]=i;
                b2[counter]=j;
                //cout<<i<<" "<<j<<endl;
                counter++;
            }
        }
    }
    //cout<<counter<<endl;
}

int get_round(int x,int y)//城市在x,y处完成游戏所对应的回合数
{
    
    
   
    int p=1;//现在的人数
    int produce[10]={
    
    0,g[x][y]};//每个人对应的每回合可以获取的食物数
    int round=0;//回合数
    
    priority_queue<int> q;//让食物多的地方率先被分配
    for(int i=0;i<a3_num;i++)
    {
    
    
        int n_x=x+a3[i];
        int n_y=y+b3[i];
        if(n_x<=n && n_x>0 &&n_y <=n && n_y>0 )
        {
    
    
            q.push(g[n_x][n_y]);
        }
    }
    int total=0;//积累的总食物
    while(p<population_max)
    {
    
    
        
        for(int i=1;i<10;i++)
        {
    
    
            total+=produce[i];
        }
        if(total>=p*p*8)
        {
    
    
            p++;
            if(!q.empty())//空则表明有人失业了,这个人不能产出食物了
            {
    
    
            produce[p]=q.top();
            q.pop();
            }
        }
        round++;

    }
    return round;

}
int mini_round; //最小回合数
int visited[n_max+1][n_max+1];
int bfs()
{
    
    
    mini_round=get_round(x,y);
    memset(visited,0,sizeof(visited));
    int n_x,n_y;
    visited[x][y]=1;
    queue<pair<int,int>> q;
    int level=0;
    q.push(make_pair(x,y));
    int rounds[mini_round]={
    
    1};//记录每层的合法的点数
    while (!q.empty())
    {
    
    
        pair<int,int> temp=q.front();
        mini_round=min(get_round(temp.first,temp.second)+level,mini_round);
        q.pop();
        for(int i=0;i<a2_num;i++)
        {
    
    
	        n_x=temp.first+a2[i];
	        n_y=temp.second+b2[i];
	        if(n_x<=n && n_x>0 &&n_y <=n && n_y>0 && !visited[n_x][n_y])
	        {
    
    
	            //cout<<n_x<<" "<<n_y<<" "<<level<<endl;
	            q.push(make_pair(n_x,n_y)); 
	            visited[n_x][n_y]=1;
	            rounds[level+1]+=1;//压的是下一层的点所以level+1
	        }   
        }
        rounds[level]-=1;
        if( rounds[level]==0) //该层遍历完了该遍历下一层了
        {
    
      
            level++;
            if(level>=mini_round) /*每下一层就在建城前多移动了level回合
                                    如果移动的回合大于目前最小的总回合数,那再往放下一层找,也不可能产生更优解了*/
                 break;
                
        }
    }
    return mini_round; 
     
}

int main()
{
    
    
    init();
    cin>>test;
    for(int k=0; k<test; k++)
    {
    
    
    scanf("%d%d%d",&n,&x,&y);
    for(int i=1;i<=n;i++)
    {
    
    
        for(int j=1;j<=n;j++)
        {
    
    
            scanf("%d",&g[i][j]);
        }
    }
    cout<<bfs()<<endl;
    }
}

第一版发布时把memset(visited,0,sizeof(visited));
改成了int visited[n_max][n_max]={0}导致后面地址越界了;
由于visited数组少开了一行,之前在静态区还没事,放到栈区后,访问到边界时,就越界了。

T5 (图文详解)
T6 (解释的不是很详细但思路挺不一样的)
T7-T8没看

猜你喜欢

转载自blog.csdn.net/weixin_42378324/article/details/107462517