2019模拟赛09场解题报告

目录

  • 2019模拟赛09场解题报告

    • 目录la~~
    • 题一:瞬间移动
    • 题二:食物订购
    • 题三:马蹄印
    • 题四:景观美化

2019模拟赛09场解题报告

标签(空格分隔): 解题报告 Forever_chen 2019.8.20


目录la~~


题一:瞬间移动

【题面】
有一天,暮光闪闪突然对如何将一个整数序列a1,a2,...,an排序为一个不下降序列起了兴趣。身为一只年轻独角兽的她,只能进行一种叫做“单元转换”(unit shift)的操作。换句话说,她可以将序列的最后一个元素移动到它的起始位置:
$$a1,a2,...,an→an,a1,a2,...,an?1$$
请帮助暮光闪闪计算一下:她对这个序列进行排序所需的最小操作次数是多少?
【输入格式】
第一行一个整数n(2≤n≤10^5)。
第二行n个整数表示a1,a2,...,an(1≤ai≤10^5)。
【输出格式】
如果序列无法被排序,输出-1.
否则输出暮光闪闪对它排序所需要的最少操作次数。
| 样例输入1 | 样例输出1 |
|------| ------|
| 2
2 1 | 1|
| 样例输入2 | 样例输出2 |
| 1
1 3 2| -1 |
| 样例输入3 | 样例输出3 |
| 2
1 2| 0 |

  • 【算法分析】
  • 模拟要满足题目所给的不下降序列,我们可以举个例子,如要变为$1,2,3,4,5$这个序列所给的数列只有是:$1,2,3,4,5;5,1,2,3,4,;4,5,1,2,3;3,4,5,1,2;2,3,4,5,1$
    通过观察我们可以看出满足的序列有一个规律,就是一定会有两个或一个有序的不下降序列,而转化为不下降序列的操作个数就是$n-x+1$(总数$-$基准数的位置$+1$,但$1,2,3,4,5$这样已经是不下降子序列的序列不满足操作规律,需要特殊判断)
  • 根据此规律,我们可以得出一种判断方法,我们可以以一个数列中最小的那个数作为基准数,如上述$5$个序列,我们可以把每个序列的$1$作为基准数,会出现$3$种情况
  • $1.$基准数在序列的第一位,如$1,2,3,4,5$,只有整个序列为不下降序列才成立,否则输出$0$(特殊判断,不能用$n-x+1$)
  • $2.$基准数在序列的最后一位,如$2,3,4,5,1$,只有基准数前的序列为一个不下降序列才能得出,否则输出$0$
  • $3.$基准数在序列的中间,如,$5,1,2,3,4;4,5,1,2,3;3,4,5,1,2$,我们可以将基准数前的序列基准数后的序列分别与排好序的不下降序列比较,如上述$3$个序列中$1$后面为$2$,$2$后面是$3$,$1$前面是$5$,$5$前面是$4$,我们可以观察并得出规律(a为输入序列,b为排好序的序列,i为基准数的位置(运算时不变化),j为基准数前或基准数后的数的位置(j在往前递增或往后递减)):
    基准数前的数满足($j--$):$a[j]=b[n-i+j+1] $
    基准数后的数满足($j++$):$a[j]=b[n-i+j+1]$
  • 注意:可能出现一样的数,如$2,3,4,1,1$(基准数多次出现),所以,我们要在以上方法中加一个操作:在多个基准数中,只要有一个基准数可以满足我们给出的全部操作,就可以变回不下降序列(就输出操作次数)
    A[给出序列ai] -->|排序后找到基准数| B(基准数b1)
    B --> C{基准数的三种情况}
    C -->|基准数在序列的第一位| D[满足整个序列为不下降序列]
    C -->|基准数在序列的中间| E[基准数前后都是不下降序列且a和b序列中的数字满足关系]
    C -->|基准数在序列的最后一位| F[满足基准数前序列为不下降序列]
    D --> G{可以进行操作}
    E --> G{可以进行操作}
    F --> G{可以进行操作}
  • 【AC代码】
