第八届河北省程序设计竞赛ACGIJK
第八届河北省程序设计竞赛ACGIJK
推荐 jiangly 大佬的 vp 视频:https://www.bilibili.com/video/BV1qw4m1i7iB/?spm_id_from=333.999.0.0&vd_source=64620ace556af2e4cced07a435e06786
个人难度排序 KA I GC J
Problem K. Welcome
题面
第八届河北省大学生程序设计竞赛成功举办,现在请你输出本场比赛的简称(HBCPC2024)
Input
无
Output
输出本场比赛的简称 HBCPC2024
分析
让 jiangly 都无语的题
代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int mod=1e6+3;
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
cout<<"HBCPC2024";
}
Problem A. Update
题面
全局替换功能是一个十分好用的功能。使用这个功能,你可以轻松地在编辑器中对字符串进行修改。
Iris 是一只可爱的猫猫,她用爪子在 oql 的键盘上胡乱敲了若干次,刚好形成了一个仅由小写字母组成
的字符串。
oql 特别喜欢 i 这个字母,他想把这个字符串中的所有字母都变成 i,并准备进行如下操作若干次(也
有可能一次都不用操作):
• 选择两个a~
z的字母 x, y,将字符串中所有的 x 全部同时替换成 y。
oql 想知道,至少要用多少次操作才能把所有字符都变成 i。
Input
一行一个仅由小写字母组成的字符串s (1 ≤|s|≤10e4)。
分析
答案即除了i,哪些字母出现过,st 记录一下已出现的,代表已经加过
代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int mod=1e6+3;
int st[30];
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
string a;cin>>a;
ll res=0;
for(int i=0;i<a.size();++i)
{
if(a[i]!='i')
{
if(!st[a[i]-'a'])res++,st[a[i]-'a']=1;
}
}
cout<<res;
}
Problem I. Subnet
题面
Iris 刚刚学习了关于IPv4 子网的知识。
现在,给定一个子网和若干 IP 地址,请你帮 Iris 判断每一个地址是否属于这个子网。
IPv4 地址是一个32 位的二进制数,通常由四个八位组组成,每个八位组可以表示的最大数值是
255(即二进制的 11111111)。点分十进制(Dotted Decimal Notation)是表示 IP 地址的一种格式,
它将 IP 地址的四个八位组(octets)用点(.)分隔开来。每个八位组是一个介于0 到255 之间的十进
制数,因此称为点分十进制。
一个IP 地址可以通过子网掩码分为网络号和主机号两部分,网络号相同的 IP 地址属于同一个子网。子
网掩码是一个32 位的二进制数,由连续的若干个1 紧跟着若干个0 组成,其中的 1 表示网络位,0 表
示主机位。例如:255.255.255.0 (11111111 11111111 11111111 00000000) 表示前24 位为网络号,
后8 位为主机号。
CIDR(无类别域间路由,Classless Inter-Domain Routing)表示法是一种简化和扩展 IP 地址表示的方
法,它允许更灵活地定义和分配 IP 地址以及它们的子网。CIDR 表示法通过在 IP 地址后面附加一个斜
线 ’/’ 和一个在 0 到32 之间(含0 和32 )的数字来表示网络号的长度,这个数字表示子网掩码中连
续的前缀1 的位数。
Input
第一行,一个以CIDR 格式表示的子网。
第二行,一个整数 n (1 ≤n ≤1000),为需要判断的 IP 地址个数。
接下来n 行,每行一个以点分十进制表示的 IP 地址。

