【比赛记录】2025CSP+NOIP 冲刺模拟赛合集Ⅲ
2025CSP-S模拟赛67(HZOJ CSP-S模拟42)
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 60(70) | 25 | 30 | 5 | 120 | 5/14(7/34) |
A. 乘筛积
对于单次查询,我们可以直接枚举 \(x\) 算出对应的 \(y\) 贡献答案,时间复杂度 \(O(\frac{C}{\max(p,q)})\)。根号分治即可。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=3e5+5,mod=998244353;
int n,m,kk,T,a[maxn],b[maxn],f[550][550];
il int solve(int p,int q,int *a,int *b,int n,int m){
if(p<q){
swap(p,q),swap(a,b),swap(n,m);
}
int ans=0;
for(int i=1;i<=n&&i*p<=kk;i++){
if((kk-i*p)%q==0){
int j=(kk-i*p)/q;
if(j>0&&j<=m){
ans=(ans+a[i]*b[j])%mod;
}
}
}
return ans;
}
int main(){
freopen("sedge.in","r",stdin);
freopen("sedge.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>kk;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=m;i++){
cin>>b[i];
}
for(int i=1;i<=547;i++){
for(int j=1;j<=547;j++){
f[i][j]=solve(i,j,a,b,n,m);
}
}
cin>>T;
while(T--){
int p,q;
cin>>p>>q;
if(max(p,q)<=547){
cout<<f[p][q]<<'\n';
}else{
cout<<solve(p,q,a,b,n,m)<<'\n';
}
}
return 0;
}
}
signed main(){return asbt::main();}
B. 放进去
首先对于每个奢侈品单独考虑,不妨令 \(a_{i,p_1}\le a_{i,p_2}\le a_{i,p_3}\le\dots\le a_{i,p_m}\)。假设我们最终选的店铺集合是 \(S\),那么对于 \(i\) 我们必然选择 \(S\) 中 \(a\) 最小(也就是最靠前)的 \(p_j\)。考虑差分,即对于所有 \(k<j\),给答案加上 \(a_{i,p_{k+1}}-a_{i,p_k}\)。考虑 SOSDP,于是我们只需要给所有 \(\{p_1,p_2,\dots,p_k\}\) 的答案加上 \(a_{i,p_{k+1}}-a_{i,p_k}\),最后再做一遍高维前缀和即可。记这个和为 \(f_S\),\(\sum_{i\in S}b_i=g_S\),于是 \(S\) 的答案即为 \(g_S+f_{\complement_US}\)。时间复杂度 \(O(nm\log m+m2^m)\),轻微卡常。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=(1<<25)+5,inf=1e18;
il int pls(int x,int y){
return x+y<inf?x+y:inf;
}
il void add(int &x,int y){
x=pls(x,y);
}
int n,m,b[30],f[maxn],g[maxn];
struct node{
int p,v;
il bool operator<(const node &x)const{
return v<x.v;
}
}a[30];
int main(){
freopen("putin.in","r",stdin);
freopen("putin.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[j].v;
a[j].p=j;
}
sort(a+1,a+m+1);
a[m+1]={m+1,inf};
for(int j=1,S=0;j<=m+1;j++){
// cout<<a[j].p<<' '<<a[j].v<<'\n';
add(f[S],a[j].v-a[j-1].v);
S|=1<<(a[j].p-1);
}
}
for(int i=1;i<=m;i++){
for(int S=0;S<1<<m;S++){
if(S>>(i-1)&1){
continue;
}
add(f[S|1<<(i-1)],f[S]);
}
}
for(int i=1;i<=m;i++){
cin>>b[i];
}
int ans=inf;
for(int S=0;S<1<<m;S++){
g[S]=pls(g[S^(S&-S)],b[__lg(S&-S)+1]);
ans=min(ans,g[S]+f[((1<<m)-1)^S]);
}
cout<<ans;
return 0;
}
}
signed main(){return asbt::main();}
C. 最长路径
重要结论:最长路径上相邻的点必然满足 \(|x_i-x_j|=1\lor|y_i-y_j|=1\)。显然成立。
考虑 DP。设 \(f_{i,j}\) 表示以 \((i,j)\) 为结尾的最长路径长度,\(g_{i,j}\) 表示以 \((i,j)\) 结尾的最长路径长度的数量,显然只会从左上方的一个 _| 形区域转移过来。如果我们能求出它的范围,就可以用单调队列优化了。
我们要求的就是 \(i\) 上一行的最左端的转移点,和前一列最上面的转移点,这里以最左端转移点为例。此时对于所有点 \(([r_1+1,r_2],[c_1+1,c_2])\),它们对应的转移点都应为 \(c_1\)。显然不能直接全部赋值,考虑只给 \(([r_1+1,r_2],c_2)\) 赋值,最后再做一遍后缀取 \(\min\)。时间仍然不正确,考虑扫描线,将所有矩形按 \(c_1\) 排序,给每一列开一个并查集,将已经赋过值的合并起来,这样在未来赋值时可以直接跳过这一段。每个位置最多被赋值一次,于是时间复杂度正确。
然后就是单队 DP 了。我们对于每一行和每一列都维护单调队列,再给每一行和每一列都开一个桶维护队列中 \(f\) 值为 \(x\) 的 \(g\) 的总和即可。注意 \((i-1,j-1)\) 的贡献可能被计算两遍,减掉就好了。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e3+5,maxm=5e5+5,mod=1e9+7;
il int pls(int x,int y){
return x+y<mod?x+y:x+y-mod;
}
il void add(int &x,int y){
x=pls(x,y);
}
il int mns(int x,int y){
return x<y?x-y+mod:x-y;
}
il void sub(int &x,int y){
x=mns(x,y);
}
il void chkmin(int &x,int y){
x=min(x,y);
}
il void chkmax(int &x,int y){
x=max(x,y);
}
int T,n,m,q,f[maxn][maxn],g[maxn][maxn];
int b1[maxn][maxn],b2[maxn][maxn];
int q1[maxn][maxn],hd1[maxn],tl1[maxn];
int q2[maxn][maxn],hd2[maxn],tl2[maxn];
int s1[maxn][maxn],s2[maxn][maxn];
struct jux{
int x1,x2,y1,y2;
}a[maxm];
struct{
int fa[maxn];
il void init(int n){
for(int i=1;i<=n;i++){
fa[i]=i;
}
}
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
fa[find(u)]=find(v);
}
}d[maxn];
il void solve(){
cin>>n>>m>>q;
for(int i=1;i<=q;i++){
cin>>a[i].x1>>a[i].y1>>a[i].x2>>a[i].y2;
}
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
b1[i][j]=j,b2[i][j]=i;
}
}
sort(a+1,a+q+1,[](jux x,jux y){return x.y1<y.y1;});
for(int i=1;i<=m;i++){
d[i].init(n+1);
}
for(int i=1;i<=q;i++){
int x1=a[i].x1,x2=a[i].x2,y1=a[i].y1,y2=a[i].y2;
for(int j=d[y2].find(x1+1);j<=x2;j=d[y2].find(j)){
chkmin(b1[j][y2],y1);
d[y2].merge(j,j+1);
}
}
// puts("***********************************************************");
// for(int i=1;i<=n;i++){
// for(int j=1;j<=m;j++){
// cout<<b1[i][j]<<' ';
// }
// cout<<'\n';
// }
// puts("***********************************************************");
sort(a+1,a+q+1,[](jux x,jux y){return x.x1<y.x1;});
for(int i=1;i<=n;i++){
d[i].init(m+1);
}
for(int i=1;i<=q;i++){
int x1=a[i].x1,x2=a[i].x2,y1=a[i].y1,y2=a[i].y2;
for(int j=d[x2].find(y1+1);j<=y2;j=d[x2].find(j)){
chkmin(b2[x2][j],x1);
d[x2].merge(j,j+1);
}
}
for(int i=n;i;i--){
for(int j=m;j;j--){
if(j<m){
chkmin(b1[i][j],b1[i][j+1]);
}
if(i<n){
chkmin(b2[i][j],b2[i+1][j]);
}
}
}
// puts("***********************************************************");
// for(int i=1;i<=n;i++){
// for(int j=1;j<=m;j++){
// cout<<b1[i][j]<<' ';
// }
// cout<<'\n';
// }
// puts("***********************************************************");
// for(int i=1;i<=n;i++){
// for(int j=1;j<=m;j++){
// cout<<b2[i][j]<<' ';
// }
// cout<<'\n';
// }
// puts("***********************************************************");
for(int i=0;i<n;i++){
hd1[i]=1,tl1[i]=0;
}
for(int i=0;i<m;i++){
hd2[i]=1,tl2[i]=0;
}
for(int i=0;i<=n;i++){
for(int j=0;j<=m;j++){
s1[i][j]=s2[j][i]=0;
}
}
int ans1=0,ans2=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
while(hd1[i-1]<=tl1[i-1]&&f[i-1][j-1]>f[i-1][q1[i-1][tl1[i-1]]]){
sub(s1[i-1][f[i-1][q1[i-1][tl1[i-1]]]],g[i-1][q1[i-1][tl1[i-1]]]);
tl1[i-1]--;
}
add(s1[i-1][f[i-1][j-1]],g[i-1][j-1]);
q1[i-1][++tl1[i-1]]=j-1;
while(hd1[i-1]<=tl1[i-1]&&q1[i-1][hd1[i-1]]<b1[i][j]){
sub(s1[i-1][f[i-1][q1[i-1][hd1[i-1]]]],g[i-1][q1[i-1][hd1[i-1]]]);
hd1[i-1]++;
}
while(hd2[j-1]<=tl2[j-1]&&f[i-1][j-1]>f[q2[j-1][tl2[j-1]]][j-1]){
sub(s2[j-1][f[q2[j-1][tl2[j-1]]][j-1]],g[q2[j-1][tl2[j-1]]][j-1]);
tl2[j-1]--;
}
add(s2[j-1][f[i-1][j-1]],g[i-1][j-1]);
q2[j-1][++tl2[j-1]]=i-1;
while(hd2[j-1]<=tl2[j-1]&&q2[j-1][hd2[j-1]]<b2[i][j]){
sub(s2[j-1][f[q2[j-1][hd2[j-1]]][j-1]],g[q2[j-1][hd2[j-1]]][j-1]);
hd2[j-1]++;
}
if(hd1[i-1]>tl1[i-1]){
if(hd2[j-1]>tl2[j-1]){
f[i][j]=g[i][j]=1;
}else{
f[i][j]=f[q2[j-1][hd2[j-1]]][j-1]+1;
g[i][j]=s2[j-1][f[q2[j-1][hd2[j-1]]][j-1]];
}
}else{
if(hd2[j-1]>tl2[j-1]){
f[i][j]=f[i-1][q1[i-1][hd1[i-1]]]+1;
g[i][j]=s1[i-1][f[i-1][q1[i-1][hd1[i-1]]]];
}else{
int x=f[i-1][q1[i-1][hd1[i-1]]],y=f[q2[j-1][hd2[j-1]]][j-1];
if(x>y){
f[i][j]=x+1,g[i][j]=s1[i-1][x];
}else if(x<y){
f[i][j]=y+1,g[i][j]=s2[j-1][y];
}else{
f[i][j]=x+1,g[i][j]=pls(s1[i-1][x],s2[j-1][y]);
if(f[i-1][j-1]==x){
sub(g[i][j],g[i-1][j-1]);
}
}
}
}
if(ans1<f[i][j]){
ans1=f[i][j],ans2=g[i][j];
}else if(ans1==f[i][j]){
add(ans2,g[i][j]);
}
}
}
// puts("------------------------------------------------");
// for(int i=1;i<=n;i++){
// for(int j=1;j<=m;j++){
// cout<<f[i][j]<<' ';
// }
// cout<<'\n';
// }
// puts("------------------------------------------------");
// for(int i=1;i<=n;i++){
// for(int j=1;j<=m;j++){
// cout<<g[i][j]<<' ';
// }
// cout<<'\n';
// }
// puts("------------------------------------------------");
cout<<ans1<<' '<<ans2<<'\n';
}
int main(){
freopen("path.in","r",stdin);
freopen("path.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
solve();
}
return 0;
}
}
int main(){return asbt::main();}
/*
1
7 9 3
1 4 4 4
1 3 4 5
3 1 5 4
3 8
*/
D. 生成树的传说
2025CSP-S模拟赛68
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 100 | 37 | 40 | 25 | 202 | 5/14 |
A. 三角形
直接枚举 \(A\) 的所有 \(6\) 种状态,计算答案并取最小值即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
int n;
struct sjx{
int a[15][15];
il int*operator[](int x){
return a[x];
}
il void xz(){
sjx b;
for(int i=1;i<=n;i++){
for(int j=1,ii=n,jj=i;j<=i;j++,ii--,jj--){
b[i][j]=a[ii][jj];
}
}
*this=b;
}
il void dc(){
for(int i=1;i<=n;i++){
for(int l=1,r=i;l<=r;l++,r--){
swap(a[i][l],a[i][r]);
}
}
}
il int operator-(sjx b)const{
int res=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
res+=a[i][j]^b[i][j];
}
}
return res;
}
}a,b;
int main(){
freopen("triangle.in","r",stdin);
freopen("triangle.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
cin>>a[i][j];
}
}
for(int i=1;i<=n;i++){
for(int j=1;j<=i;j++){
cin>>b[i][j];
}
}
int ans=a-b;
a.dc(),ans=min(ans,a-b),a.dc();
a.xz(),ans=min(ans,a-b);
a.dc(),ans=min(ans,a-b),a.dc();
a.xz(),ans=min(ans,a-b);
a.dc(),ans=min(ans,a-b);
// for(int i=1;i<=n;i++){
// for(int j=1;j<=i;j++){
// cout<<a[i][j]<<' ';
// }
// cout<<'\n';
// }
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
B. 子段异或
假设第一个 \(1\) 的位置为 \(p\),我们一定要选的一个串是 \(f_S(p,n)\)。考虑其后第一段连续的 \(0\),我们希望把它们也变成 \(1\)。假设那一段 \(0\) 的开头是 \(q\),长度为 \(len\),我们希望其后的第二段 \(1\) 不受影响,也就是用这一段 \(0\) 来和它们匹配,于是第二个串的开头为 \(\max(p,q-len)\)。再特判一些 corner case 即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e7+5;
int T,n;
string s;
il bool chk1(){
for(char i:s){
if(i=='0'){
return 0;
}
}
return 1;
}
il bool chk0(){
for(char i:s){
if(i=='1'){
return 0;
}
}
return 1;
}
int main(){
freopen("xor.in","r",stdin);
freopen("xor.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
cin>>n>>s;
if(chk1()){
cout<<string(n-1,'1')<<0<<'\n';
continue;
}
if(chk0()){
cout<<0<<'\n';
continue;
}
int p=0;
while(p<n&&s[p]=='0'){
p++;
}
int q=p;
while(q<n&&s[q]=='1'){
q++;
}
if(q==n){
cout<<string(n-p,'1')<<'\n';
continue;
}
int t=q;
while(t<n&&s[t]=='0'){
t++;
}
string a=s.substr(p,n-p);
string b=string(q-p,'0')+s.substr(max(2*q-t,p),n-q);
for(int i=0;i<a.size();i++){
cout<<(int)(a[i]^b[i]);
}
cout<<'\n';
}
return 0;
}
}
int main(){return asbt::main();}
C. 三等分
记 \(a_i\) 表示 \(i\) 的个数。不妨只考虑 \((x,x+1,x+2)\) 三元组,将所有 \(a_i\) 变为三的倍数。
记 \(f_{i,x,y}\) 表示考虑到 \(i\),有 \(x\) 个 \((i,i+1,i+2)\),\(y\) 个 \((i-1,i,i+1)\) 的方案数。于是有转移:
时空复杂度都是 \(O(n^3)\)。滚动数组 + 前缀和优化即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e3+5,mod=1e9+7;
il int pls(int x,int y){
return x+y<mod?x+y:x+y-mod;
}
il void add(int &x,int y){
x=pls(x,y);
}
il int mns(int x,int y){
return x<y?x-y+mod:x-y;
}
il void sub(int &x,int y){
x=mns(x,y);
}
int n,m,a[maxn],f[2][maxn][maxn],g[maxn][maxn][3];
int main(){
freopen("three.in","r",stdin);
freopen("three.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1,x;i<=n;i++){
cin>>x;
a[x]++;
}
f[0][0][0]=g[0][0][0]=1;
for(int i=1,p=1,q=0;i<=m;i++,p^=1,q^=1){
for(int x=0,xx=min({a[i],a[i+1],a[i+2]});x<=xx;x++){
for(int y=0,yy=min({a[i]-x,a[i+1]-x,a[i-1]});y<=yy;y++){
f[p][x][y]=g[y][min({a[i]-x-y,a[i-1]-y,i>1?a[i-2]:0})][(a[i]-x-y)%3];
}
}
for(int x=0,xx=min({a[i],a[i+1],a[i+2]});x<=xx;x++){
for(int y=0,yy=min({a[i]-x,a[i+1]-x,a[i-1]});y<=yy;y++){
g[x][y][0]=y?g[x][y-1][0]:0;
g[x][y][1]=y?g[x][y-1][1]:0;
g[x][y][2]=y?g[x][y-1][2]:0;
add(g[x][y][y%3],f[p][x][y]);
}
}
}
cout<<f[m&1][0][0];
return 0;
}
}
int main(){return asbt::main();}
D. 二叉树遍历
答辩,我跟你爆了。
我们要求的是在 \(x\) 前面遍历到的 \(y\) 的数量。考虑哪些 \(y\) 能产生贡献(\(op_x\) 表示 \(x\) 的遍历方式,\(tr_x\) 表示 \(x\) 的子树,\(ls_x,rs_x\) 表示 \(x\) 的左右子树):
- \(op_x=0,y\in ls_x\)
- \(op_x=1,y\in tr_x\)
- \(\exist z,y\in ls_z,x\in rs_z\)
- \(op_y=-1,x\in tr_y\)
- \(op_y=0,x\in rs_y\)
前两类可以直接 \(O(1)\) 求,第三类能预处理出来。后两类不是非常好维护,考虑按编号分块。可以将块分成两类:第一类为整个块的遍历方式相同的块,第二类为整个块遍历方式不同的块。
对于第一类,我们只需要知道 \(x\) 是这个块内多少个点的子树/右子树,也可以预处理。考虑用数据结构维护第二类块的贡献。
对于区间推平操作,会将中间的整块变成一类块,两端的散块变成二类块。由于二类块最多一共只有 \(O(n)\) 个,考虑直接遍历内部的点在数据结构上修改。这样我们会修改 \(O(n\sqrt{n})\) 次,查询 \(O(n)\) 次,再按 dfn 分块,将区间加改为差分即可。时间复杂度 \(O(n\sqrt{n})\)。
Code
// 答辩,我跟你爆了
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e5+5,B=320,maxb=325;
int n,m,ls[maxn],rs[maxn],szl[maxn];
int dfn[maxn],cnt,stk[maxn],sz[maxn];
int bnm,bg[maxb],ed[maxb],bel[maxn];
il void dfs(int u,int x){
if(!u){
return ;
}
dfn[u]=++cnt,stk[cnt]=u,szl[u]=x;
dfs(ls[u],x),dfs(rs[u],x+sz[ls[u]]);
sz[u]=sz[ls[u]]+sz[rs[u]]+1;
}
namespace B2{
/*
just a declaration
*/
il void add(int,int,int);
il int query(int);
}
namespace B1{
/*
这个分块的对象是 y,按照编号分块
需要记录每一块的类型,以及 1 类块的标记和所有位置的标记
支持的操作是区间修改,和查询
需要调用 B2(?有毒吧)
*/
int cnts[maxn][maxb] ,cntr[maxn][maxb];
// i 是 j 块中多少点的子树,i 是 j 块中多少点的右子树
int typ[maxb],opt[maxn],tag[maxb];
il void dfs(int u){
for(int i=1;i<=bnm;i++){
if(ls[u]){
cnts[ls[u]][i]+=cnts[u][i];
cntr[ls[u]][i]+=cntr[u][i];
}
if(rs[u]){
cnts[rs[u]][i]+=cnts[u][i];
cntr[rs[u]][i]+=cntr[u][i];
}
}
if(ls[u]){
cnts[ls[u]][bel[u]]++;
dfs(ls[u]);
}
if(rs[u]){
cnts[rs[u]][bel[u]]++;
cntr[rs[u]][bel[u]]++;
dfs(rs[u]);
}
}
il void build(){
dfs(1);
for(int i=1;i<=n;i++){
tag[i]=-1;
}
for(int i=1;i<=bnm;i++){
typ[i]=1;
}
}
il void upd(int l,int r,int x){
int pl=bel[l],pr=bel[r];
if(pl==pr){
if(typ[pl]==1){
typ[pl]=2;
for(int i=bg[pl];i<=ed[pl];i++){
// cout<<"f "<<__LINE__<<' '<<i<<'\n';
if(tag[pl]==-1){
B2::add(dfn[i]+1,dfn[i]+sz[i]-1,1);
}else if(tag[pl]==0){
B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,1);
}
opt[i]=tag[pl];
}
}
for(int i=l;i<=r;i++){
// cout<<"f "<<__LINE__<<' '<<i<<'\n';
if(opt[i]==-1){
B2::add(dfn[i]+1,dfn[i]+sz[i]-1,-1);
}else if(opt[i]==0){
B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,-1);
}
opt[i]=x;
if(opt[i]==-1){
B2::add(dfn[i]+1,dfn[i]+sz[i]-1,1);
}else if(opt[i]==0){
B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,1);
}
}
return ;
}
for(int i=pl+1;i<pr;i++){
// cout<<"f "<<__LINE__<<' '<<i<<'\n';
if(typ[i]==2){
for(int j=bg[i];j<=ed[i];j++){
// cout<<"f "<<__LINE__<<' '<<j<<'\n';
if(opt[j]==-1){
B2::add(dfn[j]+1,dfn[j]+sz[j]-1,-1);
}else if(opt[j]==0){
B2::add(dfn[j]+sz[ls[j]]+1,dfn[j]+sz[j]-1,-1);
}
}
typ[i]=1;
}
tag[i]=x;
}
if(typ[pl]==1){
typ[pl]=2;
for(int i=bg[pl];i<=ed[pl];i++){
// cout<<"f "<<__LINE__<<' '<<i<<'\n';
if(tag[pl]==-1){
B2::add(dfn[i]+1,dfn[i]+sz[i]-1,1);
}else if(tag[pl]==0){
B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,1);
}
opt[i]=tag[pl];
}
}
for(int i=l;i<=ed[pl];i++){
// cout<<"f "<<__LINE__<<' '<<i<<'\n';
if(opt[i]==-1){
B2::add(dfn[i]+1,dfn[i]+sz[i]-1,-1);
}else if(opt[i]==0){
B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,-1);
}
opt[i]=x;
if(opt[i]==-1){
B2::add(dfn[i]+1,dfn[i]+sz[i]-1,1);
}else if(opt[i]==0){
B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,1);
}
}
if(typ[pr]==1){
typ[pr]=2;
for(int i=bg[pr];i<=ed[pr];i++){
// cout<<"f "<<__LINE__<<' '<<i<<'\n';
if(tag[pr]==-1){
B2::add(dfn[i]+1,dfn[i]+sz[i]-1,1);
}else if(tag[pr]==0){
B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,1);
}
opt[i]=tag[pr];
}
}
for(int i=bg[pr];i<=r;i++){
// cout<<"f "<<__LINE__<<' '<<i<<'\n';
if(opt[i]==-1){
B2::add(dfn[i]+1,dfn[i]+sz[i]-1,-1);
}else if(opt[i]==0){
B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,-1);
}
opt[i]=x;
if(opt[i]==-1){
B2::add(dfn[i]+1,dfn[i]+sz[i]-1,1);
}else if(opt[i]==0){
B2::add(dfn[i]+sz[ls[i]]+1,dfn[i]+sz[i]-1,1);
}
}
}
il int query(int x){
// for(int i=1;i<=bnm;i++){
// cout<<typ[i]<<' ';
// }
// cout<<'\n';
int res=B2::query(dfn[x])+szl[x]+1;
int optx=typ[bel[x]]==1?tag[bel[x]]:opt[x];
if(optx==0){
res+=sz[ls[x]];
}else if(optx==1){
res+=sz[x]-1;
}
for(int i=1;i<=bnm;i++){
if(typ[i]==1){
if(tag[i]==-1){
res+=cnts[x][i];
}else if(tag[i]==0){
res+=cntr[x][i];
}
}
}
return res;
}
}
namespace B2{
/*
implementation
用来维护二类块的贡献,是按照 dfn 分块的
区间加、单点查,但为了平衡时间改为差分
*/
int tag[maxb][maxb];
il void add(int l,int r,int x){
if(l>r){
return ;
}
// cout<<"a "<<l<<' '<<r<<' '<<x<<'\n';
int pl=bel[l],pr=bel[r];
if(pl==pr){
tag[pl][l-bg[pl]+1]+=x,tag[pl][r-bg[pl]+2]-=x;
return ;
}
tag[pl+1][0]+=x,tag[pr][0]-=x;
tag[pl][l-bg[pl]+1]+=x,tag[pl][ed[pl]-bg[pl]+2]-=x;
tag[pr][1]+=x,tag[pr][r-bg[pr]+2]-=x;
}
il int query(int x){
int res=0;
for(int i=1;i<=bel[x];i++){
// cout<<tag[i][0]<<' ';
res+=tag[i][0];
}
// cout<<'\n';
// cout<<"p "<<res<<'\n';
for(int i=1;i<=x-bg[bel[x]]+1;i++){
res+=tag[bel[x]][i];
// cout<<tag[bel[x]][i]<<' ';
}
// cout<<'\n';
// cout<<"q "<<res<<'\n';
return res;
}
}
int main(){
freopen("traversing.in","r",stdin);
freopen("traversing.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>ls[i]>>rs[i];
}
dfs(1,0);
// for(int i=1;i<=n;i++){
// cout<<i<<' '<<sz[i]<<' '<<ls[i]<<' '<<rs[i]<<' '<<szl[i]<<'\n';
// }
bnm=n/B;
for(int i=1;i<=bnm;i++){
bg[i]=ed[i-1]+1,ed[i]=ed[i-1]+B;
}
if(ed[bnm]<n){
bg[bnm+1]=ed[bnm]+1,ed[bnm+1]=n;
bnm++;
}
for(int i=1;i<=bnm;i++){
for(int j=bg[i];j<=ed[i];j++){
bel[j]=i;
}
}
B1::build();
while(m--){
int opt;
cin>>opt;
if(opt==1){
int l,r,x;
cin>>l>>r>>x;
B1::upd(l,r,x);
}else{
int x;
cin>>x;
cout<<B1::query(x)<<'\n';
}
}
return 0;
}
}
int main(){return asbt::main();}
2025CSP-S模拟赛72
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 100 | 100 | 20 | 20 | 240 | 2/7 |
为啥还在打 CSP 模拟赛啊
A. 翻牌
二分答案 \(mid\),将 \(\ge mid\) 的设为 \(1\),\(<mid\) 的设为 \(-1\),则当且仅当和 \(>0\) 时 \(ans\ge mid\)。找出 \(b_i-a_i\) 的最大子段和即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=3e5+5;
int n,a[maxn],b[maxn],c[maxn],d[maxn];
il bool check(int x){
int sum=0,mx=0;
for(int i=1;i<=n;i++){
sum+=a[i]>=x?1:-1;
c[i]=c[i-1]+(b[i]>=x?1:-1)-(a[i]>=x?1:-1);
mx=max(mx,c[i]-d[i-1]);
d[i]=min(d[i-1],c[i]);
}
return sum+mx>0;
}
int main(){
// freopen("flip.in","r",stdin);
// freopen("flip.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i]>>b[i];
}
int l=1,r=1e9;
while(l<r){
int mid=(l+r+1)>>1;
if(check(mid)){
l=mid;
}else{
r=mid-1;
}
}
cout<<l;
return 0;
}
}
int main(){return asbt::main();}
B. 塔
考虑如果知道了这个序列,怎么求最小操作次数:从左往右扫,如果 \(a_i>a_{i-1}\),则要增加 \(a_i-a_{i-1}\) 次,否则不用增加。于是设 \(f_{i,j}\) 表示考虑了前 \(i\) 个,\(a_i=j\) 的最小操作次数,于是有转移:
前后缀最小值优化即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=105,maxm=1e5+5,inf=1e9,B=1e5;
int n,a[maxn],f[maxn][maxm],pre[maxm],suf[maxm];
int main(){
// freopen("tower.in","r",stdin);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=2;i<=n;i++){
cin>>a[i];
}
memset(f,0x3f,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;i++){
pre[0]=f[i-1][0];
for(int j=1;j<=B;j++){
pre[j]=min(pre[j-1],f[i-1][j]-j);
}
suf[B]=f[i-1][B];
for(int j=B-1;~j;j--){
suf[j]=min(suf[j+1],f[i-1][j]);
}
for(int j=0;j<=B;j++){
f[i][j]=min(j<a[i]?inf:pre[j-a[i]]+j,j+a[i]>B?inf:suf[j+a[i]]);
}
}
int ans=inf;
for(int i=0;i<=B;i++){
ans=min(ans,f[n][i]);
}
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
C. 图
设点集大小为 \(k\),于是由于最大度数不超过 \(3\),边数最多为 \(\frac{3}{2}k\)。由于要求红、蓝均连通,边数至少为 \(2(k-1)\)。于是有不等式:\(\frac{3}{2}k\ge2(k-1)\),解集为 \(k\le4\)。于是我们只需要考虑 \(k\in[1,4]\) 的情况。
于是直接枚举所有可能合法的红边连通的点集。以每个点为根跑出一棵 dfs 树即可。考虑如果一个点连了三条边,那么这个点集不可能蓝边连通,于是这个点集在搜索树上必然是一条根链。会算重,用 set 去重即可。时间复杂度 \(O(n\log n)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,m,fa[maxn],faz[maxn],p[maxn];
bool vis[maxn],pp[maxn];
vector<int> e[2][maxn];
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
struct node{
int a[4];
node(){}
node(int u){
a[0]=a[1]=a[2]=a[3]=0;
int p=0;
while(u){
// cout<<u<<'\n';
a[p++]=u,u=faz[u];
}
sort(a,a+4);
// for(int i=0;i<=3;i++){
// cout<<a[i]<<' ';
// }
// cout<<'\n';
}
il bool operator<(const node &x)const{
if(a[0]!=x.a[0]){
return a[0]<x.a[0];
}
if(a[1]!=x.a[1]){
return a[1]<x.a[1];
}
if(a[2]!=x.a[2]){
return a[2]<x.a[2];
}
return a[3]<x.a[3];
}
};
set<node> ans;
il void dfs(int u,int fth,int dep){
// cout<<u<<'\n';
if(dep>4){
return ;
}
faz[u]=fth,vis[u]=1;
int x=u,cnt=0;
while(x){
p[++cnt]=x,x=faz[x];
}
for(int i=1;i<=cnt;i++){
fa[p[i]]=p[i],pp[p[i]]=1;
}
for(int i=1;i<=cnt;i++){
int u=p[i];
for(int v:e[1][u]){
if(pp[v]){
fa[find(u)]=find(v);
}
}
}
bool flag=0;
// cout<<cnt<<'\n';
for(int i=1;i<=cnt;i++){
// cout<<p[i]<<' '<<find(p[i])<<'\n';
if(find(p[i])==p[i]){
if(!flag){
flag=1;
}else{
goto togo;
}
}
}
ans.insert(node(u));
// puts("666");
togo:;
for(int i=1;i<=cnt;i++){
pp[p[i]]=0;
}
for(int v:e[0][u]){
if(!vis[v]){
dfs(v,u,dep+1);
}
}
vis[u]=0;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1,u,v,c;i<=m;i++){
cin>>u>>v>>c;
e[c][u].pb(v),e[c][v].pb(u);
}
for(int i=1;i<=n;i++){
// cout<<i<<'\n';
dfs(i,0,1);
}
cout<<ans.size();
return 0;
}
}
int main(){return asbt::main();}
D. 勇士斗恶龙
首先考虑 \(b_i\) 都为 \(0\) 的情况,设 \(dp_i\) 表示恶龙血量为 \(i\) 的答案,有转移:
其中 \(cnt_j\) 表示 \(a_i=j\) 的法术的数量。设 \(ans_i=\begin{bmatrix} dp_i&dp_{i-1}&\cdots&dp_{i-99} \end{bmatrix}\),于是可以设计出转移矩阵:
矩阵快速幂即可。
考虑 \(b_i\) 的限制,有新的转移方程:
其中 \(cnt_{j,k}\) 表示 \(a_i=j,b_i=k\) 的法术的数量。
考虑依然用矩阵优化,将 \(n\) 拆位,我们希望求出 \(f_i\) 表示 \(0\) 到 \(2^i\) 的转移矩阵,因为较高的幂次不会影响低幂次的转移,所以有:
其中 \(\operatorname{bit}_i(n)\) 表示 \(n\) 在二进制下的第 \(i\) 位。
考虑求 \(f_i\)。设 \(bas_i\) 表示考虑了 \(2\) 的 \(0\sim i\) 次幂的法术的转移矩阵,\(g_i\) 表示 \(0\) 到 \(2^i-1\) 的转移矩阵,于是有:
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int mod=1e9+7;
int m;
ll n;
struct juz{
int a[105][105];
juz(){
memset(a,0,sizeof(a));
}
il int*operator[](int x){
return a[x];
}
il juz operator*(juz b)const{
juz c;
for(int i=1;i<=100;i++){
for(int j=1;j<=100;j++){
for(int k=1;k<=100;k++){
c[i][j]=(a[i][k]*1ll*b[k][j]+c[i][j])%mod;
}
}
}
return c;
}
}bas[65],f[65],g[65],ans;
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1,a,b;i<=m;i++){
cin>>a>>b;
do{
bas[b++][a][1]++;
}while(b<=60);
}
for(int i=1;i<100;i++){
for(int j=0;j<=60;j++){
bas[j][i][i+1]=1;
}
}
f[0]=bas[0];
for(int i=1;i<=100;i++){
g[0][i][i]=ans[i][i]=1;
}
for(int i=1;i<=60;i++){
g[i]=f[i-1]*g[i-1];
f[i]=g[i]*bas[i];
}
for(int i=60;~i;i--){
if(n>>i&1){
ans=ans*f[i];
}
}
cout<<ans[1][1];
return 0;
}
}
int main(){return asbt::main();}
HZOJ NOIP2025模拟1
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 30 | 40 | 50 | 50 | 170 | 8/27 |
A. 公司的供应链
题意是要不断删环直到图变成 DAG。
暴力的做法是显然的,每次找出一个环然后删掉即可,时间复杂度 \(O(m^2)\)。它的问题在于每条边会重复走许多次,不够优秀。
考虑当找到了一个环时,先将它回溯出来,然后从这个环的起点(也就是 dfs 序最小的点)开始继续向下搜索。那么直接记录每个点上一次遍历到了第几条边即可。这样每条边只会遍历一次,时间复杂度 \(O(m)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
ifstream cin("dag.in");
ofstream cout("dag.out");
const int maxn=3e5+5,maxm=6e5+5;
int n,m,enm,hd[maxn];
bool vis[maxn],ban[maxm];
struct{
int u,v,nxt;
}e[maxm];
il void addedge(int u,int v){
e[++enm]={u,v,hd[u]};
hd[u]=enm;
}
il int dfs(int u){
// cout<<u<<'\n';
vis[u]=1;
for(int &i=hd[u];i;i=e[i].nxt){
int v=e[i].v;
if(vis[v]){
ban[i]=1,vis[u]=0,i=e[i].nxt;
return v;
}
int x=dfs(v);
if(x){
ban[i]=1;
if(u!=x){
vis[u]=0,i=e[i].nxt;
return x;
}
}
}
vis[u]=0;
return 0;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1,u,v;i<=m;i++){
cin>>u>>v;
addedge(u,v);
}
for(int i=1;i<=n;i++){
dfs(i);
}
int cnt=0;
for(int i=1;i<=m;i++){
if(!ban[i]){
cnt++;
}
}
cout<<cnt<<'\n';
for(int i=1;i<=m;i++){
if(!ban[i]){
cout<<e[i].u<<' '<<e[i].v<<'\n';
}
}
return 0;
}
}
int main(){return asbt::main();}
B. 宇宙的卷积
考虑对于每个 \(i\) 求出 \(\max\limits_{(k-i)\subseteq j\subseteq k}b_j\)。
设 \(f_{i,j}(j\subseteq\overline{i})\) 表示满足 \(x\cap\overline{i}=j\) 的 \(x\) 中 \(b_x\) 的最大值,于是 \(i\) 对 \(k\) 的贡献为 \(a_i+f_{i,k-i}\)。考虑转移,当我们要从 \(S\) 转移到 \(T=S\cup\{i\}\) 时,有:
时间复杂度 \(O(3^n)\)。只维护搜索树根链上的 \(f\) 值,空间复杂度 \(O(n2^n)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=(1<<18)+5;
int n,a[maxn],f[20][maxn],ans[maxn];
il void dfs(int S,int d,int p){
// cout<<(bitset<3>)S<<'\n';
for(int T=S;T<1<<n;T=(T+1)|S){
ans[T]=max(ans[T],a[S]+f[d][T^S]);
// cout<<(bitset<3>)T<<' ';
}
// cout<<'\n';
for(int i=p;i<n;i++){
int T=S|1<<i,U=((1<<n)-1)^T;
for(int X=U;;X=(X-1)&U){
f[d+1][X]=max(f[d][X],f[d][X|1<<i]);
if(!X){
break;
}
}
dfs(T,d+1,i+1);
for(int X=U;;X=(X-1)&U){
f[d+1][X]=0;
if(!X){
break;
}
}
}
}
int main(){
freopen("juanji.in","r",stdin);
freopen("juanji.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=0;i<1<<n;i++){
cin>>a[i];
}
for(int i=0;i<1<<n;i++){
cin>>f[0][i];
}
dfs(0,0,0);
for(int i=0;i<1<<n;i++){
cout<<ans[i]<<' ';
}
return 0;
}
}
int main(){return asbt::main();}
C. 舰队的远征
整条路线可分为从 \(s\) 走到 \(x\),从 \(x\) 跳到 \(y\),再从 \(y\) 走到 \(t\) 三段。于是我们先用 dijkstra 跑出 \(s\) 到每个点的最短路 \(dis_i\) 和每个点到 \(t\) 的最短路 \(dit_i\)。于是对于 \((x,y)\),答案即为:
括号内的是一条关于 \(y\) 的直线,使用李超线段树即可。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
ifstream cin("far.in");
ofstream cout("far.out");
const int maxn=2e5+5,inf=1e18;
int n,m,s,t,dis[maxn],dit[maxn];
bool vis[maxn];
vector<pii> es[maxn],et[maxn];
priority_queue<pii> q;
il void dijkstra(int x,vector<pii> *e,int *dis){
for(int i=1;i<=n;i++){
dis[i]=inf,vis[i]=0;
}
dis[x]=0,q.push(mp(0,x));
while(q.size()){
int u=q.top().sec;
q.pop();
if(vis[u]){
continue;
}
vis[u]=1;
for(pii i:e[u]){
int v=i.fir,w=i.sec;
if(!vis[v]&&dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
q.push(mp(-dis[v],v));
}
}
}
}
struct line{
int k,b;
line(int k=0,int b=inf):k(k),b(b){}
il int calc(int x){
return k*x+b;
}
}tr[maxn<<2];
il void insert(int id,int l,int r,line x){
int mid=(l+r)>>1;
if(tr[id].calc(mid)>x.calc(mid)){
swap(tr[id],x);
}
int lo=tr[id].calc(l),ro=tr[id].calc(r);
int ln=x.calc(l),rn=x.calc(r);
if(ln<lo){
insert(lid,l,mid,x);
}else if(rn<ro){
insert(rid,mid+1,r,x);
}
}
il int query(int id,int l,int r,int p){
if(l==r){
return tr[id].calc(p);
}
int mid=(l+r)>>1,res=tr[id].calc(p);
if(p<=mid){
res=min(res,query(lid,l,mid,p));
}else{
res=min(res,query(rid,mid+1,r,p));
}
return res;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>s>>t;
for(int i=1,u,v,w;i<=m;i++){
cin>>u>>v>>w;
es[u].pb(mp(v,w));
et[v].pb(mp(u,w));
}
dijkstra(s,es,dis);
dijkstra(t,et,dit);
// for(int i=1;i<=n;i++){
// cout<<i<<' '<<dis[i]<<' '<<dit[i]<<'\n';
// }
for(int i=1;i<=n;i++){
insert(1,1,n,line(-2*i,dis[i]+i*i));
}
int ans=inf;
for(int i=1;i<=n;i++){
ans=min(ans,query(1,1,n,i)+dit[i]+i*i);
}
cout<<ans;
return 0;
}
}
signed main(){return asbt::main();}
D. 军团的阵列线
\(\min\) 前面都是负号,因此我们可以依次给每个序列取相反数,这样就都求 \(\max\) 即可。于是我们要求的就是这个式子:
考虑扫描线右端点,用线段树维护这个式子。最大值用单调栈维护,然后在线段树上维护 \(A,B,C,AB,AC,BC,ABC\) 即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define lid id<<1
#define rid id<<1|1
#define a(id) tr[id].a
#define b(id) tr[id].b
#define c(id) tr[id].c
#define ab(id) tr[id].ab
#define ac(id) tr[id].ac
#define bc(id) tr[id].bc
#define abc(id) tr[id].abc
#define ta(id) tr[id].ta
#define tb(id) tr[id].tb
#define tc(id) tr[id].tc
using namespace std;
namespace asbt{
ifstream cin("team.in");
ofstream cout("team.out");
const int maxn=1e5+5;
int n,a[maxn],b[maxn],c[maxn];
int sa[maxn],sb[maxn],sc[maxn];
struct{
unsigned int a,b,c,ab,ac,bc,abc,ta,tb,tc;
}tr[maxn<<2];
il void pushup(int id){
a(id)=a(lid)+a(rid);
b(id)=b(lid)+b(rid);
c(id)=c(lid)+c(rid);
ab(id)=ab(lid)+ab(rid);
ac(id)=ac(lid)+ac(rid);
bc(id)=bc(lid)+bc(rid);
abc(id)=abc(lid)+abc(rid);
}
il void pushta(int id,int l,int r,unsigned int x){
ta(id)+=x,a(id)+=x*(r-l+1);
ab(id)+=x*b(id),ac(id)+=x*c(id),abc(id)+=x*bc(id);
}
il void pushtb(int id,int l,int r,unsigned int x){
tb(id)+=x,b(id)+=x*(r-l+1);
ab(id)+=x*a(id),bc(id)+=x*c(id),abc(id)+=x*ac(id);
}
il void pushtc(int id,int l,int r,unsigned int x){
tc(id)+=x,c(id)+=x*(r-l+1);
ac(id)+=x*a(id),bc(id)+=x*b(id),abc(id)+=x*ab(id);
}
il void pushdown(int id,int l,int r){
int mid=(l+r)>>1;
if(ta(id)){
pushta(lid,l,mid,ta(id));
pushta(rid,mid+1,r,ta(id));
ta(id)=0;
}
if(tb(id)){
pushtb(lid,l,mid,tb(id));
pushtb(rid,mid+1,r,tb(id));
tb(id)=0;
}
if(tc(id)){
pushtc(lid,l,mid,tc(id));
pushtc(rid,mid+1,r,tc(id));
tc(id)=0;
}
}
il void build(int id,int l,int r){
tr[id]={0,0,0,0,0,0,0,0,0,0};
if(l==r){
return ;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
}
il void adda(int id,int L,int R,int l,int r,unsigned int x){
// cout<<"a "<<id<<' '<<L<<' '<<R<<' '<<l<<' '<<r<<' '<<x<<'\n';
if(L>=l&&R<=r){
pushta(id,L,R,x);
return ;
}
pushdown(id,L,R);
int mid=(L+R)>>1;
if(l<=mid){
adda(lid,L,mid,l,r,x);
}
if(r>mid){
adda(rid,mid+1,R,l,r,x);
}
pushup(id);
}
il void addb(int id,int L,int R,int l,int r,unsigned int x){
// cout<<"b "<<id<<' '<<L<<' '<<R<<' '<<l<<' '<<r<<' '<<x<<'\n';
if(L>=l&&R<=r){
pushtb(id,L,R,x);
return ;
}
pushdown(id,L,R);
int mid=(L+R)>>1;
if(l<=mid){
addb(lid,L,mid,l,r,x);
}
if(r>mid){
addb(rid,mid+1,R,l,r,x);
}
pushup(id);
}
il void addc(int id,int L,int R,int l,int r,unsigned int x){
// cout<<"c "<<id<<' '<<L<<' '<<R<<' '<<l<<' '<<r<<' '<<x<<'\n';
if(L>=l&&R<=r){
pushtc(id,L,R,x);
return ;
}
pushdown(id,L,R);
int mid=(L+R)>>1;
if(l<=mid){
addc(lid,L,mid,l,r,x);
}
if(r>mid){
addc(rid,mid+1,R,l,r,x);
}
pushup(id);
}
il unsigned int solve(){
unsigned int ans=0;
int tpa=0,tpb=0,tpc=0;
build(1,1,n);
for(int i=1;i<=n;i++){
while(tpa&&a[i]>a[sa[tpa]]){
adda(1,1,n,sa[tpa-1]+1,sa[tpa],a[i]-a[sa[tpa]]);
tpa--;
}
while(tpb&&b[i]>b[sb[tpb]]){
addb(1,1,n,sb[tpb-1]+1,sb[tpb],b[i]-b[sb[tpb]]);
tpb--;
}
while(tpc&&c[i]>c[sc[tpc]]){
addc(1,1,n,sc[tpc-1]+1,sc[tpc],c[i]-c[sc[tpc]]);
tpc--;
}
sa[++tpa]=sb[++tpb]=sc[++tpc]=i;
adda(1,1,n,i,i,a[i]);
addb(1,1,n,i,i,b[i]);
addc(1,1,n,i,i,c[i]);
ans+=abc(1);
// cout<<i<<' '<<abc(1)<<'\n';
}
// cout<<ans<<'\n';
return ans;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
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];
}
unsigned int ans=0;
for(int S=0;S<8;S++){
if(S&0b1){
for(int i=1;i<=n;i++){
a[i]=-a[i];
}
}
if(S&0b10){
for(int i=1;i<=n;i++){
b[i]=-b[i];
}
}
if(S&0b100){
for(int i=1;i<=n;i++){
c[i]=-c[i];
}
}
// cout<<bitset<3>(S)<<'\n';
ans+=solve();
if(S&0b1){
for(int i=1;i<=n;i++){
a[i]=-a[i];
}
}
if(S&0b10){
for(int i=1;i<=n;i++){
b[i]=-b[i];
}
}
if(S&0b100){
for(int i=1;i<=n;i++){
c[i]=-c[i];
}
}
}
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
HZOJ NOIP2025模拟2
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 100 | 0 | 40 | 60 | 200 | 10/29 |
A. 数字(math)
不妨令 \(n\le m\),于是有 \(a\times b=a(c\times10^{m-n}+d)\),其中 \(\lg a=\lg c\)。于是我们优先提升 \(ac\),再提升 \(ad\)。
显然要将所有数码从大到小往里填,因此 \(a+c\) 是定值,我们要使 \(|a-c|\) 尽可能的小。考虑如果 \(a\) 更大,则会顺带使 \(ad\) 更大,而 \(c\) 更大则不会有额外的贡献,因此要求 \(a\ge c\)。于是我们每次取出前两大的数码填,如果此前 \(a=c\) 则将较大的填入 \(a\),否则此时已经满足 \(a>c\) 了,将较大的填入 \(c\)。最后再将剩下的数码倒序填入 \(d\) 即可。
需要高精乘,时间复杂度 \(O(nm)\)。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
ifstream cin("math.in");
ofstream cout("math.out");
const int maxn=2e3+5;
int T,n,m,p[maxn],c[maxn];
int main(){
// system("fc ex_math2.out my.out");
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
cin>>n>>m;
if(n>m){
swap(n,m);
}
int cnt=0;
for(int i=1,x;i<=9;i++){
cin>>x;
while(x--){
p[++cnt]=i;
}
}
if(!n){
cout<<0<<'\n';
continue;
}
string a,b;
bool flag=0;
while(cnt){
if(a.size()==n){
b+=p[cnt--];
}else{
if(!flag){
if(p[cnt]>p[cnt-1]){
flag=1;
}
a+=p[cnt--],b+=p[cnt--];
}else{
b+=p[cnt--],a+=p[cnt--];
}
}
}
reverse(a.begin(),a.end());
reverse(b.begin(),b.end());
a=" "+a,b=" "+b;
// for(int i=1;i<=n;i++){
// cout<<(int)a[i];
// }
// cout<<' ';
// for(int i=1;i<=m;i++){
// cout<<(int)b[i];
// }
// cout<<'\n';
for(int i=0;i<=n+m;i++){
c[i]=0;
}
c[0]=n+m;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
c[i+j-1]+=a[i]*b[j];
}
}
for(int i=1;i<=c[0];i++){
c[i+1]+=c[i]/10,c[i]%=10;
}
while(c[0]>1&&!c[c[0]]){
c[0]--;
}
for(int i=c[0];i;i--){
cout<<c[i];
}
cout<<'\n';
}
return 0;
}
}
signed main(){return asbt::main();}
B. 游戏(game)
考虑如果确定了 \(A\) 和 \(B\) 第一次选的位置,那么 \(B\) 后面的每一步都可以与 \(A\) 相向而行。进而当确定了 \(A\) 的起点 \(x\) 后,\(B\) 可以通过选择起点来使 \(A\) 最后的区间之和为所有包含了 \(x\) 的长为 \(\lceil\frac{n}{2}\rceil\) 的区间中最小的一个。而 \(A\) 又可以选择起点,因此用 ST 表求出每个 \(x\) 对应的最小的和,再对每个 \(x\) 的答案取 \(\max\) 即可。

Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
ifstream cin("game.in");
ofstream cout("game.out");
const int maxn=5e5+5,inf=2e9;
int n,a[maxn<<1],st[maxn][22];
il int query(int l,int r){
if(l>r){
return inf;
}
int p=__lg(r-l+1);
return min(st[l][p],st[r-(1<<p)+1][p]);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
a[i+n]=a[i];
}
for(int i=1;i<=n<<1;i++){
a[i]+=a[i-1];
}
int B=(n+1)/2;
for(int i=1;i<=n;i++){
st[i][0]=i<B?a[i+n]-a[i+n-B]:a[i]-a[i-B];
}
for(int j=1;j<=19;j++){
for(int i=1;i+(1<<j)-1<=n;i++){
st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
}
int ans=0;
for(int i=1;i<=n;i++){
int l=i,r=i+B-1;
if(r>n){
r-=n;
}
ans=max(ans,l<=r?query(l,r):min(query(1,r),query(l,n)));
}
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
C. 海报(posters)
D. 环(ring)
根号分治。设阈值 \(B\),对于块长小于 \(B\) 的环,考虑直接用数据结构维护其贡献。每次修改操作要进行 \(B\) 次单点修改,分块即可,修改 \(O(B)\),查询 \(O(B+\frac{n}{B})\)。
对于较大的环,因为其数量最多有 \(\frac{n}{B}\) 个,一个直接的想法是维护环上的前缀和,二分出查询区间在环上的位置,单次复杂度 \(O(\frac{n}{B}\log n)\)。考虑离线,对于每个环算出每个位置的前驱后继,修改过程中记录偏移量,这样对于每个询问就可以 \(O(1)\) 算出这个大环的贡献,总时间复杂度 \(O((n+q)\frac{n}{B})\)。
取 \(B=\sqrt{n}\),总时间复杂度 \(O((n+q)\sqrt{n})\)。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
ifstream cin("ring.in");
ofstream cout("ring.out");
const int maxn=1.5e5+5,B=387,maxb=397;
int n,m,q,a[maxn],b[maxn],c[maxn],ans[maxn];
int bnm,st[maxb],ed[maxb],sm[maxb],bel[maxn];
int sum[maxn<<1],pre[maxn],nxt[maxn];
vector<int> vc[maxn];
struct{
int opt,l,r,x;
}d[maxn];
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=n;i++){
cin>>b[i];
c[b[i]]++,vc[b[i]].pb(i);
}
for(int i=1;i<=n;i++){
cin>>a[i];
}
bnm=n/B;
for(int i=1;i<=bnm;i++){
st[i]=ed[i-1]+1,ed[i]=ed[i-1]+B;
for(int j=st[i];j<=ed[i];j++){
bel[j]=i;
if(c[b[j]]<=B){
sm[i]+=a[j];
}
}
}
if(ed[bnm]<n){
st[bnm+1]=ed[bnm]+1,ed[bnm+1]=n;
bnm++;
for(int i=st[bnm];i<=ed[bnm];i++){
bel[i]=bnm;
if(c[b[i]]<=B){
sm[bnm]+=a[i];
}
}
}
// for(int i=1;i<=n;i++){
// cout<<bel[i]<<' ';
// }
// cout<<'\n';
// for(int i=1;i<=bnm;i++){
// cout<<st[i]<<' '<<ed[i]<<'\n';
// }
for(int i=1;i<=q;i++){
int opt;
cin>>opt;
if(opt==1){
int l,r;
cin>>l>>r;
d[i]={opt,l,r,0};
int pl=bel[l],pr=bel[r];
if(pl==pr){
for(int j=l;j<=r;j++){
if(c[b[j]]<=B){
ans[i]+=a[j];
}
}
}else{
for(int j=pl+1;j<pr;j++){
// cout<<"b "<<j<<' '<<sm[j]<<'\n';
ans[i]+=sm[j];
}
for(int j=l;j<=ed[pl];j++){
if(c[b[j]]<=B){
// cout<<"a "<<j<<' '<<a[j]<<'\n';
ans[i]+=a[j];
}
}
for(int j=st[pr];j<=r;j++){
if(c[b[j]]<=B){
// cout<<"a "<<j<<' '<<a[j]<<'\n';
ans[i]+=a[j];
}
}
}
}else{
int x;
cin>>x;
d[i]={opt,0,0,x};
if(c[x]<=B){
for(int j=0;j<c[x];j++){
sm[bel[vc[x][j]]]+=a[vc[x][j?j-1:c[x]-1]]-a[vc[x][j]];
}
for(int j=c[x]-1;j;j--){
swap(a[vc[x][j]],a[vc[x][j-1]]);
}
}
}
}
// for(int i=1;i<=q;i++){
// if(d[i].opt==1){
// cout<<ans[i]<<'\n';
// }
// }
for(int i=1;i<=m;i++){
if(c[i]<=B){
continue;
}
// cout<<i<<":\n";
int cnt=0;
for(int j=0;j<c[i];j++){
sum[cnt+1]=sum[cnt]+a[vc[i][j]];
cnt++;
}
for(int j=0;j<c[i];j++){
sum[cnt+1]=sum[cnt]+a[vc[i][j]];
cnt++;
}
// for(int j=0;j<=c[i]*2;j++){
// cout<<sum[j]<<' ';
// }
// cout<<'\n';
int p=1;
for(int j=0;j<c[i];j++){
while(p<=vc[i][j]){
nxt[p++]=j+1;
}
}
while(p<=n){
nxt[p++]=c[i]+1;
}
p=n;
for(int j=c[i]-1;~j;j--){
while(p>=vc[i][j]){
pre[p--]=j+1;
}
}
while(p){
pre[p--]=0;
}
// for(int i=1;i<=n;i++){
// cout<<pre[i]<<' ';
// }
// cout<<'\n';
// for(int i=1;i<=n;i++){
// cout<<nxt[i]<<' ';
// }
// cout<<'\n';
cnt=0;
for(int j=1;j<=q;j++){
if(d[j].opt==1){
int l=d[j].l,r=d[j].r;
// cout<<l<<' '<<r<<' ';
l=nxt[l]+c[i],r=pre[r]+c[i];
if(l>r){
continue;
}
l-=cnt,r-=cnt;
// cout<<l<<' '<<r<<' '<<sum[r]-sum[l-1]<<'\n';
ans[j]+=sum[r]-sum[l-1];
}else{
if(d[j].x==i){
cnt=(cnt+1)%c[i];
}
}
}
}
for(int i=1;i<=q;i++){
if(d[i].opt==1){
cout<<ans[i]<<'\n';
}
}
return 0;
}
}
signed main(){return asbt::main();}

浙公网安备 33010602011771号