#include<bits/stdc++.h>
using namespace std;
int n,a[100010],b[100010],ans,x;
bool f=0,fg=0,fg2=0;
bool cmp(int x,int y) {
    return x<y;
}
bool work(int i) {
    bool f=0;
    x=i;//x记录i的值(基准数的位置),在最后运算时用
    if(i==1) {//基准数在序列的第一位
        for(int j=i+1; j<=n; j++) {
            if(a[j]!=b[j]) {
                f=1;
                break;
            }
        }
    } else {
        if(i==n) {//基准数在序列的最后一位
            for(int j=i-1; j>=1; j--) {
                if(a[j]!=b[j+1]) {
                    f=1;
                    break;
                }
            }
        } else {//基准数在序列的中间
            for(int j=i-1; j>=1; j--) {//对基准数前的数字进行判断
                if(a[j]!=b[n-i+j+1]) {
                    f=1;
                    break;
                }
            }
            for(int j=i+1; j<=n; j++) {//对基准数后的数字进行判断
                if(a[j]!=b[j-i+1]) {
                    f=1;
                    break;
                }
            }
        }
    }
    return f;
}
int main() {
    scanf("%d",&n);
    for(int i=1; i<=n; i++) {
        scanf("%d",&a[i]);
        b[i]=a[i];
    }
    sort(b+1,b+1+n,cmp);
    for(int i=1; i<=n; i++) {//特判,当输入的序列就为不下降序列时
        if(a[i]!=b[i]) {
            fg=1;
        }
    }
    if(fg==0) {
        printf("0");
        return 0;
    } else {
        for(int i=1; i<=n; i++) {
            if(a[i]==b[1]) {
                if(work(i)==0) {//只要一个基准数满足
                    printf("%d",n-x+1);
                    return 0;
                }
            }
        }
        printf("-1");

    }
    //system("pause");
    return 0;
}

题二:食物订购

【题面】
新年快到了,乐乐开了一家著名的餐厅,名叫“巧克力餐厅。
这个餐厅可提供n种菜,其中第i种菜的单价为ci,共制作了ai份。从餐厅订单上显示,今天会有m个客户将一个接一个光临餐厅,第j个客户将购买第ti种食物dj份,并且第j+1个客户只有在第j个客户购买以后才会到来。如果餐厅无法满足这个人对第ti种菜的需求的话,那他就会买目前还有的价格最低的菜来满足他的需求,最低的菜买好后还不够他会继续买目前最低的菜,直到满足他的需求。(提示:如果所有菜都卖完了,但还是不能满足他的需求,那么就直接输出0,因为餐厅不能满足这个人的需要求,客户会愤怒地离开,无论之前提供多少菜肴,这个客户都不会买单,吃霸王餐)。如果餐厅能满足这个人的需求,就输出这个人的总消费数。
总之每个人必须先买他想要的那种菜,数量不够时再买价格最低的,直到买到他所需的数量为止,当然将所有菜都买完也不能满足他的需求,则输出0。你的任务是计算每个人的总消费数。
【输入格式】
第一行输入整数n和m(1≤n,m≤10^5),分别代表不同类型的菜总数和客户总数。
第二行输入n个正整数a1,a2,...,an(1≤ai≤10^7),ai表示第i种菜的初始数量。
第三行输入n个正整数c1,c2,...,cn(1≤ci≤10^6),ci表示第i种菜的价格。
下面m行,每行输入一个客户的订单。第j行输入两个正整数tj和dj(1≤tj≤n,1≤dj≤10^7),分别表示第j个客户订购的菜的种类和数量。
【输出格式】
输出共m行。第j行输出第j个客户的总消费数。(不能满足当前这个客户的需求则输出0)。
| 样例输入1 | 样例输出1 |
|------| ------|
| 8 5
8 6 2 1 4 5 7 5
6 3 3 2 6 2 3 2
2 8
1 4
4 7
3 4
6 10 | 22
24
14
10
39|
| 样例输入2 | 样例输出2|
| 6 6
6 6 6 6 6 6
6 66 666 6666 66666 666666
1 6
2 6
3 6
4 6
5 6
6 66|36
396
3996
39996
399996
0|
| 样例输入3 | 样例输出3|
|6 6
6 6 6 6 6 6
6 66 666 6666 66666 666666
1 6
2 13
3 6
4 11
5 6
6 6| 36
11058
99996
4333326
0
0 |
【样例解释】
样例一:将按以下方式提供5个客户:
1.将提供给第一个顾户6份第2种菜,此时还不能满足他的需要求,所以就买最便宜的第4种菜1份和第6种菜1份。价格是63+12+12=22。 8种菜的剩余数量为{8,0,2,0,4,4,7,5}。
2.将提供给第二个客户4份第一种菜。成本是4
6= 24。剩余数量为{4,0,2,0,4,4,7,5}。
3.第三个客户需要7份第4种菜,由于第4种菜卖完了,所以只能提4份第6种菜和3份第8种菜。成本是42+32= 14。剩余数量为{4,0,2,0,4,0,7,2}。
4.第四个客户需要4份第3种菜,所以餐厅将提供2份第3种菜和2份第8种菜。成本是23+22= 10。剩余数量为{4,0,0,0,4,0,7,0}。
5.第五个客户需要10份第6种菜,第6种菜卖完了,所以餐厅将提供客户7份第7种菜,3份第1种菜。成本是73+36= 39。剩余数量为{1,0,0,0,4,0,0,0}。
样例二:每个客户都按他们的订单服务,除了最后一个客户,他愤怒地离开并且没有付款。例如,第二个客户需要6份第2种菜,因此成本为666= 396。
样例三:某些客户可能无法获得他们订单服务。例如,第二个客户需要13份第2种菜,所以只能买目前最便宜的6份第3种菜,1份第4种菜,因此成本为66
6+6666+66661=11058

  • 【算法分析】

    • 模拟我们可以将输入的菜按照他们的价格进行双关键字排序,记录他们原始的序号$(id)$,将排好的序列用类似链表的方式存下来
    • 注意:如果餐厅没发满足顾客需求,顾客就会把剩下的菜吃掉,但不付钱(真不知道都没付钱,为什么要愤怒)
  • 【AC代码】