分析
首先吐槽一下,因为第一页只有样例,解释在最下面,导致瞪眼看了半天也没看出来啥意思,这里搬到上面去了wwww~
首先就是很粗暴的记录一下net串的 4 个地址块和 / 后的数字net_len,几乎不用考虑方式,写完输出一致即可
Net_len并不一定是整除8的,先算完整块 com ,再算剩下的last。
接下来枚举IP串,同样的道理记录IP串的 4 个地址块,然后先比较完整块是否一致,再比较剩下的,
位运算解决即可,值得一提的是 last 是向左的 last 真正块的剩余应该是 8-last。
代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int mod=1e6+3;
int zi_num[5],cnt;
int ip_num[5],cnt2;
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
string zi;cin>>zi;
int net_len=0;
//记录一下母串的 4 个地址块和 / 后的数字
for(int i=0;i<zi.size();++i)
{
if(zi[i]=='.')cnt++;
else if(zi[i]=='/')
{
for(int j=i+1;j<zi.size();++j)
{
net_len=net_len*10+zi[j]-'0';
}
break;
}
else zi_num[cnt]=zi_num[cnt]*10+zi[i]-'0';
}
int n;cin>>n;
//先算完全的 com ,再算剩下的last。
int last=net_len%8,com=net_len/8;
while(n--)
{
cnt=0;cnt2=0;
memset(ip_num,0,sizeof ip_num);
string ip;cin>>ip;
//记录IP串的 4 个地址块
for(int i=0;i<ip.size();++i)
{
if(ip[i]=='.')cnt2++;
else ip_num[cnt2]=ip_num[cnt2]*10+ip[i]-'0';
}
bool flag=true;
//先比较完整块是否一致
for(int i=0;i<com;++i)
{
if(ip_num[i]!=zi_num[i])
{
flag=false;
break;
}
}
//再比较剩下的
if(flag&&last)
{
for(int i=8;i>=(8-last);--i)
{
if((ip_num[com]>>i)!=(zi_num[com]>>i))
{
flag=false;
break;
}
}
}
if(flag)cout<<"YES\n";
else cout<<"NO\n";
}
}
Problem C. Goose Goose Duck
题面
Iris 有n 个喜欢玩鹅鸭杀的朋友,编号为1∼n。
假期的时候,大家经常会在群里问有没有人玩鹅鸭杀,并且报出现在已经参与的人数。
但是每个人对于当前是否加入游戏都有自己的想法。
具体的来说,对于第 i 个人,如果当前已经加入游戏的人数处于区间 [li, ri] 之间,那ta 就会愿意加入游
戏。
你认为参与游戏的人越多,游戏将会越有趣,所以你决定给大家安排一个加入顺序,使得加入游戏的人
数最多。
Input
第一行,一个整数 n (1 ≤n ≤106),表示总人数。
接下来i 行,每行为两个由空格分隔的整数 li, ri (0 ≤li, ri ≤106),含义见题目描述。
分析
这道题赛时以为是简单的贪心排序,所以没搞出来,实际上这题关键就是走的总步数 res有关,而和本身l,r的位置无关,所以排序必然是错解。所以我们应该匹配每次 res的情况,使其+1,如果不能+1,则结束,那么如何匹配?首先满足区间中的l<=res,如果 res+1.这些 l依然<=res+1,可以延续下去,再在这些满足的区间中找一个r大于等于 res 的最小值,如果r 小于 res 自不必说不符合+1 条件,最小值r1是因为如果用更大的r2,res+1后可能淘汰r1而r2已经被用,所以只能用 r3,r4......,但是如果用的是 r1,res+1后就可以用r2,r3,r4,所以r1是属于不用白不用的状况,那我们自然要贪心的用掉它。
这里是因为看了 jiangly 大佬的视频,干脆直接学习一下优雅的写法,不过内嵌比较函数还是懒得学还是用了自己习惯的写法。
代码
#include<bits/stdc++.h>
using namespace std;
//#define int long long
const int N=1e6+10;
int l[N],r[N];
bool cmp(int a,int b)
{
return l[a]<l[b];
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int n;cin>>n;
for(int i=0;i<n;++i)cin>>l[i]>>r[i];
vector<int>p(n);
vector<int>pl;
iota(p.begin(),p.end(),0);
//iota算法用于填充一个区间,以递增的方式给每个元素赋予一个值。
sort(p.begin(),p.end(),cmp);
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<>>q;
int i=0,res=0;
while(true){
while(i<n&&res>=l[p[i]])
{
q.emplace(r[p[i]],p[i]);
i++;
}
while(!q.empty()&&q.top().first<res)q.pop();
if(q.empty())break;//没有一个条件r/l可以使res+1
pl.emplace_back(q.top().second);
res++;
q.pop();
}
cout<<res<<'\n';
for(auto j:pl)cout<<j+1<<' ';
}
Problem G. Bracelet
题面
oql 有若干个形如 00,01,11 的珠子,喜欢做手工的 oql 希望把他们串成一个手链,他精心设计了手链
的样式并绘出了图纸。
手链的样式可以抽象为一个由 0 和1 组成的字符串S,且首尾相接。
但是此时路过了一只叫Iris 的猫猫,现在三种珠子分别只剩下n, m, k 个了。
oql 很伤心,但是还是决定努力完成手链,至少完成手链的连续一部分,他想知道自己现在能完成的最
长连续部分的长度是多少。
显然,你不应该破坏珠子。不过,你可以翻转珠子,对于00 和11,翻转之后没有变化,对于01,翻转
之后变为10。
Input
第一行三个由空格分隔的整数 n, m, k (0 ≤n, m, k ≤106),分别表示 00 的数量,01 的数量,11 的数
量。
一个仅包含0 和1 的字符串S (1 ≤|S|≤106),表示手链的样式。
分析
其实不难看出就是普通的环处理方法(将原序列复制一份放在后面)加上双指针,然后以 0 起点跑一遍,1 起点跑一遍。但是为什么我觉得难嘞,就是因为会在很多小东西上 wa 掉。
先说一下我赛时的写法,因为我看这个 00,01,11不顺眼所以我都合并成了一个数0,1,2,后来我发现没必要,用写这个地方也就没几处,其实代码出错很大都是因为复杂冗杂,应该提高代码的精炼度
我们来看一下 jiangly 大佬的代码来分析
代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int orig[3]{};
//首先就是 n,m,k的存储方面,因为后面要实时的调整和回复
//所以直接用数组存一个初始,而不是定义一个 n,m,k读入
for(int i=0;i<3;++i)cin>>orig[i];
string a;cin>>a;
//自不必多说,写一个 n 肯定比写一堆好
const int n=a.size();
//环的基本处理
a+=a;
int res=0;
for(int s=0;s<2;++s)//用 for来枚举 0,1 起点,而我是写了两个,不仅冗长而且不好debug
{
int cur[3]{orig[0],orig[1],orig[2]};//定义实时变化记录剩余可以使用的 00,01,11
for(int i=s,j=s;i<n;ii=2)//双指针
{
while(j+2<=n+i && cur[a[j]-'0'+a[j+1]-'0'])//这里的 j 指向的是满足的后一位
{
cur[a[j]-'0'+a[j+1]-'0']--;
j+=2;
}
res=max(res,j-i);
cur[a[i]-'0'+a[i+1]-'0']++;
}
}
cout<<res;
}
Problem J. Iris’ Food
题面
猫猫 Iris 又在玩 oql 的键盘了。
在 T 天的时间里,每一天 Iris 会在玩 oql 键盘的时候敲下若干个字符,而这些字符恰好全都是0∼9 这
10 个阿拉伯数字。经过统计,数字 i 有ai 个。
oql 每天可能会给 Iris 喂一定数量的猫粮。他决定,在这 9
i=0 ai 个数字里选择 m 个,经过重新排列后
形成一个十进制下m 位、且不包含前导 0 的非负整数 x,然后oql 将在这天给 Iris 投喂 x 克的猫粮。
然而,Iris 还是一只小猫,不宜食用太多的猫粮。因此 oql 想让这个数字尽可能小。请你帮助Iris 计算
出她每天能得到多少猫粮。
由于答案可能很大,你只需要输出答案对 109 + 7 取模的结果。请注意,你需要输出的是最小答案模
109 + 7 后的结果,而不是x mod 109 + 7 的最小值。
Input
第一行一个正整数 T (1 ≤T ≤104),表示天数。
第 2∼(T + 1) 行,每行 11 个由空格分隔的非负整数 m, a0, a1,···, a9 (1 ≤m ≤109
, 0 ≤ai ≤109),表示第 i 天的情况。
数据保证有解,即至少能形成一个m 位不包含前导 0 的非负整数。

