开始: 2022-10-16 08:30:00

2022CSP-J模拟赛2

结束: 2022-10-16 12:00:00
当前: 2025-0606-1818 06:54:41  类型:OI 状态:已经结束 

A-计数问题

#include<bits/stdc++.h>

using namespace std;

int main()

{

int n,x,b,c,t=0;

cin>>n>>x;//输入范围与要查的数字;

for(int i=1;i<=n;i++)//一到n进行循环;

{

b=i;//为了不改变i的值,就把i赋值给一个数;

while(b)//如果b不等于0,继续循环;

{

c=b%10;//求是否是x,是的话计数器加1;

b=b/10;//求下一个数字是否为x;

if(c==x) t++;//计数器加1;

}

}

cout<<t<<endl;//输出计数器的数字;

return 0;//结束

}

B-表达式求值

这道题的读入方式不需要很复杂,直接 scanf(“%d”)和 scanf(“%c”)就可以,不会出问题。

处理方式是每一次读入判断其上一个运算符,进行相应的处理,然后添加到sum中。

注意如果开int,有可能两个2^31-1相乘,可能出问题。所以都用long long存。

只输出后四位,则每一步操作都%10000即可。

#include <bits/stdc++.h>

using namespace std;

int main()

{

long long x, ans = 0, sum = 0; //记得ans初始值赋为0;sum为其中一段运算(即一段连续的乘积)的值

char last = 0, now; // last存储上一个运算符,now为新读入的运算符

bool flag = true; //控制循环的变量 后文有描述

while (flag)

{

scanf("%lld", &x);

// scanf有返回值 返回的是读入的字符数now

flag = scanf("%c", &now) == 1 ? true : false;

//如果没有运算符读入了,则flag=false,既保证了此次循环的正常运行,又能在下一遍循环跳出

if (last == 0) sum = x; //如果是刚开始读入,则直接赋值

if (last == '+')

{

ans = (ans + sum) % 10000;

sum = x;

//如果上一个操作是加法,则将前一段的值加入到ans中,然后再更新此新段的值

}

if (last == '*') sum = (sum * x) % 10000; //如果上一个运算是乘法,则将此数乘入本段的值中

if (!flag) ans = (ans + sum) % 10000; //如果是最后一个元素,则进行最后的更新

last = now; //将下一个读入的运算符作为新的一个循环的上一个运算符,并继续循环

}

printf("%lld", ans); //输出

return 0;

}

C-选数分组

注:这道题有点难度,应该放在第四题,此处有出题失误,如果看不懂题解不用强求。

首先,一共有20头奶牛,那么考虑对于每一头奶牛来说有3种状态,放在一组,放在另一组,不放任何一组,如果暴力枚举时间复杂度为O(3^n) > 1e9,无法接受。

考虑将n头奶牛分为两半,每组分别暴力求解,时间复杂度O(3^\frac{n}{2})可以通过。

假设在前一半中,在第一组中放的数的和为a,在第二组中放的数为b。

假设在后一半中,在第一组中放的数的和为c,在第二组中放的数为d。

我们需要第一组和第二组和相等,即a+c=b+d。

由于我们要对每一半分开处理,所以考虑将同一半的数放在一起处理,即移项得a-b=d-c。

因此,我们只需要统计在每一半中和为a-b 或 d-c的方案有多少种,再进行组合。

一个数被放入第一组中,a-b的值变大,在第二组中,a-b的值变小,如果不放,则a-b不变,所以维护a-b的值即可。

d-c同理,不管是c-d还是d-c本质上没有区别,处理方法与a-b相同。

但是这里要注意题中问的是: “给n个数,从中任意选出一些数,使这些数能分成和相等的两组。求有多少种选数的方案。” 也就是说,只要某一组数可以分成两个相等的集合,不管这一组可以有多少种方案分成两个相等的集合那么计数加一,而不是求所有的两个集合相等的组合方案数

所以我们需要判重,这道题判重可以用状态压缩的思想。如果我们有十个数可以选,现在用于判重的是数字5,变为二进制是0000000101,就表示第一个和第三个被选了。代码中用到的位运算符号大家不了解的可以自行学习。

