20260627 比赛 总结
重生之我们机房被 GPT 入侵了 .jpg
重生之所有人都会 H 了就我不会 .jpg
重生之所有人都会 J 了就我不会 .jpg
重生之怎么有 *1500 的蓝题 .jpg
达成成就:无伤 AK。
好耶!
A - Mocha and Math
Tag:位运算,贪心。
首先 & 运算符有个特点,就是任何一个数 & 上另外任何一个数后,得到的新结果一定不大于原来的数。那么就贪一下让每个数和尽可能多的数做 &,因为没有限制次数,所以事实上任何 \(a_i\) 和 \(a_j\) 都有机会做 &。
于是最后整个数组就变成一个数也就是整个 \(a\) 序列 & 起来的结果。显然这个就是最小的最大值。
时间复杂度:\(O(Tn)\)。
戳这里看代码
#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;
int T,n,a[105],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;
}
int main(){
T=read();
while(T--){
n=read();
for(int i=1;i<=n;i++)a[i]=read();
Ans=a[1];
for(int i=2;i<=n;i++)Ans&=a[i];
cout<<Ans<<"\n";
}
return 0;
}
B - 选数
Tag:DFS,爆搜,线性筛。
首先发现 \(k\) 个数的和的上限是 \(10^8\),用线性筛随便预处理一下这以内的数的质数合数情况。
接着发现 \(n\) 只有 \(20\),那 \(2^n\) 能过去不是随便搞,写个 DFS 搜一下所有的情况,然后把和是质数的累加答案就行。
时间复杂度:\(O(V + 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 V = 1e8+5;
const int up = 100000000;
int n,k,a[25],p[V],cnt,Ans;
bool vis[V];
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 init(){
vis[0]=1,vis[1]=1;
for(LL i=2;i<=up;i++){
if(!vis[i])p[++cnt]=i;
for(LL j=1;j<=cnt&&i*p[j]<=up;j++){
vis[i*p[j]]=1;
if(i%p[j]==0)break;
}
}return;
}
void DFS_(int u,int cc,int sum){
if(u>n){
if(cc==k&&!vis[sum])Ans++;
return;
}
if(cc<k)DFS_(u+1,cc+1,sum+a[u]);
DFS_(u+1,cc,sum);return;
}
int main(){
n=read(),k=read();
init();
for(int i=1;i<=n;i++)a[i]=read();
DFS_(1,0,0);
cout<<Ans<<"\n";
return 0;
}
C - 装箱问题
Tag:DP,背包 DP。
发现是典型的背包,但是没有价值了,取而代之的是要求容量尽可能大。
考虑 bool 类型的 DP,令 \(dp_j\) 表示是否存在一种装箱方式使箱子内被占用空间恰好为 \(j\)。随便背包一下就行了,由于这里做了滚动数组,所以 \(j\) 需要倒着枚举。
最后就是找到尽可能大的使 \(dp_x = \text{true}\) 的 \(x\) 并输出 \(V-x\) 即可。
时间复杂度:\(O(nV)\)。
戳这里看代码
#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 M = 2e4+5;
int m,n,a[35];bool dp[M];
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(){
m=read(),n=read();
for(int i=1;i<=n;i++)a[i]=read();
dp[0]=1;
for(int i=1;i<=n;i++)
for(int j=m;j>=a[i];j--)dp[j]|=dp[j-a[i]];
for(int i=m;i>=0;i--)
if(dp[i]){cout<<m-i<<"\n";break;}
return 0;
}
D - Sereja and Suffixes
Tag:枚举,离线,桶。
暴力处理每个询问的话时间复杂度是平方的,完全无法接受,但是发现每次要处理的是一段后缀,于是考虑把所有询问都读进来,然后按照 \(l\) 降序排序,随便拿个桶维护一下就行。最后再按询问顺序恢复顺序输出即可。
时间复杂度:\(O(Q \log Q + 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;
struct ask{int x,id,ans;}q[N];
int n,Q,a[N],cnt,t[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;
}
bool cmpx(ask q1,ask q2){return q1.x>q2.x;}
bool cmpid(ask q1,ask q2){return q1.id<q2.id;}
void add(int x){
t[a[x]]++;
if(t[a[x]]==1)cnt++;
return;
}
int main(){
n=read(),Q=read();
for(int i=1;i<=n;i++)a[i]=read();
for(int i=1;i<=Q;i++)
q[i].x=read(),q[i].id=i,q[i].ans=0;
sort(q+1,q+Q+1,cmpx);
int now=n;
for(int i=1;i<=Q;i++){
while(now>=q[i].x)add(now),now--;
q[i].ans=cnt;
}
sort(q+1,q+Q+1,cmpid);
for(int i=1;i<=Q;i++)cout<<q[i].ans<<"\n";
return 0;
}
E - .. (Double Dots)
Tag:BFS,构造。
首先你跑个 BFS 把 \(i\) 到 \(1\) 的距离 \(dis_i\) 求出来,然后发现如果有一个路标 \(i \to j\)(\(i\) 和 \(j\) 之间一定有边),则一定有 \(dis_i = dis_j + 1\)。于是再枚举一下边的情况即可,如果某个 \(i\) 不存在一个满足条件的 \(j\) 那么就无解了。
时间复杂度:\(O(n+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 = 2e5+5;
int n,m,to[N],dis[N];
bool vis[N],Flag;
vector<int> g[N];
queue<int> 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_soldis(int St){
memset(dis,0x3f,sizeof(dis));
q.push(St),vis[St]=1,dis[St]=0;
while(!q.empty()){
int u=q.front();q.pop();
for(int v:g[u])if(!vis[v])
dis[v]=dis[u]+1,vis[v]=1,q.push(v);
}return;
}
int main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
int x=read(),y=read();
g[x].pb(y),g[y].pb(x);
}BFS_soldis(1);
for(int i=2;i<=n;i++){
for(int u:g[i])if(dis[u]+1==dis[i])to[i]=u;
if(!to[i])Flag=1;
}
if(Flag){cout<<"No\n";return 0;}
cout<<"Yes\n";
for(int i=2;i<=n;i++)cout<<to[i]<<"\n";
return 0;
}
F - 斐波那契数列
Tag:矩阵快速幂。
矩阵快速幂板子,随便推一下就能发现矩阵长这样
\begin{bmatrix}
0 & 1 \\
1 & 1
\end{bmatrix}
至少按我的矩阵乘法的写法,这东西长这样。然后做就完了,初始矩阵我给的
\begin{bmatrix}
0 & 1
\end{bmatrix}
因此求的是 \(n-1\) 次方。
时间复杂度:\(O(\log n)\),存在一个 \(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 LL MOD = 1e9+7;
struct Mat{LL xx,yy,a[3][3];}Ans,Bas;
Mat operator * (const Mat &A , const Mat &B){
Mat C;
C.xx=A.xx,C.yy=B.yy;
for(int i=1;i<=A.xx;i++)
for(int j=1;j<=B.yy;j++)C.a[i][j]=0;
for(int i=1;i<=A.xx;i++)
for(int j=1;j<=B.yy;j++)for(int k=1;k<=A.yy;k++)
C.a[i][j]=(C.a[i][j]+A.a[i][k]*B.a[k][j])%MOD;
return C;
}
LL 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()-1;
Ans.a[1][1]=0,Ans.a[1][2]=1;
Bas.a[1][1]=0,Bas.a[1][2]=1,
Bas.a[2][1]=1,Bas.a[2][2]=1;
Ans.xx=1,Ans.yy=2;
Bas.xx=2,Bas.yy=2;
while(n){
if(n&1)Ans=Ans*Bas;
Bas=Bas*Bas,n>>=1;
}
cout<<Ans.a[1][2]<<"\n";
return 0;
}
G - a-Good String
Tag:分治,递归。
某个小糖喵认为直接分治做是 \(O(n^2)\) 的于是它隔了很久很久才写。我不说是谁。
这个东西直接递归做就行了,两种情况,一种是左边纯色右边递归,还有一种是右边纯色左边递归。分别求一下就行。
形式与归并排序是一样的。
时间复杂度:\(O(n \log 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;
int T,n;
string s;
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 Sol(int l,int r,char c){
if(l==r)return (s[l]!=c);
int res1=0,res2=0;
int mid=(l+r)>>1;
for(int i=l;i<=mid;i++)res1+=(s[i]!=c);
for(int i=mid+1;i<=r;i++)res2+=(s[i]!=c);
res1+=Sol(mid+1,r,c+1);
res2+=Sol(l,mid,c+1);
return min(res1,res2);
}
int main(){
T=read();
while(T--){
n=read();
cin>>s;s=" "+s;
cout<<Sol(1,n,'a')<<"\n";
}
return 0;
}
H - Linova and Kingdom
Tag:树的遍历,DFS,性质推导,贪心,排序。
【数据删除】:首先我们令 \(1\) 为根。
:?\(1\) 不是本来就是根吗??
首先需要观察到一个性质,就是如果选了一个点那么它的子树也都会被选。那么考虑一个点不选的贡献就是 \(sz_u - dep_u\)(\(sz_u\) 是以 \(u\) 为根的子树内的节点数量,\(dep_u\) 是 \(u\) 到根的距离),跑一次 DFS 后把这些值塞进一个数组,排序后贪心取最大的 \(n-k\) 个求和即可。
时间复杂度:\(O(n \log 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 = 2e5+5;
LL n,k,dep[N],sz[N],f[N],Ans;
vector<int> g[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;
}
void DFS_sol(int u,int fa){
sz[u]=1,dep[u]=dep[fa]+1;
for(int v:g[u])if(v^fa)
DFS_sol(v,u),sz[u]+=sz[v];
f[u]=sz[u]-dep[u];return;
}
int main(){
n=read(),k=read();
for(int i=1;i<n;i++){
int x=read(),y=read();
g[x].pb(y),g[y].pb(x);
}DFS_sol(1,0);
sort(f+1,f+n+1);
for(int i=n;i>k;i--)Ans+=f[i];
cout<<Ans<<"\n";
return 0;
}
I - 能量项链
Tag:区间 DP。
首先因为题目给的所谓手链是一个环,所以需要破坏成链开两倍。
然后区间 DP,定义 \(dp_{i,j}\) 表示考虑了 \([i,j]\) 的最大能量值,每次更新时候考虑中间值 \(i \le k < j\),新增价值就是 \(a_i \times b_k \times b_j\) 了。
由于破环成链了,所以最后统计答案的时候要考虑起点为 \(1 \sim n\) 的 \(n\) 种情况,取 \(\max\) 即可。
时间复杂度:\(O(n^3)\),有一个 \(4\) 的常数。
戳这里看代码
#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 = 205;
struct node{int x,y;}a[N];
int n,dp[N][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;
}
int main(){
n=read();
for(int i=1;i<=n;i++)
a[i].x=read(),a[i-1].y=a[i].x;
a[n].y=a[1].x;
for(int i=1;i<=n;i++)a[i+n]=a[i];
for(int len=2;len<=n;len++)
for(int i=1;i+len-1<=2*n;i++){
int j=i+len-1;
for(int k=i;k<j;k++)
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+a[i].x*a[k].y*a[j].y);
}
for(int i=1;i<=n;i++)Ans=max(Ans,dp[i][i+n-1]);
cout<<Ans<<"\n";
return 0;
}
J - Moving to the Capital
Tag:BFS,DFS,DP,贪心。
【数据删除】:所以最后跑个分层图就行了。
【数据删除】:我写了个 Tarjan。
Joler:?你们在干嘛?不是写个 DFS 跑一遍就行了吗?怎么分层图这种高科技都出来了??
:对啊对啊。
首先跑个 BFS 把 \(dep\) 数组求出来,这个是简单的。
然后发现第二种走法,也就是走到 \(d_i\) 更小的点,只会在最后一步,因为再走一定更劣。
那么考虑 \(dp_i\) 表示节点 \(i\) 的答案,写个 DFS,首先把所有相邻的没去过的 \(dep_v > dep_u\) 的 \(v\) 节点遍历一通。然后更新答案,对于一个 \(u\) 枚举其所有相邻节点 \(v\),如果 \(dep_v > dep_u\) 则 \(dp_u \gets \min(dp_u , dp_v)\),如果 \(dep_v \le dep_u\) 则 \(dp_u \gets \min(dp_u , dep_v)\)。
最后的 \(dp\) 数组就是答案,输出即可。
戳这里看代码
#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 T,n,m,dis[N],dp[N];
bool vis[N];
vector<int> g[N];
queue<int> 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_soldis(int St){
while(!q.empty())q.pop();
memset(dis,0x3f,sizeof(dis));
memset(vis,0,sizeof(vis));
q.push(St),vis[St]=1,dis[St]=0;
while(!q.empty()){
int u=q.front();q.pop();
for(int v:g[u])if(!vis[v])
dis[v]=dis[u]+1,vis[v]=1,q.push(v);
}return;
}
void DFS_soldp(int u){
vis[u]=1,dp[u]=dis[u];
for(int v:g[u]){
if(!vis[v]&&dis[v]>dis[u])DFS_soldp(v);
if(dis[v]>dis[u])dp[u]=min(dp[u],dp[v]);
else dp[u]=min(dp[u],dis[v]);
}return;
}
int main(){
T=read();
while(T--){
n=read(),m=read();
for(int i=1;i<=n;i++)g[i].clear();
for(int i=1;i<=m;i++){
int x=read(),y=read();
g[x].pb(y);
}BFS_soldis(1);
memset(vis,0,sizeof(vis));
DFS_soldp(1);
for(int i=1;i<=n;i++)
cout<<dp[i]<<" ";cout<<"\n";
}
return 0;
}
K - Fullmetal Bitchemist
Tag:前缀和,取模,容斥,惊人的注意力。
:首先需要惊人的注意力。
【数据删除】:注意力惊人。
很显然,像嘎嘎喵这种鸭子(猫?)是无法获得惊人的注意力的。
首先发现在变化的过程中,要么是少两个 \(1\) 多一个 \(0\),要么是少两个 \(0\) 多一个 \(1\)。于是差值的变化一定是 \(+3\) 或者 \(-3\),这启示我们考虑差值 \(\bmod 3\) 的结果,然后发现,当差值是 \(3\) 的倍数时,这个子串就是不合法的。
具体可以将 \(1\) 视作 \(+1\) 并将 \(0\) 视作 \(-1\),这样就只需要考虑和了。
但是直接这样做是错的,因为还有一种类似 \(101010\dots\) 或 \(010101\dots\) 的情况也是不合法的,那么维护一下这种交替出现的末端在哪,也给减掉就行了。
具体实现可以用容斥。
戳这里看代码
#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;
LL T,n,sum[5],now,jt,Ans;
string s;
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(){
T=read();
while(T--){
n=read();
cin>>s;s=" "+s;
sum[0]=1,sum[1]=0,sum[2]=0;//清空
Ans=n*(n+1)/2;now=0,jt=1;
for(int i=1;i<=n;i++){
if(s[i]=='1')now++;else now--;
Ans-=sum[(now%3+3)%3];//%3=0 需减去
sum[(now%3+3)%3]++;//存桶
if(s[i-1]==s[i])jt=i;
Ans-=(i-jt)/2;
}cout<<Ans<<"\n";
}
return 0;
}
总结
今天的题很杂啊。
不管了,反正无伤 AK 就是很爽有没有懂的。
:老师你快加题啊 不然我就去写总结了 .jpg
总结好吃,吃掉总结。

浙公网安备 33010602011771号