20260704 比赛 总结
我是 fvv 我是 fvv 我是 fvv 我是 fvv 我是 fvv 我是 fvv 我是 fvv 我是 fvv 我是 fvv 我是 fvv 我是 fvv 我是 fvv。对。
排名趋势大起大落 .jpg
A - ORXOR
Tag:爆搜,枚举。
注意到 \(n\) 只有 \(20\),\(2^n\) 能过,于是随便写个 DFS 枚举所有可能的情况就行了。
时间复杂度:\(O(2^n)\)。
戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 25;
int n,a[N],Ans;
int read(){
int su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
void DFS_(int u,int sum,int now){
if(u>n){Ans=min(Ans,sum^now);return;}
DFS_(u+1,sum,now|a[u]);
DFS_(u+1,sum^now,a[u]);return;
}
int main(){
n=read();
for(int i=1;i<=n;i++)a[i]=read();
Ans=(1<<30)+1;DFS_(1,0,0);
cout<<Ans<<"\n";
return 0;
}
B - Typical Stairs
Tag:DP。
定义 \(dp_i\) 表示到达第 \(i\) 阶台阶的方案数,正常来讲应该是 \(dp_i = dp_{i-1} + dp_{i-2}\),但是这题有禁忌,所以特判一下就行。具体地,我们令 \(vis_i\) 表示 \(i\) 是否为禁忌,则:
- \(vis_i = 1\):\(dp_i = 0\);
- \(vis_i = 0\) 但 \(vis_{i-1} = 1\) 且 \(vis_{i-2} = 0\):\(dp_i = dp_{i-2}\);
- \(vis_i = 0\) 但 \(vis_{i-1} = 0\) 且 \(vis_{i-2} = 1\):\(dp_i = dp_{i-1}\);
- \(vis_i = 0\) 但 \(vis_{i-1} = 1\) 且 \(vis_{i-2} = 1\):\(dp_i = 0\)。
最后的答案是 \(dp_n\)。记得取模。
时间复杂度:\(O(n)\)。
戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 1e5+5;
const LL MOD = 1e9+7;
LL n,m,dp[N];
bool vis[N];
LL read(){
LL su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
int main(){
n=read(),m=read();
for(int i=1;i<=m;i++)vis[read()]=1;
dp[0]=1;
for(int i=1;i<=n;i++){
if(vis[i])continue;
if(!vis[i-1])dp[i]=(dp[i]+dp[i-1])%MOD;
if(i-2>=0&&!vis[i-2])
dp[i]=(dp[i]+dp[i-2])%MOD;
}cout<<dp[n]<<"\n";
return 0;
}
C - Tenzing and Books
Tag:按位或运算,贪心。
考察或的性质,显然,只要能拿且拿完之后还有机会变成 \(X\),我们就会尽可能拿,然后贪心做一下就行。
至于当前状态 \(now\) 是否还有机会变成 \(X\),只需要判断是否 (now | X) == X 即可。
时间复杂度:\(O(n)\),不过有一个 \(3\) 的常数。
戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 1e5+5;
int T,n,X,now,a[N];
int read(){
int su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
int main(){
T=read();
while(T--){
n=read(),X=read(),now=0;
for(int t=1;t<=3;t++){
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=n;i++){
int nxt=now|a[i];
if((nxt|X)==X)now|=a[i];
else break;
}
}
if(now==X)cout<<"Yes\n";
else cout<<"No\n";
}
return 0;
}
D - 01迷宫
Tag:BFS,连通块,预处理。
罚时原因:
- 存每次遍历的点的数组大小误开成了 \(N\),事实上要开 \(N^2\)。
这题有个坑,就是每次走的是不同色的点而非同色的点。
你从每个没去过的点出发,按照规则走下去,把所有经过的点都记录下来,它们构成一个极大连通块,相互之间到且仅可到,记录其个数 \(cnt\) 并给其中每个点 \((i,j)\) 的 \(ans_{i,j} = cnt\)。
查询的时候直接输出对应的 \(ans_{x,y}\) 就行了,\(O(1)\) 的。
时间复杂度:\(O(n^2 + m)\)。
戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 1005;
int n,m,ans[N][N];
char c[N][N];
bool vis[N][N];
int dx[]={0,1,0,-1},dy[]={1,0,-1,0};
pii tmp[N*N];int tcnt;
queue<pii> q;
int read(){
int su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
void BFS_(int Sx,int Sy){
while(!q.empty())q.pop();
tcnt=0;
q.push({Sx,Sy});
vis[Sx][Sy]=1;
while(!q.empty()){
auto [x,y]=q.front();q.pop();
tmp[++tcnt]={x,y};
for(int i=0;i<4;i++){
int x2=x+dx[i],y2=y+dy[i];
if(x2<1||x2>n||y2<1||y2>n)continue;
if(c[x2][y2]==c[x][y])continue;
if(!vis[x2][y2])vis[x2][y2]=1,q.push({x2,y2});
}
}
for(int i=1;i<=tcnt;i++){
auto [x,y]=tmp[i];
ans[x][y]=tcnt;
}return;
}
int main(){
n=read(),m=read();
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)cin>>c[i][j];
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
if(!vis[i][j])BFS_(i,j);
while(m--){
int x=read(),y=read();
cout<<ans[x][y]<<"\n";
}
return 0;
}
E - Military Problem
Tag:树的遍历,DFS 序(dfn)。
阅读理解题,首先需要硬着头皮把长文阅读结束,然后发现,其实就是让你求一个遍历过程中第 \(k\) 个经过的点的编号。
这个好做,反正遍历的顺序是固定的,我们从根节点 \(1\) 出发,DFS 一下求出每个点的 dfn 以及其子树大小,每次询问的时候直接在 dfn 数组里查找就行了。至于求子树大小是干什么的,嗯,这个是用来判断 \(k\) 是否越界的啦。
时间复杂度:\(O(n + q)\)。
戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2e5+5;
int n,Q,fa[N],sz[N];
int dfn[N],Cnt,p[N];
vector<int> g[N];
int read(){
int su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
void DFS_(int u){
dfn[u]=(++Cnt),p[Cnt]=u,sz[u]=1;
for(int v:g[u])DFS_(v),sz[u]+=sz[v];return;
}
int main(){
n=read(),Q=read();
for(int i=2;i<=n;i++)
fa[i]=read(),g[fa[i]].pb(i);
for(int i=1;i<=n;i++)
sort(g[i].begin(),g[i].end());
DFS_(1);
while(Q--){
int x=read(),k=read();
if(sz[x]<k)cout<<"-1\n";
else cout<<p[dfn[x]+k-1]<<"\n";
}
return 0;
}
F - Transportation
Tag:虚点,最小生成树,Kruskal,分类讨论。
罚时原因:
- 存边的数组大小应该开到 \(2n+m=6 \times 10^5\),但我只开到了 \(n+m = 4 \times 10^5\),导致 RE。
首先看到这种题目,就是明晃晃提示你虚点,但是需要对机场和港口各建一个虚点(分别为 \(n+1\) 和 \(n+2\)),然后跑常规 Kruskal 就是可以的。
但是,显然没这么简单,第二个样例解释提到:
注意,并不要求三种设施都至少建造一次。
哦,完蛋了,那我们需要分类讨论四种情况,分别是:
- 不建机场,也不建港口;
- 建机场,但不建港口;
- 不建机场,但建港口;
- 建机场,也建港口。
对于这四种不同的情况,我是没想到什么好方法能一起解决,只好复制粘贴四遍让代码跑四次 Kruskal,答案取 \(\min\) 即可。当然也能包装成函数,代码会好看一些,但没啥区别。
时间复杂度:\(O(m \log m)\),不过有一个 \(8\) 的常数。
戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 6e5+5;
const LL INF = 0x3f3f3f3f3f3f3f3f;
struct lines{LL u,v,w;}p[N],ln[N];
LL n,m,cnt,A[N],B[N],fa[N];
LL res1=0,res2=0,res3=0,res4=0;
LL read(){
LL su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
bool cmp(lines l1,lines l2){return l1.w<l2.w;}
int FF(int u){return (fa[u]==u?u:fa[u]=FF(fa[u]));}
int main(){
n=read(),m=read();
for(int i=1;i<=n+2;i++)fa[i]=i;
for(int i=1;i<=n;i++)A[i]=read();
for(int i=1;i<=n;i++)B[i]=read();
for(int i=1;i<=m;i++){
LL x=read(),y=read(),w=read();
p[i]={x,y,w};
}
cnt=0;
for(int i=1;i<=m;i++)ln[++cnt]=p[i];
for(int i=1;i<=n;i++)
ln[++cnt]={i,n+1,A[i]};
for(int i=1;i<=n;i++)
ln[++cnt]={i,n+2,B[i]};
sort(ln+1,ln+cnt+1,cmp);
for(int i=1;i<=n+2;i++)fa[i]=i;
for(int i=1;i<=cnt;i++){
auto [x,y,w]=ln[i];
x=FF(x),y=FF(y);
if(x!=y)fa[x]=y,res1+=w;
}
for(int i=1;i<=n+2;i++)
if(FF(i)!=FF(1))res1=INF;
cnt=0;
for(int i=1;i<=m;i++)ln[++cnt]=p[i];
for(int i=1;i<=n;i++)
ln[++cnt]={i,n+1,A[i]};
sort(ln+1,ln+cnt+1,cmp);
for(int i=1;i<=n+1;i++)fa[i]=i;
for(int i=1;i<=cnt;i++){
auto [x,y,w]=ln[i];
x=FF(x),y=FF(y);
if(x!=y)fa[x]=y,res2+=w;
}
for(int i=1;i<=n+1;i++)
if(FF(i)!=FF(1))res2=INF;
cnt=0;
for(int i=1;i<=m;i++)ln[++cnt]=p[i];
for(int i=1;i<=n;i++)
ln[++cnt]={i,n+1,B[i]};
sort(ln+1,ln+cnt+1,cmp);
for(int i=1;i<=n+1;i++)fa[i]=i;
for(int i=1;i<=cnt;i++){
auto [x,y,w]=ln[i];
x=FF(x),y=FF(y);
if(x!=y)fa[x]=y,res3+=w;
}
for(int i=1;i<=n+1;i++)
if(FF(i)!=FF(1))res3=INF;
cnt=0;
for(int i=1;i<=m;i++)ln[++cnt]=p[i];
sort(ln+1,ln+cnt+1,cmp);
for(int i=1;i<=n;i++)fa[i]=i;
for(int i=1;i<=cnt;i++){
auto [x,y,w]=ln[i];
x=FF(x),y=FF(y);
if(x!=y)fa[x]=y,res4+=w;
}
for(int i=1;i<=n;i++)
if(FF(i)!=FF(1))res4=INF;
cout<<min({res1,res2,res3,res4})<<"\n";
return 0;
}
G - Choosing Capital for Treeland
Tag:树形 DP,换根 DP。
首先你建图的时候肯定不会建单向边,肯定还是双向边,但是双向边带有边权,如果这条边与单向边方向相同则边权为 \(0\),否则边权为 \(1\)。
然后你随便跑个 DP 就行,你发现换根最好做,而推一下后发现 \(u\) 和 \(fa\) 的差别仅在于 \(u \to fa\) 这一条边的方向。于是就结束了,最后取个 \(\min\) 就行。
时间复杂度:\(O(n)\),有一个 \(3\) 左右的常数。
戳这里看代码
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define pDD pair<LD,LD>
#define fr first
#define se second
#define pb push_back
#define isr insert
#define _i128 __int128
using namespace std;
const int N = 2e5+5;
int n,dp[N],sum;
vector<pii> g[N];
int read(){
int su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
void DFS_dp(int u,int fa){
for(auto [v,w]:g[u])if(v^fa)
DFS_dp(v,u),dp[u]+=dp[v]+w;return;
}
void DFS_sol(int u,int fa){
for(auto [v,w]:g[u])if(v^fa)
dp[v]=dp[u]+(w==1?-1:1),DFS_sol(v,u);return;
}
int main(){
n=read(),sum=n;
for(int i=1;i<n;i++){
int x=read(),y=read();
g[x].pb({y,0}),g[y].pb({x,1});
}DFS_dp(1,0);DFS_sol(1,0);
for(int i=1;i<=n;i++)sum=min(sum,dp[i]);
cout<<sum<<"\n";
for(int i=1;i<=n;i++)
if(dp[i]==sum)cout<<i<<" ";cout<<"\n";
return 0;
}

浙公网安备 33010602011771号