【比赛记录】2025CSP+NOIP 冲刺模拟赛合集Ⅳ
HZOJ NOIP2025模拟3
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 100 | 40 | 20 | 12 | 172 | 7/28 |
A. 变形怪
直接记忆化搜索即可。\(x\) 中包含前十个质数时答案最大,为 \(458123\),可以接受。
Code
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
int m;
ll n,a[17];
__gnu_pbds::cc_hash_table<ll,__gnu_pbds::null_type> ans;
il void dfs(ll x){
// cout<<x<<'\n';
if(ans.find(x)!=ans.end()){
return ;
}
ans.insert(x);
if(!x){
return ;
}
for(int i=1;i<=m;i++){
dfs(x/a[i]);
}
}
int main(){
freopen("set.in","r",stdin);
freopen("set.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a[i];
}
sort(a+1,a+m+1);
m=unique(a+1,a+m+1)-a-1;
dfs(n);
cout<<ans.size();
return 0;
}
}
int main(){return asbt::main();}
/*
562949953421312 10
2 3 5 7 11 13 17 19 23 29
*/
B. 忍者小队
设 \(b_x=\sum_{i=1}^{n}[x|S_i]\),可以调和级数求。于是有如果最小值存在则最大值为 \(b_x\),否则最大值也不存在。
记值域为 \(V\)。注意到前七个质数的乘积就超过了 \(V\),所以 \(k=1\) 时答案最多为 \({7\choose6}=7\),显然 \(k\) 更大时答案也不会超过 \(7\)。考虑枚举每个答案是否可行。假设当前枚举到了 \(t\),设 \(f_x\) 表示选出 \(t\) 个数使它们的 \(\gcd=x\) 的方案数,则有:
于是若 \(f_x=0\) 则 \(t\) 不可行,否则可行。时间复杂度 \(O(7V\ln V)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=3e5+5,mod=1e9+7,V=3e5,inf=1e9;
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],fac[maxn],inv[maxn],tong[maxn],f[maxn],g[maxn],ans[maxn];
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 void init(int n=V){
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;
}
}
il int C(int x,int y){
return x<y||y<0?0:fac[x]*1ll*inv[y]%mod*inv[x-y]%mod;
}
int main(){
freopen("sor.in","r",stdin);
freopen("sor.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
tong[a[i]]++;
}
for(int i=1;i<=V;i++){
for(int j=i;j<=V;j+=i){
g[i]+=tong[j];
}
}
init();
memset(ans,0x3f,sizeof(ans));
for(int t=1;t<=7;t++){
for(int i=V;i;i--){
f[i]=C(g[i],t);
for(int j=i<<1;j<=V;j+=i){
sub(f[i],f[j]);
}
if(f[i]){
ans[i]=min(ans[i],t);
}
}
}
for(int i=1;i<=m;i++){
if(ans[i]>=inf){
cout<<-1<<' '<<-1<<'\n';
}else{
cout<<ans[i]<<' '<<g[i]<<'\n';
}
}
return 0;
}
}
int main(){return asbt::main();}
C. 尘埃下的神话
D. 怪盗德基
11.08 HZOJ NOIP2025模拟4
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 100 | 40 | 65 | - | 205 | 4/24 |
听了 zwh 的建议决定记录日期,因为教练老是改比赛名字🐱💻
A. 括号问号
首先考虑对于一个确定的字符串求 \(f(S)\),设 \(dp_{i,j}\) 表示考虑到 \(i\),多出来 \(j\) 个 ( 的方案数,有转移:
考虑对每个子序列求和,设 \(dp_{i,j,k}\) 表示考虑到 \(i\),\(i\) 是当前子序列的第 \(j\) 位,多出来 \(k\) 个 ( 的方案数,转移是类似的。注意到第二维只会从 \(j-1\) 转移到 \(j\),可以直接去掉。然后再前缀和优化一下即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=5e3+5,mod=998244353;
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,f[maxn][maxn];
string s;
int main(){
freopen("bracket.in","r",stdin);
freopen("bracket.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>s;
s=" "+s;
f[0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=i;j++){
if(s[i]=='('){
f[i][j]=pls(f[i-1][j],j?f[i-1][j-1]:0);
}else if(s[i]==')'){
f[i][j]=pls(f[i-1][j],f[i-1][j+1]);
}else{
f[i][j]=pls(f[i-1][j],pls(j?f[i-1][j-1]:0,f[i-1][j+1]));
}
}
}
cout<<f[n][0];
return 0;
}
}
int main(){return asbt::main();}
B. 狗卡
考虑从总贡献中减去损失的贡献,即如果存在英雄在 \(t\) 时刻完成了升级则将贡献减去 \(t\)。
于是我们要做的就是将 \(n\) 个数组 \(a_{i,j}\) 重排到另一个数组 \(b_i\) 中,使得在 \(b\) 中满足 \(a\) 中的顺序,所有前缀和的和最小。考虑两端在 \(a\) 中连续的 \(x\) 和 \(y\),则 \(x\) 在 \(y\) 的前面的充要条件就是 \(x\) 的平均值小于 \(y\)。于是我们要将每个 \(a_i\) 分段,使得每一段的平均值都尽可能的小。考虑 \(a\) 的前缀和数组 \(s\),对于每个 \(i\),我们会有 \(k_i\) 个点 \((j,s_j)\),而两点之间的斜率就是这一段的平均值。考虑最后每一段的平均值都最小,则必然斜率递增,于是我们维护一个下凸包即可。然后就不断将每个 \(i\) 的最前面一段丢进优先队列即可。注意这里不要使用 queue,因为它的底层实现是 deque,内存是分段连续的多个固定大小的数组块,空间占用巨大。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=6e5+5,maxm=1.2e6+5;
int n,b[maxn],s[maxm],p[maxn];
ll m,c[maxm];
vector<int> a[maxn];
struct node{
int i,l,r,len;
ll sum;
node(int i=0,int l=0,int r=-1,ll sum=0):i(i),l(l),r(r),len(r-l+1),sum(sum){}
il bool operator<(const node &x)const{
return sum*x.len>x.sum*len;
}
};
vector<node> d[maxn];
priority_queue<node> q;
int main(){
freopen("dog.in","r",stdin);
freopen("dog.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>b[i];
a[i].resize(b[i]+1);
int top=0;
s[++top]=0;
for(int j=1;j<=b[i];j++){
cin>>a[i][j];
c[j]=c[j-1]+a[i][j];
while(top>1&&(c[j]-c[s[top]])*(s[top]-s[top-1])<=(c[s[top]]-c[s[top-1]])*(j-s[top])){
top--;
}
s[++top]=j;
}
for(int j=2;j<=top;j++){
d[i].pb(node(i,s[j-1]+1,s[j],c[s[j]]-c[s[j-1]]));
}
q.push(d[i].front()),p[i]=1;
}
// puts("666");
// return 0;
ll ans=0,cur=0,sum=0;
while(q.size()){
node t=q.top();
q.pop();
for(int i=t.l;i<=t.r;i++){
ans+=cur*a[t.i][i],cur++,sum+=a[t.i][i];
}
if(p[t.i]<d[t.i].size()){
q.push(d[t.i][p[t.i]]),p[t.i]++;
}
}
cout<<ans+(m-sum)*cur;
return 0;
}
}
signed main(){return asbt::main();}
C. 均衡区间
首先求出 \(i\) 左/右侧第一个比 \(a_i\) 大/小的位置 \(sl_i,gl_i,sr_i,gr_i\)。于是对于一个合法的区间 \([i,j]\),必然有 \(i<\min(sl_j,gl_j)\land j>\max(sr_i,gr_i)\)。而这个条件显然也是充分的,直接二维数点即可。
Code
#include<bits/stdc++.h>
#define ll long long
#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=1e6+5,inf=2e9;
int n,T,a[maxn],sl[maxn],gl[maxn],sr[maxn],gr[maxn],s[maxn],ans[maxn];
pii b[maxn];
struct{
#define lowbit(x) (x&-x)
int tr[maxn];
il void clear(){
memset(tr,0,sizeof(tr));
}
il void add(int p,int x){
for(;p<=n+1;p+=lowbit(p)){
tr[p]+=x;
}
}
il int query(int p){
int res=0;
for(;p;p-=lowbit(p)){
res+=tr[p];
}
return res;
}
#undef lowbit
}F;
il void work(){
int t=0;
s[0]=0;
for(int i=1;i<=n;i++){
while(t&&a[s[t]]>=a[i]){
t--;
}
sl[i]=s[t];
s[++t]=i;
}
t=0;
for(int i=1;i<=n;i++){
while(t&&a[s[t]]<=a[i]){
t--;
}
gl[i]=s[t];
s[++t]=i;
}
s[0]=n+1,t=0;
for(int i=n;i;i--){
while(t&&a[s[t]]>=a[i]){
t--;
}
sr[i]=s[t];
s[++t]=i;
}
t=0;
for(int i=n;i;i--){
while(t&&a[s[t]]<=a[i]){
t--;
}
gr[i]=s[t];
s[++t]=i;
}
// for(int i=1;i<=n;i++){
// cout<<i<<' '<<sl[i]<<' '<<gl[i]<<' '<<sr[i]<<' '<<gr[i]<<'\n';
// }
for(int i=1;i<=n;i++){
b[i]=mp(min(sl[i],gl[i]),i);
}
sort(b+1,b+n+1);
int p=n;
F.clear();
for(int i=n;i;i--){
while(p&&b[p].fir>i){
F.add(b[p--].sec,1);
}
ans[i]=F.query(n+1)-F.query(max(sr[i],gr[i]));
}
}
int main(){
freopen("interval.in","r",stdin);
freopen("interval.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>T;
for(int i=1;i<=n;i++){
cin>>a[i];
}
work();
for(int i=1;i<=n;i++){
cout<<ans[i]<<' ';
}
cout<<'\n';
reverse(a+1,a+n+1);
work();
for(int i=n;i;i--){
cout<<ans[i]<<' ';
}
return 0;
}
}
int main(){return asbt::main();}
D. 喵了个喵了个喵
11.10 HZOJ NOIP2025模拟5
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 100 | 60 | 100 | - | 260 | 5/20 |
A. 家具运输
显然当 \(w\) 增加时,运输次数一定不会变多,于是二分答案,模拟运输的过程即可。时间复杂度 \(O(n^2\log n)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e3+5;
int n,m,a[maxn];
bool b[maxn];
il bool chk(){
for(int i=1;i<=n;i++){
if(!b[i]){
return 1;
}
}
return 0;
}
il bool check(int x){
memset(b,0,sizeof(b));
int cnt=0;
while(chk()){
int sum=0;
for(int j=1;j<=n;j++){
if(!b[j]&&sum+a[j]<=x){
b[j]=1,sum+=a[j];
}
}
cnt++;
}
return cnt<=m;
}
int main(){
freopen("trans.in","r",stdin);
freopen("trans.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
int l=0,r=4e6;
for(int i=1;i<=n;i++){
cin>>a[i];
l=max(l,a[i]);
}
sort(a+1,a+n+1,greater<>());
while(l<r){
int mid=(l+r)>>1;
if(check(mid)){
r=mid;
}else{
l=mid+1;
}
}
cout<<l;
return 0;
}
}
int main(){return asbt::main();}
B. 取石子
考虑如果总和为奇数,则第一步取 \(1\) 即可获胜;否则双方不停取偶数,可以递归到 \(k\gets\lfloor\frac{k}{2}\rfloor,a_i\gets\lfloor\frac{a_i}{2}\rfloor\)。
于是我们有先手必胜的充要条件:\(\bigoplus a_i\not\equiv0\pmod{2^{\lfloor\log k\rfloor}}\)。
考虑第一步之后的异或和,假设其 \(lowbit\) 为 \(2^t\),我们显然希望第一步取的值小于 \(2^t\),否则后手就可以取 \(2^t\),先手必败。于是我们枚举取的位置和 \(t\),找出需要取多少能使第 \(t\) 位一下都变成 \(0\) 即可。有可能会算重,去重即可,时间复杂度线性对数方。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define pii pair<int,int>
#define mp make_pair
#define fir first
#define sec second
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=5e4+5;
int n,m,a[maxn];
vector<pii> ans;
int main(){
freopen("nim.in","r",stdin);
freopen("nim.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
int sum=0;
for(int i=1;i<=n;i++){
cin>>a[i];
sum^=a[i];
}
for(int i=0;1<<i<=m;i++){
if(sum>>i&1){
cout<<1<<'\n';
goto togo;
}
}
cout<<0;
return 0;
togo:;
for(int i=1;i<=n;i++){
sum^=a[i];
// cout<<i<<' '<<(bitset<4>)sum<<'\n';
for(int t=1;t<=30;t++){
int d1=a[i]&((1<<t)-1);
int d2=sum&((1<<t)-1);
int tmp=d1>d2?d1-d2:d1+(1<<t)-d2;
if(tmp<=a[i]&&tmp<=m){
ans.pb(mp(i,tmp));
}
}
sum^=a[i];
}
sort(ans.begin(),ans.end());
ans.erase(unique(ans.begin(),ans.end()),ans.end());
for(pii x:ans){
cout<<x.fir<<' '<<x.sec<<'\n';
}
return 0;
}
}
int main(){return asbt::main();}
C. 选取字符串
考虑 kmp,连边 \(nxt_i\to i\),于是我们得到了一棵树,每个点的所有祖先就是他的 border。于是对于每个点 \(u\),我们可以计算 \(p,q\) 中有 \(u\) 的答案:\((2\times dep_u-1)\times{sz_u\choose k}\)。时间复杂度 \(O(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=1e6+5,mod=998244353;
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;
}
int n,m,nxt[maxn],sz[maxn],fac[maxn],inv[maxn],ans;
string s;
vector<int> e[maxn];
il int C(int x,int y){
// cout<<x<<' '<<y<<'\n';
return x<y||y<0?0:fac[x]*1ll*inv[x-y]%mod*inv[y]%mod;
}
il void dfs(int u,int dep){
sz[u]=1;
for(int v:e[u]){
dfs(v,dep+1);
sz[u]+=sz[v];
}
// cout<<u<<' '<<dep<<' '<<2*dep-1<<' '<<sz[u]<<' '<<C(sz[u],m)<<' '<<(2*dep-1)*1ll*C(sz[u],m)<<'\n';
ans=((2*dep-1)*1ll*C(sz[u],m)+ans)%mod;
}
int main(){
freopen("string.in","r",stdin);
freopen("string.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>m>>s;
n=s.size(),s=" "+s;
e[0].pb(1);
for(int i=2,j=0;i<=n;i++){
while(j&&s[i]!=s[j+1]){
j=nxt[j];
}
if(s[i]==s[j+1]){
j++;
}
nxt[i]=j;
e[j].pb(i);
}
// for(int i=1;i<=n;i++){
// cout<<nxt[i]<<' ';
// }
// cout<<'\n';
fac[0]=1;
for(int i=1;i<=n+1;i++){
fac[i]=fac[i-1]*1ll*i%mod;
}
inv[n+1]=qpow(fac[n+1]);
for(int i=n+1;i;i--){
inv[i-1]=inv[i]*1ll*i%mod;
}
// for(int i=0;i<=n;i++){
// for(int j=0;j<=i;j++){
// cout<<C(i,j)<<' ';
// }
// cout<<'\n';
// }
dfs(0,1);
// for(int i=0;i<=n;i++){
// cout<<sz[i]<<' ';
// }
// cout<<'\n';
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
D. 蚂蚁搬家
先假设有解。
考虑操作序列的最后一条边 \((u,v)\),其必然为两棵树的重边,否则就无法操作。进一步考虑,将这条边删去(\(u,v\) 合并)后,操作序列的倒数第二条边 \((u',v')\) 必然也是重边。于是我们考虑倒着做,每次将重边删去。
那么做法实际上就很显然了:每次找到一条重边 \((u,v)\),将两点用并查集并起来,然后将二者连出的边合并到同一点上。注意到合并的次数很多,启发式合并即可。使用哈希表维护每个点的临边和图上的重边,时间复杂度 \(O(n\log n)\)。
如果这个过程中没有重边了那就无解。
Code
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define ll long long
#define il inline
#define ull unsigned ll
#define pii pair<int,int>
#define fir first
#define sec second
#define mp make_pair
using namespace std;
namespace asbt{
const int maxn=1e6+5;
int n,fa[maxn];
__gnu_pbds::gp_hash_table<int,__gnu_pbds::null_type> st[maxn];
__gnu_pbds::gp_hash_table<ull,__gnu_pbds::null_type> S;
queue<pii> q;
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il ull hash(int u,int v){
if(u>v){
swap(u,v);
}
return u*1llu*maxn+v;
}
il void addedge(int u,int v){
st[u].insert(v),st[v].insert(u);
ull ha=hash(u,v);
if(S.find(ha)!=S.end()){
q.push(mp(u,v));
}else{
S.insert(ha);
}
}
int main(){
freopen("ants.in","r",stdin);
freopen("ants.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1,u,v;i<=2*n-2;i++){
cin>>u>>v;
addedge(u,v);
}
for(int i=1;i<=n;i++){
fa[i]=i;
}
while(--n){
if(q.empty()){
cout<<"NO";
return 0;
}
int u=find(q.front().fir),v=find(q.front().sec);
q.pop();
if(st[u].size()>st[v].size()){
swap(u,v);
}
st[v].erase(u),S.erase(hash(u,v));
for(int x:st[u]){
if(x==v){
continue;
}
st[x].erase(u);
S.erase(hash(u,x));
addedge(v,x);
}
fa[u]=v;
}
cout<<"YES";
return 0;
}
}
int main(){return asbt::main();}
11.11 2025noip模拟赛73
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 100 | - | 20 | 20 | 140 | 4/7 |
久违的原神启动场啊(bushi
A. 饥饿的狐狸
最小值显然是极差,从水的温度向下走一遍再向上走一遍就好了。
考虑最大值,先不考虑喝水,我们希望每一段都尽量被经过更多次,每次交替选择最大的和最小的饼干即可。加上喝水,那就每次都将是否喝水取个最大值。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,m,a[maxn];
ll ans;
int main(){
// freopen("a25.in","r",stdin);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
int mn=m,mx=m;
for(int i=1;i<=n;i++){
cin>>a[i];
mn=min(mn,a[i]);
mx=max(mx,a[i]);
}
cout<<mx-mn<<' ';
sort(a+1,a+n+1);
ll res=0;
for(int i=1,j=n,x=m;i<=j;i++,j--){
res+=max(abs(x-a[i]),abs(m-a[i]));
if(i<j){
res+=max(abs(a[i]-a[j]),abs(m-a[j]));
}
x=a[j];
}
ans=max(ans,res),res=0;
for(int i=1,j=n,x=m;i<=j;i++,j--){
res+=max(abs(x-a[j]),abs(m-a[j]));
if(i<j){
res+=max(abs(a[i]-a[j]),abs(m-a[i]));
}
x=a[i];
}
ans=max(ans,res);
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
B. 保险箱
如果 \(x,y\) 是密码,则由裴蜀定理有 \(\gcd(x,n),\gcd(x,y)\) 必然是密码。设密码集合为 \(S\),\(g=\gcd(S_1,S_2,\dots S_p,n)\),于是 \(S\) 就是 \(g\) 的所有倍数。于是我们考虑枚举所有可能的 \(g\) 检查是否合法。合法的充要条件也是显然的,即为 \(\forall i\in[1,k-1],g\nmid m_i\)。设 \(\gcd(n,m_k)=d\),直接做是 \(O(\sigma(d)k)\) 的,其中 \(\sigma(d)\) 表示 \(d\) 的约数个数,大概是 \(10^4\) 量级。考虑优化,我们考虑提前将 \(m_i\) 的所有因数全都找出来打上标记。这看似没有优化,但进一步,我们标记所有 \(m_i\) 的因数是没有用的,因为我们只需要判断所有 \(d\) 的因数是否可行,于是令 \(m_i\gets\gcd(m_i,d)\)。此时我们可以使用记忆化搜索,并且提前将 \(d\) 的所有质因数都找出来。时间复杂度 \(O(k\log d+\sqrt{d}+\sigma(d)\omega(d))\),其中 \(\omega(d)\) 表示 \(d\) 的不同质因子个数,是 \(O(\log d)\) 的。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=2.5e5+5;
int n,m,a[maxn];
vector<int> prm;
set<int> vis;
il void div(int x){
for(int i=2;i<=x/i;i++){
if(x%i==0){
prm.pb(i);
while(x%i==0){
x/=i;
}
}
}
if(x>1){
prm.pb(x);
}
}
il void dfs(int x){
if(vis.count(x)){
return ;
}
vis.insert(x);
for(int p:prm){
if(x%p==0){
dfs(x/p);
}
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>m>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
int d=__gcd(a[n],m);
div(d);
for(int i=1;i<n;i++){
dfs(__gcd(a[i],d));
}
int i=1;
for(;i<=d/i;i++){
if(d%i==0&&!vis.count(i)){
cout<<m/i;
return 0;
}
}
for(i--;i;i--){
if(d%i==0&&!vis.count(d/i)){
cout<<m/(d/i);
return 0;
}
}
return 0;
}
}
signed main(){return asbt::main();}
C. 追逐
设 \(f_{u,i,0/1}\) 表示从根走到 \(u\) 的子树中,\(u\) 子树内部用了 \(i\) 个磁铁,\(u\) 有没有用磁铁的最大答案。注意这里我们实际上统计的是以根为开头的链,这样转移就不用考虑前效性:
其中 \(b_u\) 表示 \(u\) 的所有儿子的 \(a\) 之和。
时间复杂度 \(O(n^2V)\),于是再换根即可。需要记录转移过程中的最大值和次大值。
Code
#include<bits/stdc++.h>
#define int long long
#define il inline
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e5+5;
int n,m,a[maxn],b[maxn],f[maxn][105][2],hp[105][2];
vector<int> e[maxn];
struct{
int mx1,mx2;
il void insert(int x){
if(x>=mx1){
mx2=mx1,mx1=x;
}else if(x>mx2){
mx2=x;
}
}
}g[maxn][105];
il void dfs1(int u,int fa){
for(int v:e[u]){
if(v==fa){
continue;
}
dfs1(v,u),b[u]+=a[v];
for(int i=1;i<=m;i++){
g[u][i].insert(max(f[v][i][0],f[v][i][1]));
f[u][i][0]=max({f[u][i][0],f[v][i][0],f[v][i][1]});
}
}
for(int i=1;i<=m;i++){
f[u][i][1]=f[u][i-1][0]+b[u];
}
}
il void dfs2(int u,int fa){
for(int v:e[u]){
if(v==fa){
continue;
}
int bb=b[u]-a[v];
b[v]+=a[u];
for(int i=1;i<=m;i++){
if(max(f[v][i][0],f[v][i][1])==g[u][i].mx1){
hp[i][0]=g[u][i].mx2;
}else{
hp[i][0]=g[u][i].mx1;
}
hp[i][1]=hp[i-1][0]+bb;
g[v][i].insert(max(hp[i][0],hp[i][1]));
f[v][i][0]=max({f[v][i][0],hp[i][0],hp[i][1]});
f[v][i][1]=f[v][i-1][0]+b[v];
}
dfs2(v,u);
}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1,u,v;i<n;i++){
cin>>u>>v;
e[u].pb(v),e[v].pb(u);
}
dfs1(1,0),dfs2(1,0);
int ans=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=m;j++){
ans=max({ans,f[i][j][0],f[i][j][1]});
}
}
cout<<ans;
return 0;
}
}
signed main(){return asbt::main();}
D. 字符串
将 C 记为 \(1\),T 记为 \(-1\)。
首先考虑暴力,显然贪心,先正着扫,如果到某个位置前缀和变为负数,就将它删掉;然后再同样倒着扫一遍。
考虑怎么快速完成这个过程。给这个区间做前缀和和后缀和,分别记为 \(pre\) 和 \(suf\)。先考虑第一步正着扫,我们要将所有的负的前缀和变成非负数,也就是会在第一次出现 \(-1,-2,-3\dots\) 的地方进行加一操作,操作的次数即为 \(-\min\{pre_p\}\),记为 \(-pre_{\min}\)。然后考虑第二步倒着扫。对于每个位置 \(q\),我们考虑第一步对 \(suf_q\) 的影响。在 \(<q\) 的位置上进行加一的次数是显然的 \(-\min_{p<q}\{pre_p\}\),于是在 \(q\) 后面加一的次数即为 \(\min_{p<q}\{pre_p\}-pre_{\min}\)。于是新的后缀和 \(suf'_q=suf_q+(\min_{p<q}\{pre_p\}-pre_{\min})\)。于是两步相加,答案即为 \(-\min_{p<q}\{pre_p+suf_q\}\)。
然后可以发现这个式子实际上就是区间最大子段和减去区间和,线段树维护一下即可。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define lid id<<1
#define rid id<<1|1
using namespace std;
namespace asbt{
const int maxn=5e5+5;
int n,m;
string s;
struct node{
int sum,lm,rm,mm;
node(int sum=0,int lm=0,int rm=0,int mm=0):sum(sum),lm(lm),rm(rm),mm(mm){}
il node operator+(const node &x)const{
return node(sum+x.sum,max(lm,sum+x.lm),max(rm+x.sum,x.rm),max({mm,x.mm,rm+x.lm}));
}
}tr[maxn<<2];
il void pushup(int id){
tr[id]=tr[lid]+tr[rid];
}
il void build(int id,int l,int r){
if(l==r){
int x=s[l-1]=='C'?1:-1;
tr[id]=node(x,max(x,0),max(x,0),max(x,0));
return ;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
pushup(id);
}
il node query(int id,int L,int R,int l,int r){
if(L>=l&&R<=r){
return tr[id];
}
int mid=(L+R)>>1;
if(r<=mid){
return query(lid,L,mid,l,r);
}
if(l>mid){
return query(rid,mid+1,R,l,r);
}
return query(lid,L,mid,l,r)+query(rid,mid+1,R,l,r);
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>s>>m;
build(1,1,n);
while(m--){
int l,r;
cin>>l>>r;
node ans=query(1,1,n,l,r);
cout<<ans.mm-ans.sum<<'\n';
}
return 0;
}
}
int main(){return asbt::main();}
11.12 HZOJ NOIP2025模拟6
| A | B | C | D | Sum | Rank |
|---|---|---|---|---|---|
| 60 | 20 | - | 40 | 120 | 17/29 |
A. 汉谟拉比(crazy)
设 \(b_i=\sum_{j=1}^{n}[a_j>i]\)。于是我们可以 DP:设 \(f_{i,j}\) 表示当前分了 \(i\) 份,和为 \(j\) 的最小答案,于是有转移:
直接转移是 \(O(nm^2)\) 的。考虑此时每一份都是等价的,于是可以进行分治:
- 若 \(n\) 为奇数,递归到 \(n-1\),再 \(O(m^2)\) 暴力加入一个。
- 若 \(n\) 为偶数,递归到 \(\frac{n}{2}\),再自己跟自己卷积一下,时间复杂度也是 \(O(m^2)\) 的。
于是时间复杂度 \(O(m^2\log n)\)。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define uprb upper_bound
using namespace std;
namespace asbt{
const int maxn=5e3+5,inf=1e9;
int n,m,a[maxn],b[maxn],f[maxn],g[maxn];
il void solve(int n){
if(!n){
memset(f,0x3f,sizeof(f));
f[0]=0;
return ;
}
if(n&1){
solve(n-1);
memset(g,0x3f,sizeof(g));
for(int i=0;i<=m;i++){
for(int j=0;j<=i;j++){
g[i]=min(g[i],f[i-j]+b[j]);
}
}
memcpy(f,g,sizeof(g));
}else{
solve(n>>1);
memset(g,0x3f,sizeof(g));
for(int i=0;i<=m;i++){
for(int j=0;j<=m-i;j++){
g[i+j]=min(g[i+j],f[i]+f[j]);
}
}
memcpy(f,g,sizeof(g));
}
}
int main(){
freopen("crazy.in","r",stdin);
freopen("crazy.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
}
sort(a+1,a+n+1);
for(int i=0;i<=m;i++){
b[i]=n-(uprb(a+1,a+n+1,i)-a-1);
}
solve(n);
int ans=inf;
for(int i=0;i<=m;i++){
ans=min(ans,f[i]);
}
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
B. 虫洞折跃(flip)
设 \(f_{x,y}\) 表示走到 \((x,y)\) 的最小操作次数。于是我们有最基本的转移:
这里我们不用考虑 \(rc>1\) 的限制,因为在一般情况下,路径上一个单独的 \(1\) 可以和路径外的一些点一起取反。
然后我们需要进行一些特判。
- 首先是 \(n=1\) 的情况,如果只有一个 \(1\),则需要将它和旁边的一段 \(0\) 一起取反,然后再将那一段 \(0\) 再取一次反;否则有多少段连续的 \(1\) 就操作多少次。不过在 \(m\le 3\) 的情况下这不一定能成立,特判掉即可。\(m=1\) 同理。
- 然后考虑如果左下角是 \(1\),且它的两侧都是 \(0\),有可能又需要类似地进行两次取反。不过如果它的右边还有 \(1\),则只需要操作一次即可。右上角同理。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=1e3+5;
const int dx[]={0,1},dy[]={1,0};
int T,n,m,a[maxn][maxn],f[maxn][maxn];
int main(){
freopen("flip.in","r",stdin);
freopen("flip.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>T;
while(T--){
cin>>n>>m;
if(n==1||m==1){
if(n>m){
swap(n,m);
}
for(int i=1;i<=m;i++){
cin>>a[1][i];
}
if(m==1){
if(a[1][1]){
cout<<"Impossible\n";
}else{
cout<<0<<'\n';
}
}else if(m==2){
if(a[1][1]!=a[1][2]){
cout<<"Impossible\n";
}else{
cout<<(a[1][1]?1:0)<<'\n';
}
}else if(m==3){
switch(a[1][1]<<2|a[1][2]<<1|a[1][3]){
case 0b000:{
cout<<0<<'\n';
break;
}case 0b001:{
cout<<2<<'\n';
break;
}case 0b010:{
cout<<3<<'\n';
break;
}case 0b011:{
cout<<1<<'\n';
break;
}case 0b100:{
cout<<2<<'\n';
break;
}case 0b101:{
cout<<2<<'\n';
break;
}case 0b110:{
cout<<1<<'\n';
break;
}default:{
cout<<1<<'\n';
break;
}
}
}else{
int cnt1=0,cnt2=0;
for(int i=1;i<=m;i++){
if(a[1][i]){
int j=i;
while(j<m&&a[1][j+1]){
j++;
}
cnt1+=j-i+1,cnt2++,i=j;
}
}
cout<<(cnt1==1?2:cnt2)<<'\n';
}
}else{
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
cin>>a[i][j];
}
}
memset(f,0x3f,sizeof(f));
f[1][1]=a[1][1];
for(int x=1;x<=n;x++){
for(int y=1;y<=m;y++){
for(int i:{0,1}){
int xx=x+dx[i],yy=y+dy[i];
if(xx>n||yy>m){
continue;
}
int w=0;
if(a[x][y]==a[xx][yy]||a[x][y]){
w=0;
}else if(xx==n&&yy==1){
for(int j=2;j<=m;j++){
if(a[n][j]){
w=1;
goto togo1;
}
}
w=2;
togo1:;
}else if(xx==1&&yy==m){
for(int j=2;j<=n;j++){
if(a[j][m]){
w=1;
goto togo2;
}
}
w=2;
togo2:;
}else{
w=1;
}
f[xx][yy]=min(f[xx][yy],f[x][y]+w);
}
}
}
cout<<f[n][m]<<'\n';
}
}
return 0;
}
}
int main(){return asbt::main();}
C. 深巢温泉(dnspring)
总数总是不很好求,不妨求期望 \(E\),最后再乘 \([(2m+1)\frac{n(n+1)}{2}]^q\) 即可。
考虑对每个位置分别求出贡献。首先对于一个操作,位置 \(x\) 被选中的概率为:
设 \(f_{x,y}\) 表示 \(x\) 位置在第 \(y\) 次操作后的值,考虑修改操作,取 \(\min\) 和 \(\max\) 操作放在一起考虑,等价于有 \(\frac{1}{2}\) 的概率变成 \(0\sim m-1\) 中的任意值,\(\frac{1}{2}\) 的概率不变。于是有转移:
设 \(r_x=1-\frac{p_xm}{2m+1}\)。于是有:
设 \(f_{x,y}+\lambda=r_x(f_{x,y-1}+\lambda)\),于是 \(\lambda=\frac{1-m}{2}\)。于是:
于是:
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
using namespace std;
namespace asbt{
const int maxn=2e5+5,mod=1e9+7;
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;
}
int n,m,q,p[maxn],r[maxn];
int main(){
freopen("dnspring.in","r",stdin);
freopen("dnspring.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>q;
int ans=0;
for(int i=1;i<=n;i++){
p[i]=i*2ll*(n-i+1)%mod*qpow(n*1ll*(n+1)%mod)%mod;
r[i]=(1+mod-p[i]*1ll*m%mod*qpow(2*m+1)%mod)%mod;
ans=(p[i]*1ll*(m-1)%mod*qpow(4*m+2)%mod*(q+mod-(qpow(r[i],q)-1+mod)*1ll*qpow(r[i]-1+mod)%mod)+ans)%mod;
}
cout<<ans*1ll*qpow((2*m+1)*1ll*n%mod*(n+1)%mod*qpow(2)%mod,q)%mod;
return 0;
}
}
int main(){return asbt::main();}
D. 逃离冰场(skate)
称由于蹬了一脚而出现的冰为生成冰。有这样的结论:我们不会两次撞到生成冰上。因为每个空格走到另一个空格都可以通过先蹬出去,再蹬回来两步完成,因此如果我们像走到这个生成冰旁边的某个位置,不如当时就两步走过去。
于是我们考虑图论建模。每个「可以通过的点」向四连通的「可以通过的点」连一条边权为 \(2\) 的边,再向四个方向的第一块冰连一条边权为 \(1\) 的边,然后跑最短路即可。由于边权只有 \(1\) 和 \(2\),可以将 \(2\) 拆成两个 \(1\) 和一个虚点,于是跑 bfs 即可。
但是这样在 AT 上过不了,因为空间卡的比较死。但是我懒得写了
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ID(x,y) (n*((y)-1)+(x))
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e3+5,maxm=5e6+5,inf=1e9;
const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};
int n,m,r1,c1,r2,c2,dis[maxm];
bool vis[maxm];
string s[maxn];
vector<int> e[maxm];
queue<int> q;
int main(){
freopen("skate.in","r",stdin);
freopen("skate.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s[i];
s[i]=" "+s[i];
}
cin>>r1>>c1>>r2>>c2;
int cnt=n*m;
for(int x=1;x<=n;x++){
for(int y=1;y<=m;y++){
if(s[x][y]=='.'){
for(int i:{0,1,2,3}){
int xx=x+dx[i],yy=y+dy[i];
if(xx>0&&xx<=n&&yy>0&&yy<=m&&s[xx][yy]=='.'){
e[ID(x,y)].pb(++cnt);
e[cnt].pb(ID(xx,yy));
}
}
}else{
for(int i:{0,1,2,3}){
for(int xx=x+dx[i],yy=y+dy[i];xx>0&&xx<=n&&yy>0&&yy<=m&&s[xx][yy]=='.';xx+=dx[i],yy+=dy[i]){
e[ID(xx,yy)].pb(ID(x+dx[i],y+dy[i]));
}
}
}
}
}
memset(dis,0x3f,sizeof(dis));
dis[ID(r1,c1)]=0,q.push(ID(r1,c1)),vis[ID(r1,c1)]=1;
while(q.size()){
int u=q.front();
q.pop();
for(int v:e[u]){
if(!vis[v]){
dis[v]=dis[u]+1,q.push(v),vis[v]=1;
}
}
}
cout<<(dis[ID(r2,c2)]>=inf?-1:dis[ID(r2,c2)]);
return 0;
}
}
int main(){return asbt::main();}
upd:空间卡过了。建四连通的边时只向右面和下面连边即可减少一半的虚点。
Code
#include<bits/stdc++.h>
#define ll long long
#define il inline
#define ID(x,y) (n*((y)-1)+(x))
#define pb push_back
using namespace std;
namespace asbt{
const int maxn=1e3+5,maxm=3e6+5,inf=1e9;
const int dx[]={0,0,1,-1};
const int dy[]={1,-1,0,0};
int n,m,r1,c1,r2,c2,dis[maxm],q[maxm];
bool vis[maxm];
string s[maxn];
vector<int> e[maxm];
int main(){
freopen("skate.in","r",stdin);
freopen("skate.out","w",stdout);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>s[i];
s[i]=" "+s[i];
}
cin>>r1>>c1>>r2>>c2;
int cnt=n*m;
for(int x=1;x<=n;x++){
for(int y=1;y<=m;y++){
if(s[x][y]=='.'){
for(int i:{0,3}){
int xx=x+dx[i],yy=y+dy[i];
if(xx>0&&xx<=n&&yy>0&&yy<=m&&s[xx][yy]=='.'){
e[ID(x,y)].pb(++cnt);
e[cnt].pb(ID(xx,yy));
e[cnt].pb(ID(x,y));
e[ID(xx,yy)].pb(cnt);
}
}
}else{
for(int i:{0,1,2,3}){
for(int xx=x+dx[i],yy=y+dy[i];xx>0&&xx<=n&&yy>0&&yy<=m&&s[xx][yy]=='.';xx+=dx[i],yy+=dy[i]){
e[ID(xx,yy)].pb(ID(x+dx[i],y+dy[i]));
}
}
}
}
}
memset(dis,0x3f,sizeof(dis));
int hd=1,tl=0;
dis[ID(r1,c1)]=0,q[++tl]=ID(r1,c1),vis[ID(r1,c1)]=1;
while(hd<=tl){
int u=q[hd++];
for(int v:e[u]){
if(!vis[v]){
dis[v]=dis[u]+1,q[++tl]=v,vis[v]=1;
}
}
}
cout<<(dis[ID(r2,c2)]>=inf?-1:dis[ID(r2,c2)])<<'\n';
return 0;
}
}
int main(){return asbt::main();}

浙公网安备 33010602011771号