模拟赛好题分享
2026-10-25 20:29:39
发布于:北京
RT,挑了一些近期我打的模拟赛里的好题分享一下。
难度:CSP-S(少许题目超纲)
会持续更新。
题目 1
定义 是 树:
- 若 ,则为空树;
- 若 ,则为只有一个节点的树;
- 若 ,则为一个节点下连接两个儿子:左儿子是 ,右儿子是 。
现在 A 和 B 在玩游戏,A 先手。每个人可以选择一个子树删除,删掉根节点的人输。
求 里有多少个 A 的必胜点。
。
我们先考虑建出整棵树之后怎么做。删子树不好考虑,改成删子树根节点连向父亲的边,无法操作者输。考虑 SG 函数,令 表示将以 为根节点的子树中的边全部删除的 SG 值。
如果 是叶子,则 ,代表这个点先手必输。
如果 有且只有一个儿子 ,则有 。按照定义,这相当于在 这 个石子上增加了一个石子 。
如果 有多个儿子,按照定理,。其中 是 的边。
这可能不太容易理解,画个图:
u
/ \ / \
o o = o xor o
/ \ / \
o o o o
这样我们只能通过 求出开始是否必胜,所以我们还得枚举第一步的决策,判断删除第一步的子树后对手是否必败。时间复杂度 ,其中 是树的点数。大概是 的级别。
发现题目给出的树比较特殊,或许可以利用斐波那契的性质递推一下。设 表示在 中,删除一个子树(不是根)使得 的方案数。我们需要求 。
为了转移,我们还需要设一个 表示 中的 。
那么有转移:
我们会发现 的转移过程中下标可能变成负数,这意味着,在 的情况下,这个儿子不存在,也就是说我们把 与这个儿子间的边删除了。所以遇到非法情况,我们需要让 。
根据 的边界,有 的边界:
发现 ,时间复杂度 。
难度:紫。
Code:
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e4+4,MOD=998244353;
int n,m=1;
int f[MAXN][MAXN],g[MAXN];
int main(){
cin>>n;
f[2][0]=g[2]=1;
for(int i=3;i<=n;i++){
g[i]=g[i-1]+1^g[i-2]+1;
m=max(g[i],m);
}
for(int i=3;i<=n;i++){
for(int j=0;j<=m;j++){
if(j^g[i-2]+1) f[i][j]=(f[i][j]+f[i-1][(j^g[i-2]+1)-1])%MOD;
else f[i][j]=(f[i][j]+1)%MOD;
if(j^g[i-1]+1) f[i][j]=(f[i][j]+f[i-2][(j^g[i-1]+1)-1])%MOD;
else f[i][j]=(f[i][j]+1)%MOD;
}
}
cout<<f[n][0];
return 0;
}
题目 2
给定一个 的 矩阵,设两点的距离为它们的欧几里得距离的平方。你需要对于每一个点 ,求出上、下、左、右(允许同行同列)的 点中离它最近的那个的距离。不存在则定义为 。
输出量较大,设 上方的最近 点距离为 ,求 。另外三个同理。
。
毫无意义,直接来考虑三次方做法。
四个方向本质相同,我们只讨论 。设 表示第 列上,最靠下的 点的位置。我们按照 的顺序处理每一行,若 ,则更新 。那么 的答案即为 。时间复杂度 。
考虑推一下式子:
设 是自变量,则要求的式子形如 ,斜率优化。发现斜率是 ,单调递减;查询的是 ,单调递增;所以可以单调队列维护下凸包。时间复杂度 。
难度:蓝。
Code:
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const ll MAXN=4e3+5;
struct dque{
ll l,r;
ll dq[MAXN];
dque(){
l=1,r=0;
memset(dq,0,sizeof(dq));
}
bool empty(){return l>r;}
ll size(){return r-l+1;}
ll front(){return dq[l];}
ll ffront(){return dq[l+1];}
ll back(){return dq[r];}
ll bback(){return dq[r-1];}
void pushback(const ll &x){
dq[++r]=x;
return;
}
void popback(){
r--;
return;
}
void popfront(){
l++;
return;
}
};
ll n,m,k,u,d,l,r;
ll a[MAXN][MAXN],s[MAXN],y[MAXN],x[MAXN],res[MAXN];
dque dq;
int main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m;
for(ll i=1;i<=n;i++) for(ll j=1;j<=m;j++) cin>>a[i][j];
//处理up
memset(s,0,sizeof(s));
for(ll i=1;i<=n;i++){
for(ll j=1;j<=m;j++) if(a[i][j]) s[j]=i;//更新每一列最下面的点
dq=dque();
memset(x,0,sizeof(x));
memset(y,0,sizeof(y));
for(ll j=1;j<=m;j++){
x[j]=j;
y[j]=j*j+(i-s[j])*(i-s[j]);
}
for(ll j=1;j<=m;j++){//加直线
if(s[j]==0) continue;
while(dq.size()>1&&(y[j]-y[dq.back()])*(x[dq.back()]-x[dq.bback()])<=(y[dq.back()]-y[dq.bback()])*(x[j]-x[dq.back()])) dq.popback();
dq.pushback(j);
}
for(ll j=1;j<=m;j++){
k=j<<1;
while(dq.size()>1&&y[dq.front()]-k*x[dq.front()]>y[dq.ffront()]-k*x[dq.ffront()]) dq.popfront();
if(!dq.empty()) res[j]=j*j+y[dq.front()]-k*x[dq.front()];
else res[j]=1e9;
u^=res[j];
}
}
//处理down
memset(s,0,sizeof(s));
for(ll i=n;i;i--){
for(ll j=1;j<=m;j++) if(a[i][j]) s[j]=i;//更新每一列最上面的点
dq=dque();
memset(x,0,sizeof(x));
memset(y,0,sizeof(y));
for(ll j=1;j<=m;j++){
x[j]=j;
y[j]=j*j+(i-s[j])*(i-s[j]);
}
for(ll j=1;j<=m;j++){//加直线
if(s[j]==0) continue;
while(dq.size()>1&&(y[j]-y[dq.back()])*(x[dq.back()]-x[dq.bback()])<=(y[dq.back()]-y[dq.bback()])*(x[j]-x[dq.back()])) dq.popback();
dq.pushback(j);
}
for(ll j=1;j<=m;j++){
k=j<<1;
while(dq.size()>1&&y[dq.front()]-k*x[dq.front()]>y[dq.ffront()]-k*x[dq.ffront()]) dq.popfront();
if(!dq.empty()) res[j]=j*j+y[dq.front()]-k*x[dq.front()];
else res[j]=1e9;
d^=res[j];
}
}
//处理left
memset(s,0,sizeof(s));
for(ll i=1;i<=m;i++){
for(ll j=1;j<=n;j++) if(a[j][i]) s[j]=i;//更新每一行最右面的点
dq=dque();
memset(x,0,sizeof(x));
memset(y,0,sizeof(y));
for(ll j=1;j<=n;j++){
x[j]=j;
y[j]=j*j+(i-s[j])*(i-s[j]);
}
for(ll j=1;j<=n;j++){//加直线
if(s[j]==0) continue;
while(dq.size()>1&&(y[j]-y[dq.back()])*(x[dq.back()]-x[dq.bback()])<=(y[dq.back()]-y[dq.bback()])*(x[j]-x[dq.back()])) dq.popback();
dq.pushback(j);
}
for(ll j=1;j<=n;j++){
k=j<<1;
while(dq.size()>1&&y[dq.front()]-k*x[dq.front()]>y[dq.ffront()]-k*x[dq.ffront()]) dq.popfront();
if(!dq.empty()) res[j]=j*j+y[dq.front()]-k*x[dq.front()];
else res[j]=1e9;
l^=res[j];
}
}
//处理right
memset(s,0,sizeof(s));
for(ll i=m;i;i--){
for(ll j=1;j<=n;j++) if(a[j][i]) s[j]=i;//更新每一行最左面的点
dq=dque();
memset(x,0,sizeof(x));
memset(y,0,sizeof(y));
for(ll j=1;j<=n;j++){
x[j]=j;
y[j]=j*j+(i-s[j])*(i-s[j]);
}
for(ll j=1;j<=n;j++){//加直线
if(s[j]==0) continue;
while(dq.size()>1&&(y[j]-y[dq.back()])*(x[dq.back()]-x[dq.bback()])<=(y[dq.back()]-y[dq.bback()])*(x[j]-x[dq.back()])) dq.popback();
dq.pushback(j);
}
for(ll j=1;j<=n;j++){
k=j<<1;
while(dq.size()>1&&y[dq.front()]-k*x[dq.front()]>y[dq.ffront()]-k*x[dq.ffront()]) dq.popfront();
if(!dq.empty()) res[j]=j*j+y[dq.front()]-k*x[dq.front()];
else res[j]=1e9;
r^=res[j];
}
}
cout<<u<<' '<<d<<' '<<l<<' '<<r;
return 0;
}
题目 3
给定 个点 条边的带边权有向图,每个点有开放时间,若当前时间 满足:,则可以从此点出发到其它点。你有两种状态:停止状态和移动状态,初始你是停止状态。如果你处于停止状态的时候想移动,必须额外消耗 时间变成移动状态。移动之后如果你能不停歇地继续移动,则可以不切换为停止状态。
注意,如果在时间 ,点 开启了,并且你处于停止状态,那么无论 时间时点 是否开启,你都可以通过。也就是说,能否通过一个点取决于你准备移动地那一刻。
求 到所有点的最短路。
,,,,,没有重边自环。
首先经过 的时间,所有点的开放状态会回归初始,而 很小,可以计算出这个循环时间为 。
建立分层图,设 表示到达点 ,此时时间为 (满足 ),状态为停止还是移动的最短路。朴素实现复杂度大概是 。不可通过。
发现 也很小,且 ,说明经过一条边之后,循环次数只会 或 。这令我们想到 01bfs。设 表示最小经过循环次数,使用 01bfs 跑即可。答案为 。
时间复杂度:。
难度:青。
Code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXT=2520;
struct node{
int u,tm,s,d;
node(const int &A=0,const int &B=0,const int &C=0,const int &D=0):u(A),tm(B),s(C),d(D){}
};
struct graph{
int nxt,to,w;
graph(const int &A=0,const int &B=0,const int &C=0):nxt(A),to(B),w(C){}
};
int n,m,k,u,v,w,ans,tot=1;
int hd[5005],l[5005],r[5005],t[5005],dist[5005][MAXT][2];
graph edge[5005];
deque<node> dq;
void add(const int &u,const int &v,const int &w){
edge[++tot]=graph(hd[u],v,w);
hd[u]=tot;
return;
}
void dij(){
node u,v;
memset(dist,0x3f,sizeof(dist));
dist[1][0][0]=0;
dq.push_back(node(1,0,0,0));
while(!dq.empty()){
u=dq.front();
dq.pop_front();
if(u.s==0){
v=node(u.u,u.tm+1,0,u.d);
if(v.tm>=MAXT){
v.tm-=MAXT;
v.d++;
}
if(v.d<dist[v.u][v.tm][v.s]){
dist[v.u][v.tm][v.s]=v.d;
if(v.d==u.d) dq.push_front(v);
else dq.push_back(v);
}
}
if(u.tm%t[u.u]<l[u.u]||u.tm%t[u.u]>r[u.u]) continue;
for(int i=hd[u.u];i;i=edge[i].nxt){
v.u=edge[i].to;
v.tm=u.tm+edge[i].w;
if(u.s==0) v.tm+=k;
v.d=u.d;
v.s=0;
if(v.tm>=MAXT){
v.tm-=MAXT;
v.d++;
}
if(v.d<dist[v.u][v.tm][v.s]){
dist[v.u][v.tm][v.s]=v.d;
if(v.d==u.d) dq.push_front(v);
else dq.push_back(v);
}
v.s=1;
if(v.d<dist[v.u][v.tm][v.s]){
dist[v.u][v.tm][v.s]=v.d;
if(v.d==u.d) dq.push_front(v);
else dq.push_back(v);
}
}
}
return;
}
signed main(){
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
cin>>n>>m>>k;
for(int i=1;i<=m;i++){
cin>>u>>v>>w;
add(u,v,w);
}
for(int i=1;i<=n;i++) cin>>l[i]>>r[i]>>t[i];
dij();
for(int i=1;i<=n;i++){
ans=1e18;
for(int j=0;j<MAXT;j++) ans=min({(dist[i][j][0]>=1e9?(int)1e9:dist[i][j][0])*MAXT+j,(dist[i][j][1]>=1e9?(int)1e9:dist[i][j][1])*MAXT+j,ans});
cout<<ans<<' ';
}
return 0;
}
题目 4
给定 个居民,编号 ,再给定 个礼物,编号 。居民 只喜欢要 的倍数的礼物。求有几种分配方案使得每个人都恰好有 个礼物。
,。
假设 ,状压就行。我们只考虑 的做法。
假设居民 拿到了 。如果 ,那么无事发生;如果 ,那么我们接着考虑 怎么办。 肯定还得拿一个 , 也得接着拿,以此类推,最终拿到哪里呢?拿到一个 的数截止。
我们发现 里面的居民可以无事发生,所以先不管;但是 的居民只能拿其他人的。对于 ,假设他拿了 ,则有 ;接着,有 。因此,设最后 ,有 。我们思考这个的方案数怎么求。
设这个倍数的过程,每次乘上了 ,则有:
同时满足 。
我们来求这个的方案数,直接枚举因数再记忆化搜索就可以,时间复杂度 。设分解 的方案数为 。
此时可能会有一个问题,就是两条不同的倍数链会不会相交在某一个点?假设这个点是 ,显然:
所以不会相交。
然后我们发现,这个链的第一个数是 ,但是第二个数应该是 的,所以还需要特意枚举一下每条链的第二个数。应该有 。
那么,我们设 表示,当第一个数是 ,最后一个是 的时候的方案数,容易得到:
时间复杂度是
然后我们就可以考虑状压求解总方案了。设 表示,将 的数作为起点, 集合作为终点的分配方案数。有 。
然后我们考虑转移进来下一个终点 。设 ,有:
时间复杂度:。
难度:紫。
Code:
#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
int n,m,t;
int f[21][21],dp[1<<20];
map<int,int> vis;
void solve1(){
dp[0]=1;
for(int s=0;s<1<<n;s++){
t=__builtin_popcount(s);
for(int i=0;i<n;i++){
if(s>>i&1) continue;
if((m+i+1)%(t+1)) continue;
dp[s|1<<i]=(dp[s|1<<i]+dp[s])%MOD;
}
}
cout<<dp[(1<<n)-1];
return;
}
int dfs(const int &x){
if(vis.count(x)) return vis[x];
int res=1;
for(int i=2;i*i<=x;i++){
if(x%i==0){
res=(res+dfs(x/i))%MOD;
if(i*i<x) res=(res+dfs(i))%MOD;
}
}
return vis[x]=res;
}
void solve2(){
for(int i=1;i<=m;i++){
for(int j=1;j<=m;j++){//begin:i end:n+j
if((n+j)%i) continue;
f[i][j]=dfs((n+j)/i);
for(int k=2;k<=m/i;k++){
if((n+j)/i%k) continue;
f[i][j]=(f[i][j]-dfs((n+j)/i/k)+MOD)%MOD;
}
}
}
dp[0]=1;
for(int s=0;s<1<<m;s++){
t=__builtin_popcount(s);
for(int i=0;i<m;i++){
if(s>>i&1) continue;
dp[s|1<<i]=(dp[s|1<<i]+1ll*dp[s]*f[t+1][i+1])%MOD;
}
}
cout<<dp[(1<<m)-1];
return;
}
int main(){
cin>>n>>m;
if(n<=m) solve1();
else solve2();
return 0;
}
这里空空如也













有帮助,赞一个