#include<bits/stdc++.h>
using namespace std;
long long n,m,cnt;//cnt 总共还有多少菜
struct forever {
    long long c,num,nxt,id;//c价格,num数量,nxt下一个比a[i].c大的菜的id,id 序号
} a[100010];
bool cmp1(forever x,forever y) {
    return x.c<y.c;
}
bool cmp2(forever x,forever y) {
    return x.id<y.id;
}
int main() {
    scanf("%lld%lld",&n,&m);
    for(int i=1; i<=n; i++) {
        scanf("%lld",&a[i].num);
        a[i].id=i;
        a[i].nxt=1e9;//用nxt记录最大的菜
        cnt+=a[i].num;
    }
    for(int i=1; i<=n; i++) {
        scanf("%lld",&a[i].c);
    }
    sort(a+1,a+1+n,cmp1);//按照菜的价格排序
    for(int i=1; i<n; i++) {//将下一个比a[i].c大的菜的id记下
        a[i].nxt=a[i+1].id;
    }
    int p=a[1].id;//p是目前存在的最便宜的菜的id
    sort(a+1,a+1+n,cmp2);//排回菜原始的id顺序
    while(m--) {
        long long iid,inum,ans=0;
        scanf("%lld%lld",&iid,&inum);
        if(cnt<inum) {//如果菜不够,就输出0
            cnt=0;
            printf("0\n");
            continue;
        }
        cnt-=inum;
        if(a[iid].num>=inum) {//如果顾客的菜够,就将菜的数量乘顾客所需的数量
            a[iid].num-=inum;
            ans=a[iid].c*inum;
        } else {//如果顾客的菜不够
            ans+=a[iid].num*a[iid].c;//买完需要的菜
            inum-=a[iid].num;
            a[iid].num=0;
            while(a[p].num<inum) {//找下一个价格最小的菜
                inum-=a[p].num;
                ans+=a[p].c*a[p].num;
                a[p].num=0;
                p=a[p].nxt;//找下一个价格最小的菜
            }
            ans+=inum*a[p].c;
            a[p].num-=inum;

        }
        printf("%lld\n",ans);
        while(p!=1e9 &&a[p].num==0)p=a[p].nxt;//如果当前菜刚好用完,找出现有的最小的菜
    }
    //system("pause");
    return 0;
}