分析
先找到最小的非 0 数当做第一位,然后由小到大依次放
这道题难的就是每个数都有 1e9 这样的恐怖数据,每次放入数据就是 res*10+w,我们要想一个方式去优化时间,是递推的话有一个非常常见的优化思路:矩阵快速幂。
我们只需对于每个数字,把这个数字的个数和需要的个数取个 min,然后矩阵相乘,比如 res111,就是算res * 矩阵的三次方
只要有使用矩阵这个思路这题基本就解决了,因为这题的矩阵定义很简单,观察式子,res和 10 相乘 和w 相加
所以 初始矩阵o就需要 res和1 也就是
Res 1
0 0
变化矩阵t为
10 0
w 1
这样相乘就是
Res*10+w 1
0 0
每次不同数字改变初始矩阵的w 即可
代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int mod=1e9+7;
int cnt[20];
void muti(int c[][2],int a[][2],int b[][2])
{
int tmp[2][2]={0};
for(int i=0;i<2;++i)
for(int j=0;j<2;++j)
for(int k=0;k<2;++k)
tmp[i][j]=(tmp[i][j]+a[i][k]*b[k][j])%mod;
memcpy(c,tmp,sizeof tmp);
}
void qmi(int c[][2],int i,int k)
{
int res[][2]={ {1,0},
{0,1}
};
int t[][2]={
{10,0},
{i,1}
};
while(k)
{
if(k&1)muti(res,res,t);
muti(t,t,t);
k>>=1;
}
memcpy(c,res,sizeof res);
}
void solve(){
int m;cin>>m;
for(int i=0;i<10;++i)cin>>cnt[i];
int st=0;
if(m==1&&cnt[0])
{
cout<<0<<'\n';
return;
}
for(int i=1;i<10;++i)
{
if(cnt[i])
{
st=i,cnt[i]--,m--;
break;
}
}
int o[][2]={
{st,1},
{0,0}
};
for(int i=0;i<10;++i)
{
if(!m)break;
if(cnt[i])
{
int bit[][2]={
{1,0},
{0,1}
};
int num=min(cnt[i],m);
qmi(bit,i,num);
muti(o,o,bit);
m-=num;
}
}
cout<<o[0][0]<<'\n';
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);
int _;cin>>_;
while(_--)solve();
}

浙公网安备 33010602011771号