一、前言
这场比赛和新年赛时间距离很近。本来我不应该参与这场比赛(周中),但是我直到比赛前 5 5 5 分钟才写完学校作业,抱着最后一搏的心态,我参与了这场比赛......
二、正文
第 A 题 New Year String
观察发现,只有存在 2025 2025 2025 且不存在 2026 2026 2026 时,答案才是正数,否则就是零。
这时,我们将一个 2025 2025 2025 的 5 5 5 改为 6 6 6,此时新串合法,答案为 1 1 1。
对于判断,使用字符串的 s.substr(i,4) 来获得长度为 4 4 4 的子串。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t; cin>>t;
while (t--){
int n; cin>>n;
string s; cin>>s;
int cnt1=0,cnt2=0;
for (int i=0; i<s.size()-3; i++){
if (s.substr(i,4)=="2025") cnt1++;
else if (s.substr(i,4)=="2026") cnt2++;
}
if (cnt1==0||cnt2!=0) cout<<"0\n";
else cout<<"1\n";
}
}
第 B 题 New Year Cake
这道题也非常简单。
考虑枚举一下最小的那层的黑白,然后不停地暴力迭代,每次先判断当前颜色巧克力是否足够,如果足够,翻转颜色,所需大小翻倍,答案加 1 1 1。
取两次答案的最大值即可。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t; cin>>t;
while (t--){
int n,m; cin>>n>>m;
int cnt0=0,cnt1=0;
int x=n,y=m,now=1;
while (true){
if (x<now) break;
cnt0++; x-=now; now*=2;
if (y<now) break;
cnt0++; y-=now; now*=2;
}
x=m,y=n,now=1;
while (true){
if (x<now) break;
cnt1++; x-=now; now*=2;
if (y<now) break;
cnt1++; y-=now; now*=2;
}
cout<<max({cnt0,cnt1})<<"\n";
}
}
第 C 题 Production of Snowmen
假设我们选取 i , j , k i,j,k i,j,k 为三个数组的起点。
那么我们发现只要 a , b a,b a,b 和 b , c b,c b,c 拧完后相等即可。
我们会发现判断是否相等时,反向拧回一定次数也不影响判断。
所以 a a a 不动和 b b b 拧 j − i j-i j−i 次相等, b b b 不动和 c c c 拧 k − j k-j k−j 次相等。
那么我们考虑有多少 j − i j-i j−i 和 k − j k-j k−j 满足条件。那么假设分别有 x , y x,y x,y 种选法,那么 i i i 还有 n n n 种选法,那么答案为 x × y × n x\times y\times n x×y×n。
发现 n ≤ 5000 n\le 5000 n≤5000,暴力枚举判断即可,具体见代码。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
int a[5010],b[5010],c[5010];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t; cin>>t;
while (t--){
int n; cin>>n;
for (int i=1; i<=n; i++) cin>>a[i];
for (int i=1; i<=n; i++) cin>>b[i];
for (int i=1; i<=n; i++) cin>>c[i];
int cnt1=0,cnt2=0;
for (int i=1; i<=n; i++){
bool ok=1;
for (int j=1; j<=n; j++){
ok&=(b[(i+j-2)%n+1]>a[j]);
}
if (ok) cnt1++;
ok=1;
for (int j=1; j<=n; j++){
ok&=(c[(i+j-2)%n+1]>b[j]);
}
if (ok) cnt2++;
}
cout<<n*cnt1*cnt2<<"\n";
}
}
第 D 题 Christmas Tree Decoration
n ≤ 50 n\le 50 n≤50 的限制条件纯属吓人使用,其实人家是 O ( n ) O(n) O(n) 解决的。
那么我们首先发现对于 a 1 , a 2 , . . . , a n a_1,a_2,...,a_n a1,a2,...,an 里面的最大值 m x mx mx,那么这个过程至少持续了 m x − 1 mx-1 mx−1 整轮,还会额外进行几次。
那么剩下的 a 0 a_0 a0 怎么办呢?我们发现 a 0 a_0 a0 是帮助那些不能选的人救命的礼物。如果结束时救命药够用,那么我们一定能够合法地取完这些礼物。
那么我们首先计算 ∑ i = 1 n max ( 0 , m x − 1 − a i ) \sum_{i=1}^{n}\max(0,mx-1-a_i) ∑i=1nmax(0,mx−1−ai) 作为最后一轮前救命药的使用次数。
假设剩余了 x x x 个救命药,那么最后一轮的限制就是在最后一个 m x mx mx 取完前,不能超过 x x x 个数不是 m x mx mx。
考虑最后一个 m x mx mx 前有 i i i 个不是 m x mx mx,假设 m x mx mx 有 u n u unu unu 个,其余有 u s e use use 个,那么我们的方案数为:
C u s e i × u n u × ( u n u − 1 + i ) ! × ( u s e − 1 ) ! C_{use}^{i}\times unu\times (unu-1+i)!\times(use-1)! Cusei×unu×(unu−1+i)!×(use−1)!
求和即可,对于阶乘和组合数,预处理即可。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define M 998244353
int a[60],C[60][60],inv[60];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t; cin>>t; inv[0]=1;
for (int i=1; i<=50; i++) inv[i]=inv[i-1]*i%M;
C[0][0]=1;
for (int i=1; i<=50; i++){
C[i][0]=C[i][i]=1;
for (int j=1; j<i; j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%M;
}
while (t--){
int n; cin>>n;
for (int i=0; i<=n; i++) cin>>a[i];
int mx=0,cnt=0,use=0,unu=0;
for (int i=1; i<=n; i++){
mx=max(mx,a[i]);
}
for (int i=1; i<=n; i++){
cnt+=max(0ll,mx-a[i]-1);
if (mx==a[i]) unu++;
else use++;
}
a[0]-=cnt; int ans=0;
for (int i=0; i<=min(use,a[0]); i++){
ans+=C[use][i]*unu%M*inv[unu-1+i]%M*inv[use-i]%M;
}
cout<<ans%M<<"\n";
}
}
第 E 题 New Year's Gifts
纯属吓人。
考虑将手中的钱减少 ∑ y \sum y ∑y,那么问题转化为用 x x x 的盒子和 z − y z-y z−y 元钱可以使得一个人开心,那么最多让多少个人开心。
那么我们先让人的 x x x 和 盒子的美丽度排序,然后双指针动态插入这个盒子能够打动的所有人,然后贪心地删除掉 z − y z-y z−y 最大的那个人。
最后考虑每次访问 z − y z-y z−y 最小的那个人,如果钱够用,那么打动那个人并将他删除。我们可以使用 set 来维护这个过程。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define M 998244353
struct node{int x,y,z;}a[200010];
bool cmp(node a,node b){
return a.x<b.x;
}
int b[200010];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int t; cin>>t;
while (t--){
int n,m,k; cin>>n>>m>>k;
multiset <int> st;
for (int i=1; i<=m; i++) cin>>b[i];
sort(b+1,b+m+1);
for (int i=1; i<=n; i++){
cin>>a[i].x>>a[i].y>>a[i].z;
k-=a[i].y;
}
sort(a+1,a+n+1,cmp); int id=1;
for (int i=1; i<=m; i++){
while (id<=n&&a[id].x<=b[i]) st.insert(a[id].z-a[id].y),id++;
if (st.size()){
st.erase(st.find(*(st.rbegin())));
}
}
while (id<=n) st.insert(a[id].z-a[id].y),id++;
//cout<<k<<" "<<st.size()<<" ";
while (st.size()&&k>=(*(st.begin()))){
k-=(*(st.begin()));
st.erase(st.begin());
}
cout<<n-st.size()<<"\n";
}
}
第 F1F2 题 Christmas Reindeer
这两道题目其实并不是非常难,我们直接考虑 F2。
第一步,我们观察发现,如果将所有的鹿按照力量从大到小排序并按照这个顺序选择鹿,那么鹿的力量是单调递减的。
那么我们考虑是否选择一只最终贡献是 2 i 2^i 2i 的鹿,从大到小考虑,为了使总力量大于 x x x,情况分两种:
如果 x x x 有 2 i 2^i 2i 这一位
那么我们只能选择这一位,假设前面有 a a a 只鹿,那么初始力量为 2 a + i 2^{a+i} 2a+i 的鹿将减少一只。
如果 x x x 没有这一位
那么我们如果能够选择 2 i 2^i 2i 这一位,那么一定是总力量 > x >x >x 的合法方案。
考虑每种鹿还剩 n − x n-x n−x 只,那么这一种鹿的贡献就是 ∑ i = 0 n − x C c n t i \sum_{i=0}^{n-x}C_{cnt}^{i} ∑i=0n−xCcnti,其中 c n t cnt cnt 是鹿的总数量。
那么这个东西可以预处理一下(容斥法),其中 x x x 一定是不超过 60 60 60 的(因为只有这么多位)
我们还可以不选这一位,那么直接进入下一位考虑。
那么如何动态维护方案,每一次相当于修改一位(赋值)并且更新乘积。那么我们直接求逆元是会 TLE 的。我给出的方案是线段树维护(因为线段树可以直接赋值)。
注意恰好为 x x x 的情况。
代码:
cpp
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define ls (id<<1)
#define rs (id<<1|1)
#define mid (l+r>>1)
#define M 998244353
#define K 1000000000
int cnt[70],res,tmp[70],cnt2[70];
int C[600010][65];
int Pow(int a,int n){
int ans=1;
while (n){
if (n&1) ans=ans*a%M;
a=a*a%M; n>>=1;
}
return ans;
}
int num[260];
void update(int id,int l,int r,int qid,int val){
if (l==r){num[id]=val; return ;}
if (qid<=mid) update(ls,l,mid,qid,val);
else update(rs,mid+1,r,qid,val);
num[id]=num[ls]*num[rs]%M;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0); cout.tie(0);
int n,m; cin>>n>>m;
C[0][0]=1;
for (int i=1; i<=n+m; i++){
C[i][0]=1;
if (i<=61) C[i][i]=1;
for (int j=1; j<min(i,62ll); j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%M;
}
for (int i=1; i<=n; i++){
int x; cin>>x; cnt[x]++;
}
for (int i=0; i<=n+m; i++){
int tmp=Pow(2,i);
for (int j=0; j<=min(j,61ll); j++){
C[i][j]+=K*tmp;
tmp=(tmp+M-C[i][j]%K)%M;
}
}
for (int i=1; i<=m; i++){
int op,x; cin>>op;
if (op==1) cin>>x,cnt[x]++;
if (op==2) cin>>x,cnt[x]--;
if (op==3){
int x; cin>>x;
for (int i=0; i<=60; i++){
update(1,0,60,i,C[cnt[i]][0]/K); cnt2[i]=cnt[i];
}
int now=0,ans=0,err=1;
for (int i=60; i>=0; i--){
if (x&(1ll<<i)){
if (!cnt2[i+now]) {err=0; break;}
cnt2[i+now]--;
update(1,0,60,i+now,C[cnt[i+now]][cnt[i+now]-cnt2[i+now]]/K);
now++;
}
else{
if (cnt2[i+now]){
cnt2[i+now]--;
update(1,0,60,i+now,C[cnt[i+now]][cnt[i+now]-cnt2[i+now]]/K);
ans=(ans+num[1])%M;
cnt2[i+now]++;
update(1,0,60,i+now,C[cnt[i+now]][cnt[i+now]-cnt2[i+now]]/K);
}
update(1,0,60,i+now,C[cnt[i+now]][cnt[i+now]-cnt2[i+now]]%K);
}
}
cout<<(ans+err*num[1])%M<<"\n";
}
}
}
二、后记
上金成功了,真是以外之喜啊!
至此,2025 年双金目标在最后几天里实现了。