题三:马蹄印

【题面】
虽然当奶牛贝里斯找到平衡序列后很高兴了,但是他现在对序列提出了一个更高的要求,就是要求每个序列中必须是先一定数量的左括号然后是与左括号相同数量的右括号。例如:(((()))),就是一个完美的平衡序列。
当贝里斯某天在农场上走的时候,他在地上发现了马蹄印,这个农场是一个NN的方格,每个小方格中都有一个马蹄印。贝里斯希望从方格的最左上角的地方开始出发,然后每次可以向上或者向下或者向左或者向右移动一步,使得他走过的每个小方格中的马蹄印能够组成一个完美的平衡序列。当然了,贝里斯不能重复经过任何小方格。
请帮助贝里斯在这个N
N的方格中找出长度最长的完美序列的长度。
【输入格式】
第一行一个正整数N,表示农场的大小。
接下来N行,每行N个字符,表示N*N的方格上马蹄印的分布情况。
【输出格式】
只有一行一个整数,表示最长的完美序列的长度,如果不存在这样的完美序列(例如起始位置就是右括号),则输出0。
| 样例输入1 | 样例输出1 |
|------| ------|
| 4
(())
()((
(()(
))))| 8|

  • 【算法分析】

    • dfs因为我们要在一张图中找‘完美的平衡序列’,首先就想到搜索:$dfs$,我们可以从坐标$(1,1)$,开始搜索,如果搜索到左括号就将左括号的数量$l+1$,当搜索到右括号就将右括号的数量$r+1$,当搜索到左括号和右括号数量相同时对答案进行$max$操作,最后返回最大值
    • 注意:当$(1,1)$,为右括号时就可以直接输出$0$了(‘平衡的完美数列’由左括号开始)
  • 【数据范围约定】
  • $2<=N<=5$;说明:样例中,奶牛的行走序列是这样的:
    $$1())$$
    $$2)(( $$
    $$345( $$
    $$876)$$
  • 【AC代码】
#include<bits/stdc++.h>
using namespace std;
int n,ans;
int go1[4]={1,0,-1,0},go2[4]={0,1,0,-1};
char ch[10][10];
bool f[10][10];
void dfs(int x,int y,int l,int r) {
    if(l==r) {//当搜索到的左括号和右括号数量相同时
        //cout<<ans<<endl;
        ans=max(l*2,ans);//将搜索到的右括号和左括号的数量加起来(l+r或l*2或r*2就是‘平衡的完美序列’的长度)
        return;
    }
    for(int i=0; i<=3; i++) {
        if(x+go1[i]>0 && x+go1[i]<=n && y+go2[i]>0 && y+go2[i]<=n && f[x+go1[i]][y+go2[i]]==0) {//在满足不会超出图的边界的情况下且路没有被走过
            f[x+go1[i]][y+go2[i]]=1;
            if((ch[x+go1[i]][y+go2[i]]=='(')&&!r) {//搜索到左括号
                dfs(x+go1[i],y+go2[i],l+1,r);//左括号数量++
            }
            if(ch[x+go1[i]][y+go2[i]]==')') {//搜索到右括号
                dfs(x+go1[i],y+go2[i],l,r+1);//右括号的数量++
            }
            f[x+go1[i]][y+go2[i]]=0;
        }
    }
}
int main() {
    scanf("%d",&n);
    f[1][1]=1;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin>>ch[i][j];
        }
    }
    if(ch[1][1]==')'){//如果(1,1)为右括号
        printf("0");//直接输出0
        return 0;
    }
    dfs(1,1,1,0);
    printf("%d",ans);
    //system("pause");
    return 0;
}

题四:景观美化

