CSP-J 2021 题解
T1 分糖果
Problem
给出 \(L,R,n\),求出最大的 \(x\% n\ (L\le x\le R)\)。
Solution
简单的分类讨论。
若 \(R-L\ge n\),说明模 \(n\) 的余数 \(0\sim n-1\) 都是存在的, 那么答案就是 \(n-1\)。
若 \(R-L<n\),这种情况下又有两种情况:
- \(\lfloor \frac{L}{n}\rfloor<\lfloor\frac{R}{n}\rfloor\)。说明从 \(L\) 到 \(R\) 中经过了 \(x\to n-1\to 0\to y\) 的情况。所以答案也是 \(n-1\)。
- \(\lfloor \frac{L}{n}\rfloor=\lfloor\frac{R}{n}\rfloor\)。此时不存在 \(n-1\),因此答案就是 \(R\% n\)。
Code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll n,l,r,c1,c2,d1,d2;
int main()
{
freopen("candy.in","r",stdin);
freopen("candy.out","w",stdout);
scanf("%lld%lld%lld",&n,&l,&r);
if (r-l>=n) printf("%lld\n",n-1);
else
{
c1=l/n;c2=l%n;
d1=r/n;d2=r%n;
if (c1==d1) printf("%lld\n",d2);
else printf("%lld\n",n-1);
}
fclose(stdin);fclose(stdout);
return 0;
}
T2 插入排序
Problem
给出一个含有 \(n\) 个元素的数组 \(a\)。有 \(m\) 次操作,每次可以修改某个数的值或者查询某个数的排名。
修改操作最多 5000 次。
Solution
这题原本跟平衡树有点关系,但是看到限制:
至多有 5000 次操作属于类型一。
最多进行 5000 次修改。这意味着一次修改后求出所有元素的排名,到下次修改前的查询都是可以 \(O(1)\) 查询的。
那么我们只需要 \(O(n)\) 修改就可以轻松通过此题。
可以先预处理出每个数的排名,每次修改判断改后的值和改前的值的大小关系,选择向后还是向前修改。
Code
#include<bits/stdc++.h>
#define N 8005
using namespace std;
struct node
{
int id,val;
}a[N];
int n,q,x,y,opt,rk[N];
bool lst;
bool cmp(node x,node y)
{
if (x.val<y.val) return true;
if (x.val>y.val) return false;
return x.id<y.id;
}
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].val),a[i].id=i;
sort(a+1,a+n+1,cmp);
for (int i=1;i<=n;++i)
rk[a[i].id]=i;
while (q--)
{
scanf("%d",&opt);
if (opt==1)
{
scanf("%d%d",&x,&y);
x=rk[x];
if (y<a[x].val)
{
a[x].val=y;
for (int i=x-1;i;--i)
{
if (a[i].val>a[i+1].val||(a[i].val==a[i+1].val&&a[i].id>a[i+1].id))
{
node t;
t=a[i];a[i]=a[i+1];a[i+1]=t;
}
}
}
else if (y>a[x].val)
{
a[x].val=y;
for (int i=x;i<n;++i)
{
if (a[i].val>a[i+1].val||(a[i].val==a[i+1].val&&a[i].id>a[i+1].id))
{
node t;
t=a[i];a[i]=a[i+1];a[i+1]=t;
}
}
}
for (int i=1;i<=n;++i)
rk[a[i].id]=i;
}
if (opt==2)
{
scanf("%d",&x);
printf("%d\n",rk[x]);
}
}
fclose(stdin);fclose(stdout);
return 0;
}
T3 网络连接
Problem
有 \(n\) 台计算机,每台要么为服务机要么为用户机。
每台计算机有一个地址 \(s\),形如 \(a.b.c.d:e\),满足 \(0\le a,b,c,d\le 255,0\le e\le 65535\),且 \(a,b,c,d,e\) 都没有前导 0。
对于每台服务机,若地址不合法输出 \(\text{ERR}\),若地址已经出现过输出 \(\text{FAIL}\),否则输出 \(\text{OK}\)。
对于每台客户机,若地址不合法输出 \(\text{ERR}\),若地址出现过则输出对应服务机,否则输出\(\text{FAIL}\)。
Solution
比较明显的一个哈希。
首先先判断地址的合法性,注意前导0,\(a,b,c,d,e\) 的大小范围和 \(.\) 以及 \(:\) 的个数。
将 \(a,b,c,d,e\) 拼起来成为一个长度最大为 17 的数字,可以用 \(\text{long long}\) 存储。
判断该数字是否出现过,并根据计算机的类型输出对应的答案。
Code
#include<bits/stdc++.h>
#define ll long long
using namespace std;
int n,sum,num1,num2,x,y,opt;
ll res;
bool bj;
char ch,lst,llst;
struct node
{
int id;
ll val;
}h[19260820];
void charu(ll val,int id)
{
ll s=val%19260817;
while (h[s].val)
{
if (h[s].val==s)
{
x=2;
return;
}
++s;
if (s==19260817) s=0;
}
h[s].val=s;
h[s].id=id;
x=1;
}
void chazhao(ll val)
{
ll s=val%19260817;
while (h[s].val)
{
if (h[s].val==s)
{
y=h[s].id;
x=1;
return;
}
}
x=2;
}
int main()
{
freopen("network.in","r",stdin);
freopen("network.out","w",stdout);
scanf("%d",&n);
ch=getchar();
for (int i=1;i<=n;++i)
{
while (ch!='S'&&ch!='C') ch=getchar();
if (ch=='S') opt=1;
else opt=2;
while ((ch<'0'||ch>'9')&&ch!='.'&&ch!=':') ch=getchar();
bj=true;num1=num2=0;sum=0;res=0;
lst=' ';llst=' ';
while ((ch>='0'&&ch<='9')||ch=='.'||ch==':')
{
if (ch>='0'&&ch<='9'&&lst=='0'&&(llst=='.'||llst==':'||llst==' ')) bj=false;
if ((ch=='.'||ch==':'||ch==' ')&&(lst=='.'||lst==':'||lst==' ')) bj=false;
if (ch>='0'&&ch<='9')
{
if (sum<=65535) sum=sum*10+(ch-'0'),res=res*10+(ch-'0');
}
else
{
if (sum>255) bj=false;
sum=0;
if (ch=='.') ++num1;
if (ch==':')
{
++num2;
if (num1!=3) bj=false;
}
}
llst=lst;lst=ch;
ch=getchar();
}
if (sum>65535||!bj||num1!=3||num2!=1||lst==':') printf("ERR\n");
else
{
x=y=0;
if (opt==1)
{
charu(res,i);
if (x==1) printf("OK\n");
else printf("FAIL\n");
}
else
{
chazhao(res);
if (x==1) printf("%d\n",y);
else printf("FAIL\n");
}
}
}
fclose(stdin);fclose(stdout);
return 0;
}
T4 小熊的果篮
Problem
给出一个长度为 \(n\) 的 01 串,每次删除连续的 1 或 0 的第一个 1/0 ,问每次删去的数字的编号。
注意删除数字后存在原本不连续的变得连续。
Solution
考虑将每个块合并,例如样例 2 就可以合并成 4 3 3 2 1 1 2 4。
把各个块存入队列中,每次取出各个块的首位,更新,合并。
考虑时间复杂度,有一种卡到最劣的构造是 1 2 3 4…… \(\sqrt{n}\) 。
易证这种情况下的时间复杂度为 \(\mathcal{O}(n\sqrt{n})\)。
Code
#include<queue>
#include<cstdio>
#define N 200005
using namespace std;
struct node
{
int st,ed,cl;
};
queue<node> q,qq;
int n,num,c[N];
bool b[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",&c[i]);
c[n+1]=1-c[n];
for (int i=2,lst=1;i<=n+1;++i)
if (c[i]!=c[i-1]) q.push((node){lst,i-1,c[i-1]}),lst=i;
num=n;
while (num)
{
while (!q.empty())
{
node x=q.front();
q.pop();
while (b[x.st]&&x.st<=x.ed) x.st++;
if (x.st>x.ed) continue;
printf("%d ",x.st);
--num;b[x.st]=true;
if (x.st==x.ed) continue;
x.st++;
qq.push(x);
}
printf("\n");
while (!qq.empty())
{
node x=qq.front();
qq.pop();
while (!qq.empty())
{
node y=qq.front();
if (x.cl==y.cl)
{
x.ed=y.ed;
qq.pop();
}
else break;
}
q.push(x);
}
}
return 0;
}