【刷题记录】NOIP & CSP PJ 难度刷题记录
前言
本来不想写前言的(>人<;)
这只是 mjl 给我们布置的作业,并不是我自己在刷题!
不保证所有代码的正确性,它们仅仅是通过了所有数据点而已。
1.模拟板块
整体难度:红~黄(模拟不会有什么难题 ,别跟我说像猪国杀、儒略日那种)
T1 计算器的改良
AC at 2021-07-31 14:34:08.
难度:黄
解一元一次方程,就是把未知数的系数移到等号左边,常数移到等号右边,然后再除一下就可以了。
我们设置两个变量 \(l,r\),分别代表未知数系数计算后的结果和常数的计算结果。最后模拟就可以了。记得“移项变号”,而且往左移和往右移是相反的,如果写成一样了的可以像我一样在任意一边加一个负号,不影响结果。
不过这个模拟还是有些讲究的。
首先要把整个字符串分为 \(3\) 个部分:等号左边、等号和等号右边。
先遍历等号左边,如果看到数字了就把这个连续是一段数字的字符给转化为整数类型,然后再看这到底是系数还是常数;再再看正负。关于正负可以使用布尔变量来标记。
如果是系数就甩到 \(l\) 变量,是常数就甩到 \(r\) 变量。注意怎么甩,要移项变号。这时候可能要移项也有可能不移,要注意。
遇到减号,把布尔变量设为真。
遇到加号,把布尔变量设为假。(因为我们默认的系数和常数的符号是正,所以加号并没有什么用,只需要布尔变量归零就可以了)
遇到字母特判(经过数字的判断之后,这个字母就是系数为 \(±1\) 的未知数),也需移项变号。
等号右边同理。
记得存未知数的字母,别像我一样最后有一个测试点未知数只在等号右边,但是我处理那一块的时候没写存未知数字母的语句[捂脸]。
废话不多说,上代码。
Code
#include<cstdio>
#include<cstring>
char fh,a[105];
int len,h,l,r;
bool is_fu; //用来判断这个系数 or 常数是不是负数
int main(){
scanf("%s",a+1);
len=strlen(a+1);
//等号左边
for(int i=1;i<=len;i++){
int k=0;
if(a[i]=='='){
h=i+1;
is_fu=0;
break;
}else if(a[i]=='+') is_fu=0;
else if(a[i]=='-') is_fu=1;
else if(a[i]>='0'&&a[i]<='9'){
while(a[i]>='0'&&a[i]<='9'){
k=k*10+a[i]-'0';
i++;
}
i--;
if(a[i+1]>='a'&&a[i+1]<='z'||a[i+1]>='A'&&a[i+1]<='Z'){ //未知数
fh=a[i+1];
if(is_fu){
l-=k;
is_fu=0;
}else l+=k;
}else{ //数字
if(is_fu){
r-=k;
is_fu=0;
}else r+=k;
}
k=0;
}
}
//等号右边
is_fu=0;
for(int i=h;i<=len;i++){
int k=0;
if(a[i]=='+') is_fu=0;
else if(a[i]=='-') is_fu=1;
else if(a[i]>='0'&&a[i]<='9'){
while(a[i]>='0'&&a[i]<='9'){
k=k*10+a[i]-'0';
i++;
}
i--;
if(a[i+1]>='a'&&a[i+1]<='z'||a[i+1]>='A'&&a[i+1]<='Z'){ //未知数
fh=a[i+1];
if(is_fu){
l+=k;
is_fu=0;
}else l-=k;
}else{ //数字
if(is_fu){
r+=k;
is_fu=0;
}else r-=k;
}
k=0;
}
}
printf("%c=%.3lf",fh,-r*1.0/l);
return 0;
}
T2 税收与补贴问题
AC at 2021-08-06 9:44:39.
难度:黄
我寻思着这出题人语文该从小学重修叭。
大概意思就是先让你补全一个价格和购买人数关系的表,然后在价格上统一加(补贴)或减(收税)一个数,但是购买的人数不变,然后使得政府给出的这个价位获得的利润是所有价位都经过这个变化后中最大的。
实现我们可以用两个数组,一个用来输入,另一个,下标表示价格,数组里的值代表这个下标的价格所对应的人数。
这个问题有两个部分:
1.补全表格(此题最难部分)
题目有一个隐藏条件:在任意两个给定了人数的价格之间如果有没有给定人数的价格,那么中间所有没有给定人数的价格的人数都是“均匀地下降”,就是每两个价格所对应的人数差是一样的。
所以,遇到没有输入人数的价格时,就有三种情况:
-
这个价格小于给定人数的最大价格。
-
这个价格大于给定人数的最大价格。
-
这个价格是不合法的。(即小于成本价或者购买人数是负数)
为了避免计算不合法的价格,我们从成本价往上枚举价格,如果计算出来的人数是负数或 \(0\) 就立刻跳出循环。
思考如何计算第一种情况。假设我们已经枚举到了价格 \(=i\)。
我们需要确定这个价格两端最近的已经确定人数的价格是多少,因为我们是从小到大算,所以价格为 \(i-1\) 时的价格肯定已经算出。至于比它大的,枚举可以找出,在枚举的同时我们可以用一个 \(num\) 变量统计一下这中间价格的数量。
然后我们需要计算它对应的人数。怎么算呢?为了好理解我们把它分成两步:
第一步,算出两两价格之间的差值。
公式:\(d=\dfrac{b_{i-1}-b_R}{num}\),可以根据等差公式得出,也可以自己推(难度不大)。
其中 \(b\) 是上述提及实现方式的那个下标代表价格的数组。
第二步,根据 \(i-1\) 算出 \(i\)。
这个就很简单啦, \(b_i=b_{i-1}+d\) 即可。
然后再看看第二种情况。这个很简单,只需要在前面的基础上减去最后输入的那个数就可以了。
代码实现如下:
int l=a[0].money;
while(1){ //只要没有强制退出就一直循环
if(b[l]){ //这个价格输入中有对应,直接跳过
l++;
continue;
}
if(b[l-1]-p<=0) break; //如果这个价格不合法,退出循环
if(l<Max){ //情况一
int R=l,num=1;
while(!b[R]) R++,num++; //统计数量,找右端点
b[l]=b[l-1]-(b[l-1]-b[R])/num;
}else b[l]=b[l-1]-p; //情况二
l++;
Maxr=max(Maxr,l); //寻找合法价格的最右端点
}
Maxr--; //需要 -1,因为在此之前 l 加了 1
2.计算答案
过了难点我们就可以快乐地模拟了!
枚举补贴/收税的钱数,范围随意,能 A 就行/xyx。
大概思路就是暴力把每一个价位下补贴/收税后的利润做对比,如果政府规定的那个价格是最大的就可以输出。
比较简单(指我错了 \(10^9+7\) 遍,细节比较多 (〃>目<)),看代码理解。
for(int i=0;i<=MAXN-5;i++){
//补贴i元
int maxnum=0; //这个变量是用来统计最大的获利
for(int j=a[0].money;j<=Maxr;j++){
maxnum=max(maxnum,(j+i-a[0].money)*b[j]);
}
if(maxnum==(n+i-a[0].money)*b[n]){ //最大获利地数量等于政府规定地价格,输出结束
printf("%d",i);
return 0;
}
//收税i元
maxnum=0;
for(int j=a[0].money;j<=Maxr;j++){
maxnum=max(maxnum,(j-i-a[0].money)*b[j]);
}
if(maxnum==(n-i-a[0].money)*b[n]){
printf("-%d",i); //注意有负号
return 0;
}
}
最后加上预处理、特判等。
特别注意输入,坑死我……<(  ̄^ ̄)-+——。
Code
#include<cstdio>
#define max(a,b) (a)>(b)?(a):(b)
const int MAXN=(int)1e5+5;
struct node{int money,num;}a[MAXN];
int Max,Maxr,n,p,tot,b[MAXN];
int main(){
//输入及预处理
scanf("%d %d %d",&n,&a[0].money,&a[0].num);
b[a[0].money]=a[0].num;
while(1){
++tot;
scanf("%d %d",&a[tot].money,&a[tot].num);
if(a[tot].money==-1&&a[tot].num==-1){
--tot;
break;
}
Max=max(Max,a[tot].money);
b[a[tot].money]=a[tot].num;
}
//test
//for(int i=a[0].money;i<=Max;i++) printf("%d %d\n",i,b[i]);
scanf("%d",&p);
//补全条件
int l=a[0].money;
while(1){
if(b[l]){
l++;
continue;
}
if(b[l-1]-p<=0) break;
if(l<Max){
int R=l,num=1;
while(!b[R]) R++,num++;
b[l]=b[l-1]-(b[l-1]-b[R])/num;
}else b[l]=b[l-1]-p;
l++;
Maxr=max(Maxr,l);
}
Maxr--;
//枚举
for(int i=0;i<=MAXN-5;i++){
//补贴i元
int maxnum=0;
for(int j=a[0].money;j<=Maxr;j++){
maxnum=max(maxnum,(j+i-a[0].money)*b[j]);
}
if(maxnum==(n+i-a[0].money)*b[n]){
printf("%d",i);
return 0;
}
//收税i元
maxnum=0;
for(int j=a[0].money;j<=Maxr;j++){
maxnum=max(maxnum,(j-i-a[0].money)*b[j]);
}
if(maxnum==(n-i-a[0].money)*b[n]){
printf("-%d",i);
return 0;
}
}
printf("NO SOLUTION");
return 0;
}
T3 乒乓球
AC at 2021-07-31 14:50:38.
难度:橙
模拟水题,按照一轮一轮枚举。注意一轮结束需要同时满足两个条件,否则不要结束。
这题坑比较多,这里列两个我错过的:
-
如果给出的字符串刚刚好到一轮结束,需要在后面再输出一个“0:0”(也不知道为什么);
-
如果第一个字符是“E”记得输出两个“0:0”。
Code
#include<cstdio>
#include<cmath>
#include<cstring>
#define max(a,b) (a)>(b)?(a):(b)
int len,tot;
char s[25],a[100005];
int main(){
while(scanf("%s",s+1)!=EOF){
len=strlen(s+1);
for(int i=1;i<=len;i++){
if(s[i]==' '||s[i]=='\n') continue;
if(s[i]=='E') goto type1;
a[++tot]=s[i];
}
}
type1:
int w,l;
if(!tot){
printf("0:0\n\n0:0");
return 0;
}
//11
for(int i=1;i<=tot;i++){
w=0,l=0;
while((w<11&&l<11||abs(w-l)<2)&&i<=tot){
w+=(a[i]=='W');
l+=(a[i]=='L');
i++;
}
i--;
if(i==tot){
printf("%d:%d\n",w,l);
if(w==11||l==11) printf("0:0\n");
break;
}else printf("%d:%d\n",w,l);
}
printf("\n");
//21
for(int i=1;i<=tot;i++){
w=0,l=0;
while((w<21&&l<21||abs(w-l)<2)&&i<=tot){
w+=(a[i]=='W');
l+=(a[i]=='L');
i++;
}
i--;
if(i==tot){
printf("%d:%d\n",w,l);
if(w==21||l==21) printf("0:0\n");
break;
}else printf("%d:%d\n",w,l);
}
return 0;
}
T4 不高兴的津津
AC at 2021-07-30 21:25:42.
难度:红
这真的没什么好说的了,模拟即可 ,有手就行。/cy
Code
#include<cstdio>
#define max(a,b) (a)>(b)?(a):(b)
int Max,ans,a,b;
int main(){
for(int i=1;i<=7;i++){
scanf("%d %d",&a,&b);
if(Max<a+b) ans=i;
Max=max(a+b,Max);
}
if(Max<=8) ans=0;
printf("%d",ans);
return 0;
}
T5 花生采摘
AC at 2021-07-31 14:12:53.
难度:橙
为什么我觉得这道题应该算这套题里面比较难的了?
首先注意一个大坑:采花生需要时间!(这个我错了很久)
然后如果您认真读题,就会发现它并不是一个 dp,而是一个不大的模拟( ̄y▽, ̄)╭ 。(因为 mjl 归类的是模拟板块,我想如果这是考场上我八成会犯这个错误)。
首先把所有花生的坐标和果子数量存到一个结构体数组里面,然后按照果子数量从大到小排序,因为采摘花生的顺序是按照从大到小排的。