【题面】
农夫约翰最近决定来美化他的花园,他需要运输很多的泥土。花园是由N块花圃组成的。第i块花圃初始的时候有Ai数量的泥土。为了达到美化的目的,必须使得第i块花圃的泥土数量Ai变成Bi。
约翰有三个选择:第一,他可以买一个单位的泥土放进任意花圃中,代价是X;第二,他可以将一个单位的泥土从某一个花圃中除去,代价是Y;第三,他可以将第i块花圃中的一个单位的泥土搬运到第j块花圃中,代价是Z*|i-j|。
请帮助约翰计算为了达到目的最小需要花费的代价。
【输入格式】
第一行四个整数,分别是N,X,Y,Z。
接下来N行,每行两个整数,分别表示Ai和Bi。
【输出格式】
只有一行一个整数,表示最小的代价。

样例输入1 样例输出1
4 100 200 1
1 4
2 3
3 2
4 0
210

【提示】
数据范围:1<=N<=100,0<=Ai,Bi<=10,0<=X,Y,Z<=1000。 说明:从第4个花圃中所有的土必须被除去,其中1个单位的土被直接除去,代价是200,剩下3个单位的土从第4个花圃到第1个花圃。

  • 【算法分析】

    • dp这题我们将每个花圃的泥土都分开来,如我们将$1,2,3,4$的泥土数量分成$1,2,2,3,3,3,4,4,4,4$。,一个1表示第一块花圃有1个单位的泥土,两个2表示第二块花圃有2个单位的泥土,三个3表示第三个花圃中有3个单位的泥土,四个4表示第四个花圃中有4个单位的泥土。要进行的操作就是移动字符再转成需要的字符
    • $1.$插入一个字符:$f[i,j]=f[i,j-1]+x$($f[i,j-1]$表示原串$1到i$部分与目标串$1到j-1$部分相同,此时要使输入的串$1到i$与目标串$1到j$部分相同,就应插入一个字符,付出代价为$x $)
    • $2.$删除一个字符 $f[i,j]=f[i-1,j]+y $
    • $3.$移动一个字符并转换成任意字符 $f[i,j]=f[i-1,j-1]+z*abs(a[i]-b[j])$ $a$即输入的串,$b$即目标串
    • 初始化:当输入的串为空时,需插入与目标串长度相同的字符,代价为$ix $
      当目标串为空时,需删去与原串长度相同的字符,代价为$i
      y$
  • 【AC代码】
#include<bits/stdc++.h>
using namespace std;
int x,y,z,n,xx,yy,a[1001],b[1001],f[1001][1001],l,ll;
int main() {
    scanf("%d%d%d%d",&n,&x,&y,&z);
    for (int i=1; i<=n; i++) {
        scanf("%d%d",&xx,&yy);
        for (int j=1; j<=xx; j++){
            a[++l]=i;
        } 

        for (int j=1; j<=yy; j++){
            b[++ll]=i;
        }
    }
    memset(f,0x7f,sizeof(f));
    for (int i=0; i<=l; i++){//初始化,代价为i*y
        f[i][0]=i*y;
    }
    for (int i=0; i<=ll; i++){//初始化,代价为i*x
        f[0][i]=x*i;
    }
    for (int i=1; i<=l; i++){
        for (int j=1; j<=ll; j++){
            f[i][j]=min(f[i-1][j-1]+z*abs(a[i]-b[j]),min(f[i-1][j]+y,f[i][j-1]+x));
        }
    }
    printf("%d",f[l][ll]);
    //system("pause");
    return 0;
}

原文地址:https://www.cnblogs.com/Forever-chen/p/11386729.html

时间: 2024-10-22 14:00:25

2019模拟赛09场解题报告的相关文章

NOIP模拟赛-旅行者问题 解题报告

题目 :http://www.tsinsen.com/P9143 题是我发现.想测可以测.已经分享出去了. 旅行者问题 [问题描述] lahub是一个旅行者的粉丝,他想成为一个真正的旅行者,所以他计划开始一段旅行.lahub想去参观n个目的地(都在一条直道上).lahub在起点开始他的旅行.第i个目的地和起点的距离为ai千米(ai为非负整数).不存在两个目的地和起点的距离相同. 从第i个目的地走到第j个目的地所走的路程为 |ai-aj|千米.我们把参观n个目的地的顺序称作一次“旅行”.lahub

【模拟题(63550802...)】解题报告【贪心】【拓扑排序】【找规律】【树相关】