#include <bits/stdc++.h>

using namespace std;

int n, N, a[21], ans[2000001], s, tot;

vector<int> p[2000001];

map<int, int> b;

void dfs1(int x, int sum, int now)

{ //对前一半搜索,x表示到了第几个,sum表示a-b的值,now表示状压,取了那些数

if (x > N)

{

if (b[sum] == 0) b[sum] = ++tot; //如果a-b是第一次找到sum这个值 就用一个编号来表示sum

p[b[sum]].push_back(now); //将now这种方案放入以sum的编号b[sum]作为下标的vector数组中

return;

}

dfs1(x + 1, sum + a[x], now | (1 << (x - 1))); //三种情况讨论

dfs1(x + 1, sum - a[x], now | (1 << (x - 1)));

dfs1(x + 1, sum, now);

}

void dfs2(int x, int sum, int now)

{ //对后一半搜索,同上

if (x > n)

{

int t = b[sum]; //取出sum对应的编号

if (t != 0) //如果sum有编号 证明a-b出现过sum这个值 那就可以累计答案 如果t==0 就表示a-b没有这个值

for (int i = 0; i < p[t].size(); i++)

ans[p[t][i] | now] = 1; //对于每一种可能的组合,将值赋为1,意为这种方案取数可行

//注意,题目中要求的方案数为取数的方案数而不是分数的方案数,因此不是+1而是=1

return;

}

dfs2(x + 1, sum + a[x], now | (1 << (x - 1)));

dfs2(x + 1, sum - a[x], now | (1 << (x - 1)));

dfs2(x + 1, sum, now);

}

int main()

{

scanf("%d", &n);

N = n / 2;

for (int i = 1; i <= n; i++)

scanf("%d", &a[i]);

dfs1(1, 0, 0); //处理前一半

dfs2(N + 1, 0, 0); //处理后一半

for (int i = 1; i <= (1 << n); i++)

s += ans[i];

printf("%d", s);

return 0;

}

D-超能侠的魔法

这道题的主要难点在于旋转的代码怎么描述,剩余的就是模拟即可。如果一时不知道怎么写,可以多在纸上画一画旋转的情况,找到旋转前和旋转后坐标的对应关系。

不妨设旋转的中心(x,y)为原点(0,0),这样在纸上模拟一下就可以得到顺时针旋转90°后正方形上一点(i,j)坐标变成(j,-i),逆时针旋转90°后坐标变成(-j,i)。所以我们只需要枚举i,j即可,坐标需要加上偏移量x和y。

注意旋转时因为会覆盖其他数字,所以需要用一个新数组暂存旋转后的情况,代码中原数组为a,暂存数组为b。

#include <bits/stdc++.h>

using namespace std;

int n,m;

int x,y,r,z;

int a[510][510],b[510][510];

void change(int x,int y,int r,int z)

{

if(z==0)

{

for(int i=-r;i<=r;i++)

{

for(int j=-r;j<=r;j++)

{

b[x+j][y-i]=a[x+i][y+j];

}

}

}

if(z==1)

{

for(int i=-r;i<=r;i++)

{

for(int j=-r;j<=r;j++)

{

b[x-j][y+i]=a[x+i][y+j];

}

}

}

}

void same()

{

for(int i=1;i<=n;i++)

{

for(int j=1;j<=n;j++)

{

a[i][j]=b[i][j];

}

}

}

void print()

{

for(int i=1;i<=n;i++)

{

for(int j=1;j<=n;j++)

{

cout<<b[i][j]<<' ';

}

cout<<endl;

}

}

int main()

{

scanf("%d%d",&n,&m);

a[1][1]=1;

for(int i=1;i<=n;i++) //得到原数组 这一段大家可以自由发挥

{

if(i!=1) a[i][1]=a[i-1][1]+n;

for(int j=1;j<=n;j++)

{

if(j!=1) a[i][j]=a[i][j-1]+1;

b[i][j]=a[i][j];

}

}

for(int i=1;i<=m;i++)

{

scanf("%d%d%d%d",&x,&y,&r,&z);

change(x,y,r,z);

same(); //将改变后的数组复制到原数组中

}

print(); //输出答案

return 0;

}