【比赛记录】2025 暑假集训模拟赛合集Ⅱ
2025CSP-S模拟赛35
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 100 | 40 | 40 | - | 180 | 3/19 |
A. 114514
分别考虑 \(b\) 的每一位可以填什么。因为 \(a\) 是字典序最小的,所以对于每一位 \(a\) 不能有更小的选择,于是 \(b_i\le a_i\) 且要求 \([b_i,a_i]\) 在 \(a_i\) 及之前都出现过。用链表可以做到线性复杂度。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=4e6+5,mod=1e9+7;
int n,a[maxn],pre[maxn],nxt[maxn];
int main(){
freopen("trans.in","r",stdin);
freopen("trans.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=0;i<=4e6;i++){
nxt[i]=i+1,pre[i+1]=i;
}
int ans=1;
for(int i=1;i<=n;i++){
ans=ans*1ll*(a[i]-pre[a[i]])%mod;
nxt[pre[a[i]]]=nxt[a[i]];
pre[nxt[a[i]]]=pre[a[i]];
}
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
B. 沉默乐团
容易发现,我们只需判断不交的前后缀即可。考虑前缀和后缀都是严格单增的,于是考虑将它们归并起来,考察临项是否相等。考虑 DP,设 \(f_{i,j,k}\) 表示考虑到了 \(i\) 的前缀,\(j\) 的后缀,后缀 \(-\) 前缀 \(=k\) 的方案数,于是有转移:
注意 \(k\ne0\),初始值 \(f_{0,n+1,0}=1\)。答案即为 \(\sum_{i=0}^{n}f_{i,i+1,k}\)。差分优化,时间复杂度 \(O(n^2m)\)。
Code
#include<bits/stdc++.h>
#define il inline
using namespace std;
namespace asbt{
const int V=2e3+5,mod=1e9+7;
il int pls(int x,int y){
return x+y<mod?x+y:x+y-mod;
}
il int sub(int x,int y){
return x<y?x-y+mod:x-y;
}
il void add(int &x,int y){
x=pls(x,y);
}
il void mns(int &x,int y){
x=sub(x,y);
}
int n,ll[55],rr[55],f[55][55][V<<1];
il void upd(int *f,int l,int r,int v){
add(f[l+V],v),mns(f[r+V+1],v);
}
int main(){
freopen("orchestra.in","r",stdin);
freopen("orchestra.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>ll[i]>>rr[i];
}
upd(f[0][n+1],0,0,1);
for(int len=n+2;len>=2;len--){
for(int i=0,j=len-1;j<=n+1;i++,j++){
for(int k=-2000;k<=2000;k++){
add(f[i][j][k+V],f[i][j][k+V-1]);
if(k>0){
int l=k-rr[i+1],r=k-ll[i+1];
if(l>0||r<0){
upd(f[i+1][j],l,r,f[i][j][k+V]);
}
else{
upd(f[i+1][j],l,-1,f[i][j][k+V]);
upd(f[i+1][j],1,r,f[i][j][k+V]);
}
}
else{
int l=k+ll[j-1],r=k+rr[j-1];
if(l>0||r<0){
upd(f[i][j-1],l,r,f[i][j][k+V]);
}
else{
upd(f[i][j-1],l,-1,f[i][j][k+V]);
upd(f[i][j-1],1,r,f[i][j][k+V]);
}
}
}
}
}
int ans=0;
for(int i=0;i<=n;i++){
for(int j=-2000;j<=2000;j++){
add(ans,f[i][i+1][j+V]);
}
}
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
C. 深黯「军团」
考虑将逆序对拆成每一位的贡献,即每一位计算它后面有多少 \(<a_i\)。打个表出来长这样:
打表
1 2 3 4 |0 0 0 0
1 2 4 3 |0 0 1 0
1 3 2 4 |0 1 0 0
1 3 4 2 |0 1 1 0
1 4 2 3 |0 2 0 0
1 4 3 2 |0 2 1 0
2 1 3 4 |1 0 0 0
2 1 4 3 |1 0 1 0
2 3 1 4 |1 1 0 0
2 3 4 1 |1 1 1 0
2 4 1 3 |1 2 0 0
2 4 3 1 |1 2 1 0
3 1 2 4 |2 0 0 0
3 1 4 2 |2 0 1 0
3 2 1 4 |2 1 0 0
3 2 4 1 |2 1 1 0
3 4 1 2 |2 2 0 0
3 4 2 1 |2 2 1 0
4 1 2 3 |3 0 0 0
4 1 3 2 |3 0 1 0
4 2 1 3 |3 1 0 0
4 2 3 1 |3 1 1 0
4 3 1 2 |3 2 0 0
4 3 2 1 |3 2 1 0
发现每一列都在以 \((n-i+1)!\) 为循环周期循环,且第 \(i+1\) 列经过一个循环周期后第 \(i\) 列答案加一。具体的证明:后 \(n-i\) 位全排列一次过程中 \(i\) 的答案不会变,而将 \(i\) 和后面某个位置交换一定会使逆序对加一。
于是我们考虑对于每一列单独计算。以下将相同的数组成的段叫一块。首先是一个散块,我们可以通过第 \(i+1\) 列第一个循环节的开头位置算出它的长度。然后是几个整块,它们和前面那个散块组成一个散循环节,这些整块直接暴力枚举算出。然后是几个整循环节,直接等差数列求和即可 \(O(1)\) 算出。后面又是一个散循环节,暴力算即可。
考虑时间复杂度,\(20!>10^{18}\),于是 \(n-i\ge19\) 后没有整循环节,\(n-i\ge20\) 后没有整块。所以时间复杂度就是 \(O(n\log n)\)。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e5+5,inf=2e18;
int n,m,mod,a[maxn],fac[maxn];
struct{
#define lowbit(x) (x&-x)
int tr[maxn];
il void add(int p,int v){
for(;p<=n;p+=lowbit(p)){
tr[p]+=v;
}
}
il int query(int p){
int res=0;
for(;p;p-=lowbit(p)){
res+=tr[p];
}
return res;
}
#undef lowbit
}F;
int main(){
freopen("army.in","r",stdin);
freopen("army.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>mod;
for(int i=1;i<=n;i++){
cin>>a[i];
}
fac[0]=1;
for(int i=1;i<=19;i++){
fac[i]=fac[i-1]*i;
// cout<<fac[i]<<" ";
}
// cout<<"\n";
for(int i=20;i<=n;i++){
fac[i]=inf;
}
int ans=0;
for(int i=n,lst0=1,chu,len;i;i--){
// cout<<lst0<<"\n";
chu=F.query(a[i]);
if(lst0>m){
(ans+=m%mod*chu)%=mod;
goto togo;
}
(ans+=(lst0-1)%mod*chu)%=mod;
if(lst0>1){
chu++;
}
for(;chu<=n-i;chu++){
if(lst0+fac[n-i]-1>m){
(ans+=(m-lst0+1)%mod*chu)%=mod;
lst0=inf;
goto togo;
}
(ans+=fac[n-i]%mod*chu)%=mod;
lst0+=fac[n-i];
}
(ans+=(m-lst0+1)/fac[n-i+1]%mod*(fac[n-i]%mod)%mod*((n-i+1)*(n-i)/2%mod))%=mod;
len=(m-lst0+1)%fac[n-i+1];
for(int j=0;;j++){
if(len<fac[n-i]){
(ans+=len%mod*j)%=mod;
break;
}
(ans+=fac[n-i]%mod*j)%=mod;
len-=fac[n-i];
}
togo:;
F.add(a[i],1);
// cout<<ans<<"\n";
}
cout<<ans;
return 0;
}
}
signed main(){return asbt::main();}
/*
10 6 998244353
10 6 2 9 8 3 7 1 5 4
*/
D. 终末螺旋
2025CSP-S模拟赛36
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 40 | 10 | 20 | 12 | 82 | 21/23 |
A. 购买饮料
首先判断 \(-1\) 情况:如果能买得起 \(a\) 瓶,并且 \(b\) 块钱也能买得起 \(a\) 瓶,那么输出 \(-1\)。然后我们就不断地买 \(a\) 瓶并换钱直到买不起 \(a\) 瓶即可。时间复杂度 \(O(T)\)。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
using namespace std;
namespace asbt{
int T,n,m,a,b;
int main(){
freopen("buy.in","r",stdin);
freopen("buy.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
cin>>n>>m>>a>>b;
if(n<a*m){
cout<<n/m<<'\n';
}
else if(b>=a*m){
cout<<-1<<'\n';
}
else{
int t=(n-a*m)/(a*m-b)+1,ans=t*a;
n-=t*(a*m-b),ans+=n/m;
cout<<ans<<'\n';
}
}
return 0;
}
}
signed main(){return asbt::main();}
B. 多边形
首先判断是否有一种颜色只有一个点,如果有,那么将这个点和其他所有点相连即可。否则,一定有相邻的三个颜色不同的点,将它们分成一个小三角形,递归下去即可。可以发现按照这样的构造一定有解。需要链表和栈。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e6+5;
int n,pre[maxn],nxt[maxn],_1=1,c1,c2,c3;
bool ban[maxn];
string s;
stack<int> stk,R,G,B;
il bool chk(int x){
char hp[3]={s[pre[x]],s[x],s[nxt[x]]};
sort(hp,hp+3);
return hp[0]=='B'&&hp[1]=='G'&&hp[2]=='R';
}
int main(){
// freopen("B4.in","r",stdin);
freopen("polygon.in","r",stdin);
freopen("polygon.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>s;
s=" "+s;
for(int i=1;i<=n;i++){
pre[i]=i==1?n:i-1;
nxt[i]=i==n?1:i+1;
if(chk(i)){
stk.push(i);
// cout<<i<<'\n';
}
switch(s[i]){
case 'R':{
R.push(i),c1++;
break;
}
case 'G':{
G.push(i),c2++;
break;
}
default:{
B.push(i),c3++;
break;
}
}
}
while(n>3){
if(c1==1){
while(ban[R.top()]){
R.pop();
}
_1=R.top();
}
else if(c2==1){
while(ban[G.top()]){
G.pop();
}
_1=G.top();
}
else if(c3==1){
while(ban[B.top()]){
B.pop();
}
_1=B.top();
}
else{
goto togo;
}
for(int i=nxt[nxt[_1]];;i=nxt[i]){
cout<<i<<' '<<_1<<'\n';
if(i==pre[pre[_1]]){
break;
}
}
break;
togo:;
while(stk.size()){
int x=stk.top();
stk.pop();
if(ban[x]||!chk(x)){
continue;
}
cout<<pre[x]<<' '<<nxt[x]<<'\n';
n--,ban[x]=1;
switch(s[x]){
case 'R':{
c1--;
break;
}
case 'G':{
c2--;
break;
}
default:{
c3--;
break;
}
}
if(x==_1){
_1=nxt[x];
}
// cout<<"a "<<s[x]<<' '<<s[pre[x]]<<' '<<s[nxt[x]]<<'\n';
nxt[pre[x]]=nxt[x];
pre[nxt[x]]=pre[x];
if(chk(pre[x])){
stk.push(pre[x]);
}
if(chk(nxt[x])){
stk.push(nxt[x]);
}
break;
}
// cout<<'\n';
// cout<<n<<' '<<c1<<' '<<c2<<' '<<c3<<'\n';
}
return 0;
}
}
int main(){return asbt::main();}
C. 二分图最大权匹配
原,神秘网络流。
D. 飞毯
原,神秘构造。
2025CSP-S模拟赛37
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 100 | - | 25 | - | 125 | 19/25 |
A. 新的阶乘
我们需要一个线性做法,因此暴力枚举每个数的每一个质因子是无法接受的,考虑用欧拉筛找出每个数的最小质因子 \(\operatorname{mpf}(x)\),将 \(x\) 的幂次下放给 \(\operatorname{mpf}(x)\) 和 \(\frac{x}{\operatorname{mpf}(x)}\) 即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e7+5;
int n,prn,prm[maxn],mpf[maxn];
ll fac[maxn];
bool npr[maxn];
il void euler(int n=1e7){
for(int i=2;i<=n;i++){
if(!npr[i]){
prm[++prn]=i;
mpf[i]=i;
}
for(int j=1;j<=prn&&i*1ll*prm[j]<=n;j++){
npr[i*prm[j]]=1;
mpf[i*prm[j]]=prm[j];
if(i%prm[j]==0){
break;
}
}
}
}
int main(){
// freopen("my.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
euler();
cin>>n;
for(int i=2;i<=n;i++){
fac[i]=n-i+1;
}
for(int i=n;i>1;i--){
// cout<<mpf[i]<<" ";
if(npr[i]){
fac[mpf[i]]+=fac[i];
fac[i/mpf[i]]+=fac[i];
}
}
bool flag=0;
for(int i=1;i<=prn;i++){
if(fac[prm[i]]){
if(!flag){
cout<<"f("<<n<<")=";
flag=1;
}
else{
cout<<'*';
}
cout<<prm[i];
if(fac[prm[i]]>1){
cout<<'^'<<fac[prm[i]];
}
}
}
return 0;
}
}
int main(){return asbt::main();}
B. 博弈树
首先可以发现如果先手在直径端点上那么必胜。接下来双方一定不能走到直径端点上,否则对方再走到另一个端点就输了。因此我们可以直接将所有直径端点删掉。那么在删后的这棵树上,双方一定还是不能走到直径端点上,否则另一方走到另一个端点上后自己将不得不走到原树直径端点上。那么一层层删点,如果最后剩下一个点了那么先手必败,否则必胜。考虑这个条件等价于什么,发现就是如果起始点在直径中点那么先手必败,否则先手必胜,dfs 预处理即可。
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,d1,d2,ans,dep[maxn],mxd[maxn],des[maxn];
vector<int> e[maxn];
il void dfs1(int u,int fa){
mxd[u]=dep[u]=dep[fa]+1,des[u]=u;
for(int v:e[u]){
if(v==fa){
continue;
}
dfs1(v,u);
if(mxd[v]>mxd[u]){
mxd[u]=mxd[v],des[u]=des[v];
}
}
}
il void dfs2(int u,int fa){
des[dep[u]]=u;
if(u==d2){
ans=des[(dep[u]+1)>>1];
}
for(int v:e[u]){
if(v==fa){
continue;
}
dfs2(v,u);
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].pb(v),e[v].pb(u);
}
dfs1(1,0),d1=des[1];
dfs1(d1,0),d2=des[d1];
if(dep[d2]&1){
dfs2(d1,0);
}
while(m--){
int u;
cin>>u;
cout<<(u==ans?"Bob":"Alice")<<'\n';
}
return 0;
}
}
int main(){return asbt::main();}
C. 划分
发现我们的划分一定是一段长为 \(n-k+1\) 的和 \(k-1\) 段长为 \(1\) 的。考虑每个数对应的位权,那么我们一定是要找到最大的一段 \(n-k+1\),哈希加二分求出 LCP 即可比较大小。考虑方案数,发现因为 \(1\) 的数量是固定的,所以最后一位是 \(0\) 还是 \(1\) 都是无所谓的,所以找到有多少个长为 \(n-k+1\) 的段与最优段的前 \(n-k\) 位相同即可。注意 \(n=k\) 时方案数为 \(1\)。但是还有一个问题,如果这个最优段有前导零,那么方案数是会变多的,换句话说如果前 \(k-1\) 位都是 \(0\) 那么一定是在前导零中随便分 \(\ge k-1\) 次,组合数计算即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ull unsigned ll
using namespace std;
namespace asbt{
const int maxn=2e6+5,mod=998244353;
const ull bas1=131;
const ll bas2=233,mod2=1e9+7;
int n,m,fac[maxn],inv[maxn];
ull ha1[maxn],pw1[maxn];
ll ha2[maxn],pw2[maxn];
string s;
il int qpow(int x,int y=mod-2){
int res=1;
while(y){
if(y&1){
res=res*1ll*x%mod;
}
x=x*1ll*x%mod,y>>=1;
}
return res;
}
il int C(int x,int y){
if(x<y||y<0){
return 0;
}
return fac[x]*1ll*inv[y]%mod*inv[x-y]%mod;
}
il void chk(){
if(n==m){
int ans=0;
for(int i=1;i<=n;i++){
ans+=s[i]^48;
}
cout<<ans<<' '<<1;
exit(0);
}
for(int i=1;i<m;i++){
if(s[i]=='1'){
return ;
}
}
int ans=0,pos=n;
for(int i=1;i<=n;i++){
ans=(ans*2+(s[i]^48))%mod;
if(pos==n&&s[i]=='1'){
pos=i;
}
}
fac[0]=1;
for(int i=1;i<=n;i++){
fac[i]=fac[i-1]*1ll*i%mod;
}
inv[n]=qpow(fac[n]);
for(int i=n;i;i--){
inv[i-1]=inv[i]*1ll*i%mod;
}
int res=0;
for(int i=m-1;i<pos;i++){
(res+=C(pos-1,i))%=mod;
}
cout<<ans<<' '<<res;
exit(0);
}
il ull Ha1(int l,int r){
return ha1[r]-ha1[l-1]*pw1[r-l+1];
}
il ll Ha2(int l,int r){
return (ha2[r]-ha2[l-1]*pw2[r-l+1]%mod2+mod2)%mod2;
}
il bool Eq(int l1,int r1,int l2,int r2){
return Ha1(l1,r1)==Ha1(l2,r2)&&Ha2(l1,r1)==Ha2(l2,r2);
}
il int lcp(int p,int q){
int l=0,r=n-m+1;
while(l<r){
int mid=(l+r+1)>>1;
if(Eq(p,p+mid-1,q,q+mid-1)){
l=mid;
}
else{
r=mid-1;
}
}
return l;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>s;
s=" "+s;
chk();
pw1[0]=pw2[0]=1;
for(int i=1;i<=n;i++){
pw1[i]=pw1[i-1]*bas1;
pw2[i]=pw2[i-1]*bas2%mod2;
ha1[i]=ha1[i-1]*bas1+s[i];
ha2[i]=(ha2[i-1]*bas2+s[i])%mod2;
}
int p=1;
for(int i=2;i<=m;i++){
int t=lcp(p,i);
if(t<n-m+1&&s[p+t]<s[i+t]){
p=i;
}
}
int ans=0,cnt=0;
for(int i=p,j=1;j<=n-m+1;i++,j++){
ans=(ans*2+(s[i]^48))%mod;
}
for(int i=1;i<p;i++){
ans+=s[i]^48;
}
for(int i=p+n-m+1;i<=n;i++){
ans+=s[i]^48;
}
ans%=mod;
for(int i=1;i<=m;i++){
if(Eq(i,i+n-m-1,p,p+n-m-1)){
cnt++;
}
}
cout<<ans<<' '<<cnt;
return 0;
}
}
int main(){return asbt::main();}
D. 灯笼
考虑 DP。设 \(f_{l,r}\) 表示可以到达的高度区间为 \([l,r]\) 的最小花费,每次枚举出发点 \(i\) 进行一次 DP,时间复杂度 \(O(nk^3)\)。
考虑去掉枚举 \(i\) 的操作,考虑转换定义与转移顺序(转置)。设 \(f_{i,l,r}\) 表示当前在 \(i\),高度区间为 \([l,r]\),将 \([1,n]\) 走遍的最小花费,考虑优化状态,容易发现 \([l,r]\) 其实是 \([a_u,b_v]\),这样就可以不用记录 \(i\),于是设 \(f_{u,v}\) 表示合法海拔区间为 \([a_u,b_v]\),且区间经过了 \(u\) 和 \(v\),将 \([1,n]\) 走遍的最小花费,于是有转移:
- \(f_{u,v}\leftarrow f_{u,p}+c_p\)
- \(f_{u,v}\leftarrow f_{p,v}+c_p\)
- \(f_{u,v}\leftarrow f_{p,p}+c_p\)
此时的时间复杂度为 \(O(nk^2)\),考虑优化转移。我们显然要按照 \(a\) 升序的顺序枚举 \(u\),\(b\) 降序的顺序枚举 \(v\),那么对于 \(v'<v\),如果 \(f_{u,p}\) 不能向 \(f_{u,v}\) 转移,那么一定不能向 \(f_{u,v'}\) 转移。于是用小根堆对每个 \(u\) 维护合法的 \(f_{u,p}+c_p\) 即可。对于第三个转移,将其分步成 \(f_{p,p}\to f_{u,p}/f_{p,v}\to f_{u,v}\),特判即可。时间复杂度 \(O(k^2\log k)\)。
Code
#include<bits/stdc++.h>
#define il inline
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
using namespace std;
namespace asbt{
const int maxn=2e3+5,inf=0x7f7f7f7f;
int n,m,h[maxn],p[maxn],c[maxn],a[maxn],b[maxn];
int I[maxn],J[maxn],ll[maxn][2],rr[maxn][2],f[maxn][maxn];
priority_queue<pii> q1[maxn],q2[maxn];
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>h[i];
}
for(int i=1;i<=m;i++){
cin>>p[i]>>c[i]>>a[i]>>b[i];
I[i]=J[i]=i;
}
sort(I+1,I+m+1,[](int x,int y){return a[x]<a[y];});
sort(J+1,J+m+1,[](int x,int y){return b[x]>b[y];});
for(int i=1;i<=m;i++){
ll[i][0]=ll[i][1]=rr[i][0]=rr[i][1]=p[i];
while(ll[i][0]>1&&h[ll[i][0]-1]>=a[i]){
ll[i][0]--;
}
while(rr[i][0]<n&&h[rr[i][0]+1]>=a[i]){
rr[i][0]++;
}
while(ll[i][1]>1&&h[ll[i][1]-1]<=b[i]){
ll[i][1]--;
}
while(rr[i][1]<n&&h[rr[i][1]+1]<=b[i]){
rr[i][1]++;
}
}
memset(f,0x7f,sizeof(f));
for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++){
int x=I[i],y=J[j];
if(p[x]<ll[y][1]||p[x]>rr[y][1]||p[y]<ll[x][0]||p[y]>rr[x][0]){
continue;
}
if(a[y]<a[x]&&b[x]>b[y]){
continue;
}
int l=max(ll[x][0],ll[y][1]),r=min(rr[x][0],rr[y][1]);
if(l==1&&r==n){
f[x][y]=0;
}
else if(a[y]<a[x]){
f[x][y]=f[y][y];
}
else if(b[x]>b[y]){
f[x][y]=f[x][x];
}
else{
while(q1[x].size()){
int t=q1[x].top().sec;
if(p[t]<l||p[t]>r||b[t]<a[x]||a[t]>b[y]){
q1[x].pop();
}
else{
break;
}
}
if(q1[x].size()){
f[x][y]=min(f[x][y],-q1[x].top().fir);
}
while(q2[y].size()){
int t=q2[y].top().sec;
if(p[t]<l||p[t]>r||b[t]<a[x]||a[t]>b[y]){
q2[y].pop();
}
else{
break;
}
}
if(q2[y].size()){
f[x][y]=min(f[x][y],-q2[y].top().fir);
}
}
if(f[x][y]<inf){
q1[x].push(mp(-f[x][y]-c[y],y));
q2[y].push(mp(-f[x][y]-c[x],x));
}
}
}
for(int i=1;i<=m;i++){
if(h[p[i]]<a[i]||h[p[i]]>b[i]||f[i][i]>=inf){
cout<<-1<<'\n';
}
else{
cout<<f[i][i]+c[i]<<'\n';
}
}
return 0;
}
}
int main(){return asbt::main();}
2025CSP-S模拟赛38
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 80 | 20 | 10 | - | 110 | 9/21 |
A. 黑暗料理
首先可以发现所有的 \(1\) 只能留一个。然后我们考虑将和为质数的 \(x\) 和 \(y\) 连边,此时 \(x+y\) 一定为奇数,也就是 \(x\) 和 \(y\) 奇偶性不同,于是没有奇环。此时我们要求最大独立集,跑匈牙利即可。需要 Miller-Rabin。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int prm[]={2,3,5,7,11};
int T,n,m,a[755],mch[755];
bool vis[755];
vector<int> e[755];
il int qpow(int x,int y,int p){
int res=1;
while(y){
if(y&1){
res=res*1ll*x%p;
}
x=x*1ll*x%p,y>>=1;
}
return res;
}
il bool mlrb(int x){
if(x<=11){
for(int i:prm){
if(x==i){
return 1;
}
}
return 0;
}
int t=x-1,k=0;
while(t%2==0){
t>>=1,k++;
}
for(int i:prm){
int a=qpow(i,t,x);
for(int j=1;j<=k;j++){
int b=a*1ll*a%x;
if(b==1&&a!=1&&a!=x-1){
return 0;
}
a=b;
}
if(a!=1){
return 0;
}
}
return 1;
}
il bool dfs(int u){
for(int v:e[u]){
if(vis[v]){
continue;
}
vis[v]=1;
if(mch[v]==-1||dfs(mch[v])){
mch[v]=u;
return 1;
}
}
return 0;
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
cin>>n,m=0;
bool flag=0;
for(int i=1,x;i<=n;i++){
cin>>x;
if(x>1){
a[++m]=x;
}
else if(!flag){
flag=1,a[++m]=x;
}
}
for(int i=1;i<=m;i++){
for(int j=i+1;j<=m;j++){
if(mlrb(a[i]+a[j])){
e[i].pb(j),e[j].pb(i);
// cout<<i<<' '<<j<<'\n';
}
}
}
int ans=0;
for(int i=1;i<=m;i++){
mch[i]=-1;
}
for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++){
vis[j]=0;
}
ans+=dfs(i);
}
cout<<m-ans/2<<'\n';
for(int i=1;i<=m;i++){
e[i].clear();
}
}
return 0;
}
}
int main(){return asbt::main();}
B. 爆炸
如果一个炸弹是被横着引爆的,那么贪心地想,它一定要竖着炸。于是对每一行和每一列设点,对于一个炸弹 \((x,y)\),将第 \(x\) 行和第 \(y\) 列连边,于是获得了若干连通块。考虑如果连通块内有环,那么随便引爆环上一个点,一定是能把这个连通块炸完的,直接计算答案即可。如果没有环,那么这一定是一棵树,我们要选择一条边,只保留它的一边,另一边舍去。显然舍去一个叶子节点是最优的,暴力枚举叶子即可。时间复杂度 \(O(nm)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=6e3+5;
int n,m,a,b,cnt,lfn,ans,fa[maxn],f[maxn][maxn],hp[maxn],lf[maxn];
bool tag[maxn],vis[maxn];
string s[maxn];
vector<int> e[maxn];
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void dfs1(int u){
if(vis[u]){
return;
}
hp[++cnt]=u;
vis[u]=1;
if(u<=n){
for(int i=1;i<=m;i++){
if(s[u][i]=='k'&&++f[u][i]==1){
ans++;
}
}
}
else{
for(int i=1;i<=n;i++){
if(s[i][u-n]=='k'&&++f[i][u-n]==1){
ans++;
}
}
}
for(int v:e[u]){
dfs1(v);
}
}
il void dfs2(int u,int fa){
hp[++cnt]=u;
if(e[u].size()==1){
lf[++lfn]=u;
}
if(u<=n){
for(int i=1;i<=m;i++){
if(s[u][i]=='k'&&++f[u][i]==1){
ans++;
}
}
}
else{
for(int i=1;i<=n;i++){
if(s[i][u-n]=='k'&&++f[i][u-n]==1){
ans++;
}
}
}
for(int v:e[u]){
if(v==fa){
continue;
}
dfs2(v,u);
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>a>>b;
for(int i=1;i<=n+m;i++){
fa[i]=i;
}
for(int i=1;i<=n;i++){
cin>>s[i];
s[i]=" "+s[i];
for(int j=1;j<=m;j++){
if(s[i][j]=='b'){
// cout<<i<<' '<<n+j<<'\n';
e[i].pb(n+j),e[n+j].pb(i);
int u=find(i),v=find(n+j);
if(u==v){
tag[u]=1;
}
else{
fa[u]=v,tag[v]|=tag[u];
}
}
}
}
// for(int i=1;i<=n;i++){
// cout<<s[i]<<'\n';
// }
int Ans=0;
for(int i=1;i<=n+m;i++){
if(find(i)==i&&e[i].size()){
// cout<<i<<'\n';
if(tag[i]){
cnt=ans=0;
dfs1(i);
Ans=max(Ans,ans);
}
else{
cnt=lfn=ans=0;
dfs2(i,0);
for(int j=1;j<=lfn;j++){
int u=lf[j];
if(u<=n){
for(int i=1;i<=m;i++){
if(s[u][i]=='k'&&f[u][i]--==1){
ans--;
}
}
}
else{
for(int i=1;i<=n;i++){
if(s[i][u-n]=='k'&&f[i][u-n]--==1){
ans--;
}
}
}
Ans=max(Ans,ans);
if(u<=n){
for(int i=1;i<=m;i++){
if(s[u][i]=='k'&&++f[u][i]==1){
ans++;
}
}
}
else{
for(int i=1;i<=n;i++){
if(s[i][u-n]=='k'&&++f[i][u-n]==1){
ans++;
}
}
}
}
}
for(int j=1;j<=cnt;j++){
int u=hp[j];
if(u<=n){
for(int i=1;i<=m;i++){
f[u][i]=0;
}
}
else{
for(int i=1;i<=n;i++){
f[i][u-n]=0;
}
}
}
}
}
cout<<Ans;
return 0;
}
}
int main(){return asbt::main();}
C. 游戏
发现每个棋子是独立的,考虑算出 \(sg_{i,j}\) 表示棋子初始在 \((i,j)\) 时的 \(SG\) 值,将所有棋子的 \(SG\) 值异或起来就是总的 \(SG\) 值。
发现 \(sg_{i+1,j}\) 不受 \(sg_{i,j}\) 影响,于是倒序枚举每一行。然后对每一行进行分类讨论。记行号为 \(t\)。
本行中有障碍
这一行被这些障碍分成了若干段。于是对于一个位置 \((t,i)\),它只有三种状态:
-
从 \((t-1,i)\) 下来,可以往左、往右或往下走。
-
从 \((t,i-1)\) 过来,可以往右或往下走。
-
从 \((t,i+1)\) 过来,可以往左或往下走。
于是我们考虑预处理出 \(f_{0,i}\) 表示当前只可以往左或往下走时 \((t,i)\) 的 \(SG\) 值,\(f_{1,i}\) 表示当前可以往右或往下走时 \((t,i)\) 的 \(SG\) 值,于是 \(sg_{t,i}=\operatorname{mex}(f_{0,i-1},f_{1,i+1},sg_{t+1,i})\)。
本行中没有障碍
考虑棋子初始在 \((t,i)\),如果第一步先往左走到了 \((t,i-1)\),那么就不能再往右走了,于是 \((t,i-1)\) 的 \(SG\) 值需要 \((t,i-2)\) 的 \(SG\) 值,进一步需要 \((t,i-3)\) 的 \(SG\) 值……以此类推,我们最终需要的是 \((t,i+1)\) 的 \(SG\) 值,此时只能往下走了,是显然的。但是这样我们单次求的时间复杂度是 \(O(m)\) 的,考虑优化。考虑从 \(i-1\) 一路走到 \(i+1\) 的过程,大概是这个样子:

而 \(sg_{i,j}\) 的取值范围是 \([0,3]\),于是我们可以预处理出图中打勾的两个部分。
具体地,定义数组 \(g_{0/1/2/3,j,i}\):
-
\(g_{0,j,i}\) 表示 \((t,1)\) 的 \(SG\) 值为 \(j\),棋子从 \(i\) 移动到 \(1\),\((t,i)\) 的 \(SG\) 值。
-
\(g_{1,j,i}\) 表示 \((t,m)\) 的 \(SG\) 值为 \(j\),棋子从 \(i\) 移动到 \(m\),\((t,i)\) 的 \(SG\) 值。
-
\(g_{2,j,i}\) 表示 \((t,i)\) 的 \(SG\) 值为 \(j\),棋子从 \(1\) 移动到 \(i\),\((t,1)\) 的 \(SG\) 值。
-
\(g_{3,j,i}\) 表示 \((t,i)\) 的 \(SG\) 值为 \(j\),棋子从 \(m\) 移动到 \(i\),\((t,m)\) 的 \(SG\) 值。
于是就可以对每个点进行 \(O(1)\) 计算了。总时间复杂度 \(O(\sum nm)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e3+5;
int T,n,m,sg[maxn][maxn],f[2][maxn],g[4][4][maxn];
string s[maxn];
il int mex(int a=-1,int b=-1,int c=-1){
for(int i=0;;i++){
if(i!=a&&i!=b&&i!=c){
return i;
}
}
}
il void solve1(int t){
#define gt(i) ((i)>m?(i)-m:(i))
memset(f,-1,sizeof(f));
int _1=1;
while(s[t][_1]!='#'){
_1++;
}
for(int l=_1,r;l<m+_1;l=r){
r=l+1;
while(s[t][r]!='#'){
r++;
}
if(r==l+1){
continue;
}
f[0][l+1]=mex(sg[t+1][gt(l+1)]);
for(int i=l+2;i<r;i++){
f[0][i]=mex(f[0][i-1],sg[t+1][gt(i)]);
}
f[1][r-1]=mex(sg[t+1][gt(r-1)]);
for(int i=r-2;i>l;i--){
f[1][i]=mex(f[1][i+1],sg[t+1][gt(i)]);
}
for(int i=l+1;i<r;i++){
sg[t][gt(i)]=mex(f[0][i-1],f[1][i+1],sg[t+1][gt(i)]);
}
}
#undef gt
}
il void solve2(int t){
if(m==1){
sg[t][1]=mex(sg[t+1][1]);
return ;
}
for(int i:{0,1,2,3}){
g[0][i][1]=g[1][i][m]=g[2][i][1]=g[3][i][m]=i;
}
for(int i=2;i<=m;i++){
for(int j:{0,1,2,3}){
g[0][j][i]=mex(g[0][j][i-1],sg[t+1][i]);
}
}
for(int i=m-1;i;i--){
for(int j:{0,1,2,3}){
g[1][j][i]=mex(g[1][j][i+1],sg[t+1][i]);
}
}
for(int i=2;i<=m;i++){
for(int j:{0,1,2,3}){
g[2][j][i]=g[2][mex(j,sg[t+1][i-1])][i-1];
}
}
for(int i=m-1;i;i--){
for(int j:{0,1,2,3}){
g[3][j][i]=g[3][mex(j,sg[t+1][i+1])][i+1];
}
}
for(int i=1;i<=m;i++){
int l,r;
if(i<m){
int tmp=g[3][mex(sg[t+1][i+1])][i+1];
if(i==1){
l=tmp;
}
else{
l=g[0][mex(tmp,sg[t+1][1])][i-1];
}
}
else{
l=g[0][mex(sg[t+1][1])][i-1];
}
if(i>1){
int tmp=g[2][mex(sg[t+1][i-1])][i-1];
if(i==m){
r=tmp;
}
else{
r=g[1][mex(tmp,sg[t+1][m])][i+1];
}
}
else{
r=g[1][mex(sg[t+1][m])][i+1];
}
sg[t][i]=mex(l,r,sg[t+1][i]);
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s[i];
s[i]=" "+s[i]+s[i];
}
for(int i=1;i<=n+1;i++){
for(int j=1;j<=m;j++){
sg[i][j]=-1;
}
}
for(int i=n;i;i--){
for(int j=1;j<=m;j++){
if(s[i][j]=='#'){
solve1(i);
goto togo;
}
}
solve2(i);
togo:;
}
int ans=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(s[i][j]=='B'){
ans^=sg[i][j];
}
}
}
cout<<(ans?'A':'B')<<'\n';
}
return 0;
}
}
int main(){return asbt::main();}
D. 公司
2025CSP-S模拟赛39
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 100 | 0 | 30 | 20 | 150 | 10/19 |
A. poohfrog
直接 bfs 即可,时间复杂度 \(O(nmk)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=6e6+5,inf=1e9;
const int dx[]={-1,1,0,0,0,0};
const int dy[]={0,0,-1,1,0,0};
const int dz[]={0,0,0,0,-1,1};
int n,m,kk,xx,yy,zz,hd=1,tl=0,dep[185][185][185];
bool vis[185][185][185];
char s[185][185][185];
struct node{
int x,y,z;
node(int x=0,int y=0,int z=0):x(x),y(y),z(z){}
}q[maxn];
int main(){
// freopen("ex_poohfrog.in","r",stdin);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>kk>>xx>>yy>>zz;
for(int k=1;k<=kk;k++){
for(int i=1;i<=n;i++){
cin>>s[k][i]+1;
}
}
memset(dep,0x3f,sizeof(dep));
vis[xx][yy][zz]=1,dep[xx][yy][zz]=0;
q[++tl]=node(xx,yy,zz);
while(hd<=tl){
node u=q[hd++];
int x=u.x,y=u.y,z=u.z;
// cout<<x<<' '<<y<<' '<<z<<'\n';
for(int i=0;i<=5;i++){
int nx=x+dx[i],ny=y+dy[i],nz=z+dz[i];
if(nx&&nx<=n&&ny&&ny<=m&&nz&&nz<=kk&&!vis[nx][ny][nz]&&s[nz][nx][ny]=='.'){
vis[nx][ny][nz]=1,dep[nx][ny][nz]=dep[x][y][z]+1;
q[++tl]=node(nx,ny,nz);
}
}
}
int ans=inf;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
ans=min(ans,dep[i][j][kk]);
}
}
cout<<ans*2;
// cerr<<'\n'<<clock()*1.0;
return 0;
}
}
int main(){return asbt::main();}

浙公网安备 33010602011771号