【比赛记录】2025CSP-S模拟赛5

A. 十年之约
发现 \(f(n)\) 很小,考虑计算每个值 \(x\) 是多少个数的 \(f\) 值。显然要求 \(f(i)=x\),必定满足 \(\operatorname{lcm}[1,x-1]\mid i\land\operatorname{lcm}[1,x]\nmid i\)。因此贡献就是 \(\lfloor\frac{n}{\operatorname{lcm}[1,x-1]}\rfloor-\lfloor\frac{n}{\operatorname{lcm}[1,x]}\rfloor\)。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define gcd __gcd
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int mod=1e9+7;
int T,n;
il int lcm(int x,int y){
return x/gcd(x,y)*y;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
cin>>n;
int ans=0,cur=1,cnt=0;
for(int i=2;;i++){
int tmp=lcm(cur,i);
int num=n/cur-n/tmp;
cur=tmp,cnt+=num,ans+=num*i;
if(cnt==n){
break;
}
}
cout<<ans%mod<<"\n";
}
return 0;
}
}
signed main(){return asbt::main();}
B. 可爱三小只
考虑操作是不用考虑顺序的,行列交叉产生的贡献是好计算的,因此把行和列分开计算。那么就是一个简单的贪心了,可以用堆来做。
然后将行和列合并,考虑枚举有多少次行操作。假设有 \(i\) 次,那么行列交叉就会使答案变小 \(i\times(k-i)\)。时间复杂度 \(O((nm+k)\log nm)\)。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e3+5,maxm=1e6+5;
int n,m,k,p,a[maxn][maxn];
int f[maxm],g[maxm];
priority_queue<int> q;
il void calc(int p,int *f){
for(int i=1;i<=k;i++){
int tmp=q.top();
q.pop();
f[i]=f[i-1]+tmp;
q.push(tmp-p);
}
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>k>>p;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
for(int i=1;i<=n;i++){
int tmp=0;
for(int j=1;j<=m;j++){
tmp+=a[i][j];
}
q.push(tmp);
}
calc(p*m,f);
while(q.size()){
q.pop();
}
for(int j=1;j<=m;j++){
int tmp=0;
for(int i=1;i<=n;i++){
tmp+=a[i][j];
}
q.push(tmp);
}
calc(p*n,g);
int ans=-1e18;
for(int i=0;i<=k;i++){
ans=max(ans,f[i]+g[k-i]-p*i*(k-i));
}
cout<<ans;
return 0;
}
}
signed main(){return asbt::main();}
C. 蛋糕塌了
序列排序后对答案没有影响。
考虑排序后的序列 \(a\),对于两个数 \(a_i\) 和 \(a_j\)(\(i<j\)),它们产生的贡献为 \(\frac{a_i\times a_j}{2^{j-i+1}}\)。考虑一个分治位置 \(mid\in[i,j)\),可以将这个式子拆成 \(\frac{a_i}{2^{mid-i+1}}\times\frac{a_j}{2^{j-mid}}\)。那么我们将这个东西放在线段树上维护,对于每个区间维护答案和两个类似前后缀的东西(分别是 \(\frac{a_l}{2}+\frac{a_{l+1}}{2^2}+\dots+\frac{a_r}{2^{r-l+1}}\) 和 \(\frac{a_r}{2}+\frac{a_{r-1}}{2^2}+\dots+\frac{a_l}{2^{r-l+1}}\))。这个区间的答案就是两个分治区间的答案加上两个区间合并造成的贡献。需要离散化,而且相同的数得离散到不同的位置。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define lwrb lower_bound
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=3e5+5,mod=1e9+7;
int n,m,a[maxn],inv[maxn];
int lsh[maxn<<1],cnt[maxn<<1];
il int qpow(int x,int y){
int res=1;
while(y){
if(y&1){
(res*=x)%=mod;
}
(x*=x)%=mod,y>>=1;
}
return res;
}
struct{
int p,x;
}b[maxn];
struct node{
int len,sum,pre,suf;
node(){
len=sum=pre=suf=0;
}
node(int x){
len=1,sum=0;
pre=suf=x*inv[1]%mod;
}
il node operator+(const node &x)const{
node res;
res.len=len+x.len;
res.sum=(sum+x.sum+suf*x.pre)%mod;
res.pre=(pre+x.pre*inv[len])%mod;
res.suf=(suf*inv[x.len]+x.suf)%mod;
return res;
}
}tr[maxn<<3];
il void pushup(int id){
tr[id]=tr[lid]+tr[rid];
}
il void insert(int id,int l,int r,int x){
if(l==r){
tr[id]=node(lsh[x]);
return ;
}
int mid=(l+r)>>1;
if(x<=mid){
insert(lid,l,mid,x);
}
else{
insert(rid,mid+1,r,x);
}
pushup(id);
}
il void erase(int id,int l,int r,int x){
if(l==r){
tr[id]=node();
return ;
}
int mid=(l+r)>>1;
if(x<=mid){
erase(lid,l,mid,x);
}
else{
erase(rid,mid+1,r,x);
}
pushup(id);
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
int tot=0;
for(int i=1;i<=n;i++){
cin>>a[i];
lsh[++tot]=a[i];
}
cin>>m;
for(int i=1;i<=m;i++){
cin>>b[i].p>>b[i].x;
lsh[++tot]=b[i].x;
}
sort(lsh+1,lsh+tot+1);
for(int i=1;i<=n;i++){
a[i]=lwrb(lsh+1,lsh+tot+1,a[i])-lsh;
cnt[a[i]]++;
a[i]+=cnt[a[i]]-1;
}
for(int i=1;i<=m;i++){
int &tmp=b[i].x;
tmp=lwrb(lsh+1,lsh+tot+1,tmp)-lsh;
cnt[tmp]++;
tmp+=cnt[tmp]-1;
}
inv[n]=qpow(qpow(2,n),mod-2);
for(int i=n;i;i--){
inv[i-1]=(inv[i]+inv[i])%mod;
}
for(int i=1;i<=n;i++){
insert(1,1,tot,a[i]);
}
cout<<tr[1].sum<<"\n";
for(int i=1;i<=m;i++){
int p=b[i].p,x=b[i].x;
erase(1,1,tot,a[p]);
a[p]=x;
insert(1,1,tot,a[p]);
cout<<tr[1].sum<<"\n";
}
return 0;
}
}
signed main(){return asbt::main();}
D. 西安行
首先将题意转化成一个计数问题:
有 \(n\) 个格子,除了 \(a_i,i\in[1,m]\) 这 \(m\) 个格子之外,在每个格子后都可以放置一个隔板。规定第 \(1\) 个格子前和第 \(n\) 个格子后一定有隔板。每两个相邻的格子之间要放一黑一白两个小球,位置可以重合。求放置的方案数。
那么我们就可以设计 DP:设 \(dp_{i,0/1/2}\) 表示前 \(i\) 个格子,最后一段放了 \(0/1/2\) 个球的方案数。那么对于可放置或不可放置的格子 \(i\),可以设计出刷表的转移式:
-
可放置
-
\(dp_{i+1,0}=dp_{i,0}+dp_{i,2}\)
-
\(dp_{i+1,1}=2dp_{i,0}+dp_{i,1}+2dp_{i,2}\)
-
\(dp_{i+1,2}=dp_{i,0}+dp_{i,1}+2dp_{i,2}\)
-
-
不可放置
-
\(dp_{i+1,0}=dp_{i,0}\)
-
\(dp_{i+1,1}=2dp_{i,0}+dp_{i,1}\)
-
\(dp_{i+1,2}=dp_{i,0}+dp_{i,1}+dp_{i,2}\)
-
于是可以设计出转移矩阵,进行矩阵快速幂。时间复杂度 \(O(27m\log n)\)。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5,mod=1e9+7;
int n,m,a[maxn];
struct juz{
int mat[3][3];
juz(){
memset(mat,0,sizeof mat);
}
il int*operator[](int x){
return mat[x];
}
il juz operator*(juz x)const{
juz res;
for(int i=0;i<=2;i++){
for(int j=0;j<=2;j++){
for(int k=0;k<=2;k++){
(res[i][j]+=mat[i][k]*x[k][j])%=mod;
}
}
}
return res;
}
}bas0,bas1,ans;
il juz qpow(juz x,int y){
juz res;
res[0][0]=res[1][1]=res[2][2]=1;
while(y){
if(y&1){
res=res*x;
}
x=x*x,y>>=1;
}
return res;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a[i];
}
ans[0][0]=1;
bas0[0][0]=bas0[0][2]=bas0[1][1]=bas0[1][2]=bas0[2][0]=1;
bas0[0][1]=bas0[2][1]=bas0[2][2]=2;
bas1[0][0]=bas1[0][2]=bas1[1][1]=bas1[1][2]=bas1[2][2]=1;
bas1[0][1]=2;
a[0]=-1;
for(int i=1;i<=m;i++){
ans=ans*qpow(bas0,a[i]-a[i-1]-1)*bas1;
}
cout<<(ans*qpow(bas0,n-a[m]-1))[0][2];
return 0;
}
}
signed main(){return asbt::main();}

浙公网安备 33010602011771号