目录: 1.A[树相关]    2.B[找规律]    3.C[贪心][拓扑排序] A. 描述(A 输入文件 : A.input 输出文件 : A.output)一个城市的构成是一颗n 个节点的树(2 ≤ n ≤ 200), 现在需要在树中找出两条不相交的路径(即两条路径不能有重边也不能有重点),使得路径的长度的乘积最大.输入描述第一行一个数n 表示这个城市一共有 n 个节点.接下来 n-1 行,每行两个数ai 和bi (1 ≤ ai,bi ≤ n ),分别表示从ai 到bi,有一条边,每条边的

2019/9/17 校内练习赛 解题报告

比赛详情 本次练习赛已收录至2019/9/22 本周总结 tree 给定一棵无根树,求使得所有节点深度和最大的根节点. 思路 考虑先令\(1\)为根,\(tot\)为此时所有节点深度和,预处理\(size(x)\)表示\(x\)子树的大小.设\(u\)为\(1\)的某个儿子,显然从\(1\)走到\(u\)时,有: \[tot=tot+(n-size[u])-size[u]\] 将结论推广到所有节点,扫描整棵树得到答案,时间复杂度\(O(n)\). 代码 #include<bits/stdc++.

Noip模拟考试6:解题报告

Peter喜欢玩数组.NOIP这天,他从Jason手里得到了大小为n的一个正整数数组.Peter求出了这个数组的所有子段和,并将这n(n+1)/2个数降序排序,他想知道前k个数是什么. 不难想到,从最大字段和向下更新. 用set实现 介绍一下set:用法与priority_queue基本相同,区别是,set有自动去重,而priority_queue将所有元素放入. 然后通过三元组,分别记录权值,左端与右端. #include<cstdio> #include<algorithm>

zjgsu第五场解题报告

B.hdu2522 A simple problem 求小数循环节的思路可以看看这个链接 http://www.cnblogs.com/hxsyl/p/3330481.html 1/n的循环节最多有(n-1)个数,只要用长除法得到第一次余数重复,前面的的商就是答案 n也可以是负数 #include <iostream> #include <cstdio> #include <cmath> #include <cstring> using namespace

13南京区域赛现场赛 题目重演 解题报告

A.GPA(HDU4802): 纯属进入状态用,给你一些字符串对应的权重,求加权平均,如果是N,P不计入统计 GPA Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 32768/32768 K (Java/Others)Total Submission(s): 1193    Accepted Submission(s): 743 Problem Description In college, a student may take

Noip模拟考试八:解题报告

1. 鼎纹(grain.cpp/.c/.pas)[问题描述]据说鼎纹的 种制造 式是 铜模印出来的,这是我国古代劳动 智慧的结晶.铜模印过的地 ,会留下深深的印记,经过时间的炼化,洗练成历史的遗存.聪明的古代劳动人民拥有一个 a 行 b 列的铜模,每个位置要么是 0(代表这个点是平的),要么是 1(代表这个点是凸起的).他们想造 个 n 行 m 列的鼎 ,其中每个位置也都是 0 或 1,表示经过若干 次印后,每个位置的结果.有一些要求.铜模是不能旋转和翻转的:在印的过程当中,铜模的凸起不能出现在

Noip模拟考试7:解题报告

1 . 二叉树( ( binary .cpp/c/pas)[问题描述]二叉排序树或者是一棵空树,或者是具有下列性质的二叉树:(1)若左子树不空,则左子树上所有结点的值均小于它的根结点的值:(2)若右子树不空,则右子树上所有结点的值均大于它的根结点的值:(3)左.右子树也分别为二叉排序树:(4)没有键值相等的结点.完全二叉树:只有最下面的两层结点度能够小于2,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树. 给出N个数,且这N个数构成1至N的排列.现在需要你按顺序构建一棵二叉排序树,并按

NOIP模拟2017.6.11解题报告

T1: 水题: 代码: #include <cstdio> #include <iostream> #include <algorithm> using namespace std; #define maxn 100005 int n,ai[maxn],Max[maxn],bi[maxn],size; int tree[maxn]; inline void in(int &now) { char Cget=getchar();now=0; while(Cget&