CSP2021 J2参考解析
P7909 [CSP-J 2021] 分糖果 candy
-
涉及知识点:阅读理解、分支+数学思维、循环
-
解析:本题能够直接读明白题目,但是要注意题目要求输出的结果是自己单次能得到的最大值。
-
方法1:暴力枚举,直接将 [L, R] 进行枚举,和现有最大值进行比较,每次取最大值。
但是题目要求的数据范围到 1e9,而竞赛中一般认为计算机 1秒可执行的次数约在 5e8, 1e9必爆,故而该方法很遗憾只有70分,不能拿到满分。 -
方法2:数学思想,如果每个小朋友至少可以有 2 次选取的机会(也就是 L/n < R/n 的时候),那么最大值就是 n-1;
否则当小朋友只有 1 次选择的机会的时候,最大值为 R%n。
于是,O(1)的时间复杂度就解决了这个看似很难,但也确实不容易的题目。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int n,l,r;
int slove1(){
int ans=0;
for(int k=l; k<=r; k++){
ans=max(ans, k%n);
}
return ans;
}
int slove2(){
int ans=0;
if(l/n < r/n) ans=n-1;
else ans=r%n;
return ans;
}
int main(){
freopen("candy.in", "r", stdin);
freopen("candy.out", "w", stdout);
while(cin>>n>>l>>r){
// cout<<slove1()<<endl;
cout<<slove2()<<endl;
}
fclose(stdin); fclose(stdout);
return 0;
}
P7910 [CSP-J 2021] 插入排序 sort
-
涉及知识点:插入排序、复杂度、算法优化
-
解析:题目较长,耐心读完,发现题目好像不难,但是一看数据点,直接玩完,不过还是要冷静下来动手去拿到最高的分数。
-
方法1:纯模拟,什么都不思考,就按照题目模拟一遍,当然这样只能拿到 36~52分。
-
方法2:将原位置排序后的现有位置记录下来,但是还是用到了暴力排序,大概能拿到 70~80分。
-
方法3:方法2 的基础上优化,题目中有一个重要信息:保证类型 1 的操作次数不超过 5000,也就是说我们的主要操作会发生在...查询上,那么考虑如何减少查询所需要的时间。肯定是开个数组直接记录,保证 O(1) 查询即可,之后的时间主要是在插入元素后的排序上,那么可以对此进行优化到 O(n),这样保证整体复杂度为 O(n*q)。
注意:\(O(n*q)\) 不会带入最大数据计算,因为其中有修改和查询两种操作,很难计算到精确值。 -
方法5:数学思维,分桶计数,不模拟交换,计数 a[x]左边小于 a[x] 的元素个数 l,以及 a[x]右边大于 a[x]的元素个数 r,发现 a[x]排序后的位置为:x+r-l, 但是这样的方法同样会超时,毕竟每个数都需要进行一次查找,大概能拿到 70~80分。
-
方法1 : 36分代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e4;
struct T{
int id, value;
}a[N], b[N];
void insertSort(T arr[], int n){
for(int i=1; i<=n; i++)
for(int j=i; j>=2; j--)
if(arr[j].value < arr[j-1].value){
T t = arr[j-1]; arr[j-1] = arr[j]; arr[j] = t;
}
}
int find(T arr[], int n, T x){
for(int i=1; i<=n; i++)
if(arr[i].value==x.value && arr[i].id==x.id) return i;
return -1;
}
bool cmp(T a, T b){
return a.value<b.value;
}
int main(){
freopen("sort.in", "r", stdin);
freopen("sort.out", "w", stdout);
int n,q; scanf("%d%d", &n, &q);
for(int i=1; i<=n; i++){
scanf("%d", &a[i].value); a[i].id=i;
}
for(int i=1; i<=q; i++){
int f,x,v; scanf("%d%d", &f, &x);
if(f==1){ //修改
scanf("%d", &v); a[x].value=v;
}else if(f==2){ //查询
for(int i=1; i<=n; i++) b[i]=a[i];
insertSort(b, n);
printf("%d\n",find(b, n, a[x]));
}
}
fclose(stdin); fclose(stdout);
return 0;
}
- 方法1: 52分代码,在模拟的基础上,利用 sort 稍加改进,注意稳定排序
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e4;
struct T{
int id, value;
}a[N];
//因为不是完全模拟插入排序 ,所以cmp发生一点小改动,需要保证稳定排序
bool cmp(T a, T b) {
if(a.value!=b.value) return a.value<b.value;
return a.id<b.id;
}
int main(){
freopen("sort.in", "r", stdin);
freopen("sort.out", "w", stdout);
int n,q; scanf("%d%d", &n, &q);
for(int i=1; i<=n; i++){
scanf("%d", &a[i].value); a[i].id=i;
}
sort(a+1, a+1+n, cmp);
for(int i=1; i<=q; i++){
int f,x,v; scanf("%d%d", &f, &x);
if(f==1){ // 修改
scanf("%d", &v);
for(int j=1; j<=n; j++){
if(a[j].id==x){
a[j].value=v; break;
}
}
sort(a+1, a+1+n, cmp);
}else if(f==2){ // 查询
for(int j=1; j<=n; j++){
if(a[j].id==x){
printf("%d\n", j); break;
}
}
}
}
fclose(stdin); fclose(stdout);
return 0;
}
- 方法2 : 70~80分代码,将原位置的现有位置记录下来,不用每次都去查询
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e4;
struct T {
int id,value;
}a[N];
int b[N];//存放原位置的现在下标
bool cmp(T a, T b) {
if(a.value!=b.value) return a.value<b.value;
return a.id<b.id;
}
int main() {
freopen("sort.in", "r", stdin);
freopen("sort.out", "w", stdout);
int n,q; scanf("%d%d", &n, &q);
for(int i=1; i<=n; i++) {
scanf("%d", &a[i].value); a[i].id=i;
}
sort(a+1, a+1+n, cmp);
for(int i=1; i<=n; i++) b[a[i].id]=i;
int f,x,v;
for(int i=1; i<=q; i++) {
scanf("%d%d", &f, &x);
if(f==1) { //修改
scanf("%d", &v); a[b[x]].value=v;
sort(a+1, a+1+n, cmp);
for(int i=1; i<=n; i++) b[a[i].id]=i;
} else if(f==2) { //查询
printf("%d\n", b[x]);
}
}
fclose(stdin); fclose(stdout);
return 0;
}
- 方法3:100分代码,由于数据只有一个无序,可以考虑对排序部分进行 O(n) 优化
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e4;
int n,q;
struct T {
int id,value;
bool operator< (const T&temp) const {
if(value!=temp.value) return value<temp.value;
return id<temp.id;
}
}a[N];
int b[N];//存放原位置的现在下标
void insertSort1(int x){ // 1次优化
T temp=a[b[x]];
for(int i=b[x]; i<n; i++) a[i]=a[i+1]; a[n]=temp;
for(int i=n; i>1; i--){
if(a[i]<a[i-1]) swap(a[i-1],a[i]);
else break;
}
}
void insertSort(int x){ // 2次优化
int p=b[x];
for(int i=b[x]-1; i>=1; i--){
if(a[p]<a[i]) swap(a[p],a[i]),p=i;
else break;
}
for(int i=b[x]+1; i<=n; i++){
if(a[i]<a[p]) swap(a[p],a[i]),p=i;
else break;
}
}
int main() {
freopen("sort.in", "r", stdin);
freopen("sort.out", "w", stdout);
scanf("%d%d", &n, &q);
for(int i=1; i<=n; i++) {
scanf("%d", &a[i].value); a[i].id=i;
}
sort(a+1, a+1+n);
for(int i=1; i<=n; i++) b[a[i].id]=i;
int f,x,v;
for(int i=1; i<=q; i++) {
scanf("%d%d", &f, &x);
if(f==1) { //修改
scanf("%d", &v); a[b[x]].value=v;
insertSort(x);
// sort(a+1, a+1+n);
for(int i=1; i<=n; i++) b[a[i].id]=i;
} else if(f==2) { //查询
printf("%d\n", b[x]);
}
}
fclose(stdin); fclose(stdout);
return 0;
}
- 方法4:70~80分代码,带有一点数学思想,具体推导过程如下图。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e4;
int a[N];
int main(){
freopen("sort.in", "r", stdin);
freopen("sort.out", "w", stdout);
int n,q; cin>>n>>q;
for(int i=1; i<=n; i++) cin>>a[i];
for(int i=1; i<=q; i++){
int f,x,v; cin>>f>>x;
if(f==1){
cin>>v; a[x]=v;
}else{ //查询
int l=0,r=0;
for(int i=1; i<x; i++) if(a[i]>a[x]) l++;
for(int i=x+1; i<=n; i++) if(a[i]<a[x]) r++;
cout<<x-l+r<<endl;
}
}
fclose(stdin); fclose(stdout);
return 0;
}
P7911 [CSP-J 2021] 网络连接 network
-
涉及知识点:模拟
-
解析:一道大模拟,题目不难,但是很考验代码量,本题需要仔细读题,
然后你会发现一个十分有趣的东西, 50% 的数据满足性质 1,那么性质1 又是什么呢? -
就是说 ip合法,那么问题来了,本题最难的是什么?
不就是判断 ip地址合法嘛,我假设 ip合法,直接开始做,也能得到 50分,不香吗? -
50分代码如下
点击查看代码
#include<bits/stdc++.h>
#include<stdio.h>
using namespace std;
map<string, int> m;
//检查 ip地址是否合法
bool check(string s){
return true;
}
int main(){
freopen("network.in", "r", stdin);
freopen("network.out", "w", stdout);
int n; cin>>n;
for(int i=1; i<=n; i++){
string op,ad;cin>>op>>ad;
if(!check(ad)){
cout<<"ERR"<<endl;
}else{
if(op=="Server"){
if(m[ad]==0){
m[ad]=i; cout<<"OK"<<endl;
}else{
cout<<"FAIL"<<endl;
}
}else if(op=="Client"){
if(m[ad]!=0){
cout<<m[ad]<<endl;
}else{
cout<<"FAIL"<<endl;
}
}
}
}
fclose(stdin); fclose(stdout);
return 0;
}
- 100分代码
加上 ip地址合法的判断,注意:这里我使用了sscanf/sprintf
的写法,这里部分小伙伴没用过,
那么也可以使用其他写法,我只是觉得这样更方便,如果没学过这部分的同学可以参考如下链接 https://www.cnblogs.com/hellohebin/p/15456897.html
点击查看代码
#include<bits/stdc++.h>
#include<stdio.h>
using namespace std;
map<string, int> m;
//检查 ip地址是否合法
bool check(string s){
char buf[100], buf2[100];
for(int i=0; i<s.size(); i++) buf[i]=s[i]; buf[s.size()]='\0';
int a,b,c,d,e;
sscanf(buf, "%d.%d.%d.%d:%d", &a, &b, &c, &d, &e);
sprintf(buf2, "%d.%d.%d.%d:%d", a, b, c, d, e);
// printf("%s\n", buf); //输出调试
// printf("%s\n", buf2);
// printf("%d.%d.%d.%d:%d\n", a, b, c, d, e);
if(a<0||a>255||b<0||b>255||c<0||c>255||d<0||d>255||e<0||e>65535) return false;
if(strcmp(buf, buf2)) return false;
return true;
}
int main(){
freopen("network.in", "r", stdin);
freopen("network.out", "w", stdout);
int n; cin>>n;
for(int i=1; i<=n; i++){
string op,ad;cin>>op>>ad;
if(!check(ad)){
cout<<"ERR"<<endl;
}else{
if(op=="Server"){
if(m[ad]==0){
m[ad]=i; cout<<"OK"<<endl;
}else{
cout<<"FAIL"<<endl;
}
}else if(op=="Client"){
if(m[ad]!=0){
cout<<m[ad]<<endl;
}else{
cout<<"FAIL"<<endl;
}
}
}
}
fclose(stdin); fclose(stdout);
return 0;
}
P7912 [CSP-J 2021] 小熊的果篮 fruit
-
涉及知识点:模拟、复杂度、算法优化
-
解析:模拟题+优化
-
方法1:纯模拟一遍,打好标记即可,但是会超时,能得 30~70分。
-
方法2:对方法1 进行优化,主要时间浪费在查询上面,每一次处理过的元素其实不必再次处理了,需要用到分块/分桶的思想,开结构体,用左右元素标记块的界限,结合队列的方式来实现。
-
30~70分代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,a[N],cnt=0;
int main() {
freopen("fruit.in", "r", stdin);
freopen("fruit.out", "w", stdout);
scanf("%d", &n);
for(int i=1; i<=n; i++) scanf("%d", &a[i]);
while (cnt<n) {
int pre=2; // 不是 01就行
for(int i=1; i<=n; i++) {
if(a[i]!=-1 && pre!=a[i]) {
printf("%d ", i);
pre=a[i],a[i]=-1,cnt++;
}
} puts("");
}
fclose(stdin); fclose(stdout);
return 0;
}
- 这里提一下,有一个细节问题,如下程序中,使用结构体存储数据,建议不要写成
for(int i=1; i<=n; i++) scanf("%d", &a[i]), t[i]=T(a[i],i);
而是单独将输入输出分开,可以有一定的加速。
不信可以将下面这个程序进行修改,发现会少 10分,我的天啊!竟然少了 10分。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,a[N],vis[N],cnt=0;
struct T {
int value,id;
T(int a=0,int b=0):value(a),id(b) {}
} t[N];
int main() {
freopen("fruit.in", "r", stdin);
freopen("fruit.out", "w", stdout);
scanf("%d", &n);
for(int i=1; i<=n; i++) scanf("%d", &a[i]);
for(int i=1; i<=n; i++) t[i]=T(a[i],i);
while(cnt<n) {
int pre=2; // 不是 01就行
for(int i=1; i<=n; i++) {
if(!vis[i] && pre!=t[i].value) {
printf("%d ",t[i].id);
vis[i]=1, ++cnt, pre=t[i].value;
}
} puts("");
}
fclose(stdin); fclose(stdout);
return 0;
}
-
分块+队列代码,将每一个块,放入队列;每次对队首元素进行处理。
-
该解法思路是正解,但是需要二次优化,如下程序在官方数据下跑了 80分。
点击查看代码
#include<cstdio>//企图不用万能头,降低预编译时间,然而并没用。
#include<iostream>
#include<queue>
using namespace std;
const int N=1e6+10;
int n,a[N],vis[N],cnt=0;
struct T {
int l,r,value;
T(int a=0,int b=0,int c=0):l(a),r(b),value(c) {}
};
queue<T> que;
int main() {
freopen("fruit.in", "r", stdin);
freopen("fruit.out", "w", stdout);
scanf("%d", &n);
for(int i=1; i<=n; i++) scanf("%d", &a[i]);
que.push(T(1,1,a[1]));
for(int i=2; i<=n; i++) {
if(que.back().value==a[i]) que.back().r=i;
else que.push(T(i,i,a[i]));
}
while(que.size()) {
int size=que.size(), pre=2;
for(int i=1; i<=size; i++) {
if(que.front().value!=pre) {
printf("%d ", que.front().l);
que.front().l++;
pre=que.front().value;
}
if(que.front().l <= que.front().r) {
que.push(que.front());
}
que.pop();
} puts("");
}
fclose(stdin); fclose(stdout);
return 0;
}
- 满分优化是在一个块用完之后,出现可以合并的块,就合并。
- 100分代码
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+10;
int n,a[N],vis[N],cnt=0,len=0;
struct T {
int l,r,value;
T(int a=0,int b=0,int c=0):l(a),r(b),value(c) {}
} p, t;
queue<T> que, que2;
int main() {
// freopen("fruit.in", "r", stdin);
// freopen("fruit.out", "w", stdout);
scanf("%d", &n);
for(int i=1; i<=n; i++) scanf("%d", &a[i]);
que.push(T(1,1,a[1]));
for(int i=2; i<=n; i++) {
if(que.back().value==a[i]) que.back().r=i;
else que.push(T(i,i,a[i]));
}
while(cnt<n) {
while(que.size()) {
p=que.front(), que.pop();
while(vis[p.l] && p.l<=p.r) p.l++;
if(p.l>p.r) continue;
printf("%d ", p.l), cnt++;
vis[p.l]=1, p.l++;
if(p.l<=p.r) que2.push(p);
} puts("");
while(que2.size()) {
p=que2.front(), que2.pop();
while(que2.size()) {
t=que2.front();
if(p.value==t.value) p.r=t.r,que2.pop();
else break;
}
que.push(p);
}
}
return 0;
}