2019模拟赛09场解题报告

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\) 份。价格是 \(6*3+1*2+1*2=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 种菜。成本是 \(4*2+3*2=14\)。剩余数量为 \({4,0,2,0,4,0,7,2}\)
4.第四个客户需要 4 份第 3 种菜,所以餐厅将提供 2 份第 3 种菜和 2 份第 8 种菜。成本是 \(2*3+2*2= 10\)。剩余数量为 \({4,0,0,0,4,0,7,0}\)
5.第五个客户需要 10 份第 6 种菜,第 6 种菜卖完了,所以餐厅将提供客户 7 份第 7 种菜,3 份第 1 种菜。成本是 \(7*3+3*6=39\)。剩余数量为 \({1,0,0,0,4,0,0,0}\)
样例二:每个客户都按他们的订单服务,除了最后一个客户,他愤怒地离开并且没有付款。例如,第二个客户需要 6 份第 2 种菜,因此成本为 \(66*6= 396\)
样例三:某些客户可能无法获得他们订单服务。例如,第二个客户需要 13 份第 2 种菜,所以只能买目前最便宜的 6 份第 3 种菜,1 份第 4 种菜,因此成本为 \(66*6+666*6+6666*1=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;
}

题三:马蹄印

【题面】
虽然当奶牛贝里斯找到平衡序列后很高兴了,但是他现在对序列提出了一个更高的要求,就是要求每个序列中必须是先一定数量的左括号然后是与左括号相同数量的右括号。例如:(((()))),就是一个完美的平衡序列。
当贝里斯某天在农场上走的时候,他在地上发现了马蹄印,这个农场是一个 \(N*N\) 的方格,每个小方格中都有一个马蹄印。贝里斯希望从方格的最左上角的地方开始出发,然后每次可以向上或者向下或者向左或者向右移动一步,使得他走过的每个小方格中的马蹄印能够组成一个完美的平衡序列。当然了,贝里斯不能重复经过任何小方格。
请帮助贝里斯在这个 \(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\) 即目标串
    • 初始化:当输入的串为空时,需插入与目标串长度相同的字符,代价为 $i*x $
      当目标串为空时,需删去与原串长度相同的字符,代价为 \(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;
}
posted @ 2019-08-21 08:55  半笙、凡尘  阅读(199)  评论(2)    收藏  举报