2025-11-07 NOIP 模拟赛3 赛后总结
赛时 Record
- 8:11 读 T1。
- 8:39 会了。发现自己糖糖了,T1 想了四十分钟。
- 8:56 对拍了 \(60\) 组没有问题。开 T2。
- 9:13 T2 好水啊。把速度压一下塞状态里建分层图就成 dij 板子了。
- 9:41 过掉 T2 大洋里。发现自己连边连到层数上去了,/bangbangt。
- 9:46 想到 T3 矩乘做法。把 \(a\times b\) 压进一行,然后处理出来转移矩阵,然后快速幂,然后就做完了。
- 10:10 发现 \(op\) 是 \(0,1,2\) 而不是 \(1,2,3\)。
- 10:46 卡了半个小时的常,极限数据还是只有 \(4.1\) 秒。结果发现 \(a\times b>25\) 的点不超过 \(3\) 个。
- 11:03 疑似树上启发式合并。但我不会。
- 11:06 哦我草 Nanatsukaze 新歌真空都市真好听。
- 11:15 放弃正解,打了 70pts 暴力跑路了。
结果 T4 暴力没取模挂完了。
结果是 100+100+100+0。
T1 序列异或
题意
给定长为 \(n\) 的序列 \(a\),求满足 \(a_i\oplus a_j\oplus a_k\oplus a_l=0\) 的四元组 \((i,j,k,l)\) 的数量。
赛时
糖糖了,想了半天 \(O(n^2V)\) 解法。
题解
\(a_i\oplus a_j\oplus a_k\oplus a_l=0\) 显然等价于 \(a_i\oplus a_j = a_k\oplus a_l\)。
所以对左边维护一个桶,记录所有二元组的异或的出现次数,每次对右边暴力扫一遍匹配即可。
时间复杂度 \(O(n^2)\)。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3f
using namespace std;
int n;
int a[5010];
long long bkt[1<<20];
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
long long ans=0;
for(int i=2;i<=n-2;i++){
for(int j=1;j<i;j++) bkt[a[i]^a[j]]++;
for(int j=i+2;j<=n;j++) ans+=bkt[a[i+1]^a[j]];
}
cout<<ans<<"\n";
# ifndef ONLINE_JUDGE
cerr<<"\nUsed time: "<<clock()*1.0/CLOCKS_PER_SEC<<"s.\n";
# endif
return 0;
}
T2 自驾游
题意
有 \(n\) 个点,\(m\) 条单向边,有边权 \(d\)。初始速度为 \(v=1\),有 \(p\) 个特殊点,在特殊点 \(x_i\) 上可以花费 \(c_i\) 的时间,使速度翻倍,即 \(v\gets 2v\)。
以速度 \(v\) 经过边权为 \(d\) 的边花费的时间为 \(\lceil \frac{d}{v}\rceil\)。
每条边有特殊属性 \(w\)。
- \(w=0\):经过这条边后速度不会变化。
- \(w=1\):经过这条边后速度变为 \(v=1\)。
求 \(s\) 到 \(t\) 的最短路。
赛时
一眼盯出把速度设进状态然后建分层图。
结果看了半天发现自己连边连错了。
题解
边权不超过 \(10^6\)。不难发现当速度足够大(\(2^{20}\))之后,经过一条边的时间是不会变的。
所以我们可以把当前点拆成 \(21\) 层(\(0\) 到 \(20\)),第 \(k\) 层表示走下一条边的速度为 \(2^k\)。
特殊属性为 \(1\) 的边显然都连向第 \(0\) 层的点,特殊点的速度翻倍自然就是向上一层连边。
所以建成分层图后跑一边最短路就行了。
时间复杂度 \(O(m\log n\log V)\)。
注意空间开够。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3f
using namespace std;
// kn+b -> speed=v*2^k, id=b.
struct edge{int to,nxt;long long w;}e[2000010];int head[420010],cnt=1;
inline void addedge(int x,int y,long long w){e[cnt]={y,head[x],w},head[x]=cnt++;}
int n,m,S,T,adt;
struct node{
int x;long long v;
bool operator<(const node&_Q)const{return v>_Q.v;}
};priority_queue<node> q;
long long d[420010];
bool vis[420010];
void dij(){
memset(d,0x3f,sizeof d);
q.push({S,0});
d[S]=0;
while(!q.empty()){
node fr=q.top();q.pop();
int x=fr.x;
if(vis[x]) continue;
vis[x]=1;
for(int i=head[x];i;i=e[i].nxt){
int y=e[i].to;
if(d[y]>d[x]+e[i].w){
d[y]=d[x]+e[i].w;
q.push({y,d[y]});
}
}
}
}
int main(){
ios::sync_with_stdio(false);
cin.tie(0),cout.tie(0);
cin>>n>>m>>S>>T;
for(int i=1;i<=m;i++){
int x,y,w;cin>>x>>y>>w;
char c;cin>>c;
if(c=='G') for(int j=0;j<=20;j++) addedge(j*n+x,j*n+y,(w+(1<<j)-1)/(1<<j));
else for(int j=0;j<=20;j++) addedge(j*n+x,y,(w+(1<<j)-1)/(1<<j));
}
cin>>adt;
for(int i=1;i<=adt;i++){
int x;long long w;cin>>x>>w;
for(int j=0;j<20;j++) addedge(j*n+x,(j+1)*n+x,w);
}
dij();
long long ans=infll;
for(int j=0;j<=20;j++) ans=min(ans,d[j*n+T]);
if(ans==infll) cout<<"-1\n";
else cout<<ans<<"\n";
# ifndef ONLINE_JUDGE
cerr<<"\nUsed time: "<<clock()*1.0/CLOCKS_PER_SEC<<"s.\n";
# endif
return 0;
}
T3 车马象
题意
在大小为 \(a\times b\) 的棋盘上,\((c,d)\) 有一个棋子,可能为车、马、象。
棋子可在棋盘中走 \(e\) 步,问总共有多少种可能的移动序列。
棋子走法和中国象棋中相同。
赛时
看到 \(a\times b\le 100\) 如此神奇的数据范围,第一反应不是矩乘就是状压。
然后想了想怎么转移。
然后发现 \(op\) 是 \(0,1,2\) 而不是 \(1,2,3\)。
卡了半个小时常才发现我的代码在正确的极限数据里只跑了 \(0.17\) 秒。
题解
注意到 \(e\le 10^9\),所以考虑矩乘。
把 \(a\times b\) 的棋盘重标号再拍扁到一维,然后考虑构造转移矩阵。
显然,原棋盘中两个互相可达的点可以互相转移。
所以就做完了。
时间复杂度 \(O(m(ab)^{3}\log e)\)。
魔改了我自己的矩乘缺省源。
#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define infll 0x3f3f3f3f3f3f3f3f
using namespace std;
#ifdef ONLINE_JUDGE
#define getchar getchar_unlocked
#define putchar putchar_unlocked
#endif
template <typename Tp> static inline void read(Tp&x){
int f=1;x=0;
char c=getchar();
while(c<'0'||c>'9') f=(c=='-'?-f:f),c=getchar();
while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
x*=f;
}
template <typename Tp> static inline void print(Tp x){
if(x<0) putchar('-'),x=-x;
if(x<=9) putchar(x^48);
else print(x/10),putchar((x%10)^48);
}
const long long mod=19260817;
struct matrix{
int n,m;
long long a[110][110];
inline matrix(int _n=0,int _m=0){
n=_n,m=_m;
for(int i=1;i<=_n;i++) for(int j=1;j<=_m;j++) a[i][j]=0;
}
inline void reset(int _n=0,int _m=0){*this=matrix(_n,_m);}
inline matrix operator*(const matrix&w)const{
matrix ans(n,w.m);
if(m!=w.n) return ans;
for(int i=1;i<=n;i++) for(int j=1;j<=w.m;j++) for(int k=1;k<=m;k++) ans.a[i][j]=(ans.a[i][j]+a[i][k]*w.a[k][j]%mod)%mod;
return ans;
}
inline void operator*=(const matrix&w){*this=*this*w;}
};
matrix mata,matb;
int main(){
int Q;read(Q);
while(Q--){
int op,a,b,c,d,n;
read(op),read(a),read(b),read(c),read(d),read(n);
mata.reset(1,a*b);
matb.reset(a*b,a*b);
if(op==0){
for(int x=1;x<=a*b;x++){
int xrow=(x-1)/b+1;
int xcol=(x%b==0?b:x%b);
for(int y=x+1;y<=a*b;y++){
int yrow=(y-1)/b+1;
int ycol=(y%b==0?b:y%b);
if(xrow==yrow||xcol==ycol) matb.a[x][y]=matb.a[y][x]=1;
}
}
}else if(op==1){
for(int x=1;x<=a*b;x++){
int xrow=(x-1)/b+1;
int xcol=(x%b==0?b:x%b);
for(int y=x+1;y<=a*b;y++){
int yrow=(y-1)/b+1;
int ycol=(y%b==0?b:y%b);
if((abs(xrow-yrow)==2&&abs(xcol-ycol)==1)||(abs(xrow-yrow)==1&&abs(xcol-ycol)==2)) matb.a[x][y]=matb.a[y][x]=1;
}
}
}else{
for(int x=1;x<=a*b;x++){
int xrow=(x-1)/b+1;
int xcol=(x%b==0?b:x%b);
for(int y=x+1;y<=a*b;y++){
int yrow=(y-1)/b+1;
int ycol=(y%b==0?b:y%b);
if(abs(xrow-yrow)==2&&abs(xcol-ycol)==2) matb.a[x][y]=matb.a[y][x]=1;
}
}
}
mata.a[1][(c-1)*b+d]=1;
while(n){
if(n&1) mata*=matb;
matb*=matb;
n>>=1;
}
long long ans=0;
for(int i=1;i<=a*b;i++) (ans+=mata.a[1][i])%=mod;
print(ans);
putchar('\n');
}
# ifndef ONLINE_JUDGE
cerr<<"\nUsed time: "<<clock()*1.0/CLOCKS_PER_SEC<<"s.\n";
# endif
return 0;
}
总结
T4 场上想到了树上启发式合并。但很显然我不会。
然后去打了 \(O(n^2\log n)\) 的暴力,还忘了取模打挂了。
赛后问了 mhh 和 hxf 结果一个主席树一个线段树合并。但是很显然,我都不会写。
370 -> 300。
遗憾离场。
距离 AK 最近的一次。

浙公网安备 33010602011771号