20240726题解
abcd做题目名称真的是
a
题面:有长度为\(n\)的自然数序列\(a\),\(m=\text{mex}(a)\),问是否存在\(l,r,k\)(\(1\le l\le r\le n\wedge k\ge 0\)),使得将\(a_l,a_{l+1},\dots,a_r\)变成\(k\)后满足\(\text{mex}(a)=m+1\)。
题解:要使\(m+1\)在序列中不存在,且\(m\)在序列中存在,那么令\(m+1\)出现最早和最晚的位置为\(l\)和\(r\)。再判断新序列是否存在\(x\in[1,m)\)。
特殊情况:\(\text{mex}(a)=n\),此时不满足。
时间复杂度\(\Theta(\sum n)\)
代码:
#include<cstdio>
#include<cstring>
const int N=1000005;
int T,n,a[N],mex,tmp;
bool vis[N];
inline int min(int x,int y){return x<y?x:y;}
inline int max(int x,int y){return x>y?x:y;}
int main(){
for(freopen("a.in","r",stdin),freopen("a.out","w",stdout),scanf("%d",&T);T--;){
scanf("%d",&n),memset(vis,0,n+2);
for(int i=1;i<=n;i++)scanf("%d",a+i),vis[a[i]]=true;
for(int i=0;i<=n;i++)if(!vis[i]){
mex=i;
break;
}
if(vis[mex+1]){
int l=n,r=1;
for(int i=1;i<=n;i++)if(a[i]==mex+1)l=min(l,i),r=max(r,i);
for(int i=l;i<=r;i++)a[i]=mex;
memset(vis,0,n+2);
for(int i=1;i<=n;i++)vis[a[i]]=true;
for(int i=0;i<=n;i++)if(!vis[i]){
tmp=i;
break;
}
puts(tmp==mex+1?"Yes":"No");
}
else puts(mex!=n?"Yes":"No");
}
return fflush(stdout),fclose(stdin),fclose(stdout),0;
}
b
有\(n\)个数组\(c_i\),长度均为\(m\),初始值为\(c_{i,j}=b_j\)。有两种操作:
操作一:\(1<i<j<n\),\(a_i\leftarrow a_i-1\),\(a_j\leftarrow a_j-1\),\(a_{i-1}\leftarrow a_{i-1}+1\),\(a_{j+1}\leftarrow a_{j+1}+1\)
操作二:\(1<i<j<n-1\),\(a_i\leftarrow a_i-1\),\(a_j\leftarrow a_j-1\),\(a_{i-1}\leftarrow a_{i-1}+1\),\(a_{j+2}\leftarrow a_{j+2}+1\)
已知\(c_k\)经历了\(x\)操作二,其他序列经历了若干次操作一,求\(k\)和\(x\)。
题解:已知一序列设为\(0,0,0,0,0,0,0,0\)
经历操作一后序列为\(0,1,-1,0,-1,1,0,0\)
前缀和为\(0,1,0,0,-1,0,0,0\)
经历操作一后序列为\(0,1,-1,0,-1,0,1,0\)
前缀和为\(0,1,0,0,-1,-1,0,0\)
经历操作一后前缀和的和不变,经历操作二后前缀和的和减一,求\(c_i\)的前缀和的和即可。
时间复杂度\(\Theta(\sum nm)\)
代码:
#include<cstdio>
#include<cstring>
#define int long long
const int N=1000005;
int n,m,T,v[N],sum[N];
inline int pos(int x,int y){
return (x-1)*m+y;
}
signed main(){
for(freopen("b.in","r",stdin),freopen("b.out","w",stdout),scanf("%lld",&T);T--;){
scanf("%lld%lld",&n,&m),memset(v,0,sizeof(v)),memset(sum,0,sizeof(sum));
for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){
scanf("%lld",v+pos(i,j));
if(j^1)v[pos(i,j)]+=v[pos(i,j-1)];
sum[i]+=v[pos(i,j)];
}
sum[n+1]=sum[1];
for(int i=1;i<=n;i++)if(sum[i]<sum[i+1]){
printf("%lld %lld\n",i,sum[i+1]-sum[i]);
break;
}
}
return fflush(stdout),fclose(stdin),fclose(stdout),0;
}
c
题面:有\(n\)个点的完全图,每个点有一个由N
或者Y
组成的长度为\(m\)的字符串,两点之间边权为字符串不同的地方的数量,求最小生成树。
题解:将字符串转化成二进制表示,定义\(f(i,j)\)表示与状态\(i\)有\(j\)个字母不同且作为点上的字符串,双向搜索转移状态即可。
其中长度为奇数的距离拆成\(k\)和\(k+1\),长度为偶数的距离拆成两个\(k\)。
最后先搜索到的两点之间距离使用kruskal
算法即可。
复杂度\(O(2^m\times m)\)
代码:
#include<cstdio>
#include<cstring>
#define int long long
const int N=300005;
int T,n,m,v[N],fa[N],size[N],ans,f[N][40];
char s[N];
int find(int x){return x^fa[x]?fa[x]=find(fa[x]):x;}
inline void swap(int&x,int&y){x^=y^=x^=y;}
bool merge(int x,int y){
x=find(x),y=find(y);
if(x==y)return false;
if(size[x]<size[y])swap(x,y);
fa[y]=x,size[x]+=size[y];
return true;
}
signed main(){
for(freopen("c.in","r",stdin),freopen("c.out","w",stdout),scanf("%lld",&T);T--;){
scanf("%lld%lld",&n,&m),memset(v+1,0,n<<3),ans=0,memset(f,0,sizeof(f));
for(int i=1;i<=n;i++){
scanf("%s",s+1),fa[i]=i,size[i]=1;
for(int j=1;j<=m;j++)v[i]=(v[i]<<1)|(s[j]=='Y');
f[v[i]][0]=i;
}
int cnt=n-1;
for(int i=0;i<=m/2;i++){
for(int j=0;j<1<<m;j++)if(f[j][i])for(int k=0;k<m;k++)if(f[j^(1<<k)][i]&&merge(f[j][i],f[j^(1<<k)][i]))ans+=(i<<1)+1,cnt--;
if(!cnt)break;
for(int j=0;j<1<<m;j++)if(f[j][i])for(int k=0;k<m;k++){
if(f[j^(1<<k)][i+1]){
if(merge(f[j][i],f[j^(1<<k)][i+1]))ans+=(i<<1)+2,cnt--;
}
else f[j^(1<<k)][i+1]=f[j][i];
}
if(!cnt)break;
}
printf("%lld\n",ans);
fflush(stdout);
}
return fflush(stdout),fclose(stdin),fclose(stdout),0;
}