9.5 上午 becoder 模拟赛总结 & 题解

T1 文本编辑器

说实话,看到题目的第一瞬间,我还以为 gm 第一道就放了平衡树。

一道链表的模板题,当然愿意也可以用平衡树写,不多说了,直接放代码(100pts):

#include<bits/stdc++.h>
using namespace std;
#define N 1000005
char s[N],t[N];
int now,pre[N],nxt[N];
int main(){
    scanf("%s%s",s+1,t+1);
    int n=strlen(s+1);
    for(int i=1;i<=n;i++){
        if(t[i-1]=='L')
            nxt[pre[now]]=i,nxt[i]=now,pre[i]=pre[now],pre[now]=i,now=i;
        else
            pre[nxt[now]]=i,nxt[i]=nxt[now],pre[i]=now,nxt[now]=i,now=i;
        pre[0]=nxt[0]=0;
    }
    while(pre[now])  now=pre[now];
    for(int i=1;i<=n;i++)  printf("%c",s[now]),now=nxt[now];
}

T2 便利店

因为所有边的边权都是 \(1\),所以我们的单源最短路就可以直接 \(O(m)\) 用广搜实现。

那题目就简单了,对于每个询问,先跑一次最短路,用一个 vector 存储每个点的最短路径可以从哪些点走过来,记为 \(pre\)

然后就从终点 dfs 回去,将每个点的 \(pre\) 与它连边,这样我们连的所有边就都是最短路径上的了。

最后再从起点 dfs 一遍,求每个点的概率就行了,就典型的概率 DP,不做赘述。

时间复杂度 \(O(km)\),代码如下(100pts):

#include<bits/stdc++.h>
using namespace std;
#define N 5005
bool vis[N];
queue<int> q;
double now[N],sum[N];
int n,m,k,num,d[N],rd[N];
vector<int> v[N],pre[N],nxt[N];
void bfs(int s){
    memset(d,0x3f,sizeof d),d[s]=0,q.push(s);
    while(!q.empty()){
        int x=q.front();q.pop();
        for(auto y:v[x]){
            if(d[y]==0x3f3f3f3f)  d[y]=d[x]+1,q.push(y);
            if(d[y]==d[x]+1)  pre[y].push_back(x);
        }
    }
}
void dfs1(int x){
    if(vis[x])  return;
    vis[x]=1;
    for(auto y:pre[x])  nxt[y].push_back(x),dfs1(y);
}
void dfs2(int x){
    for(auto y:nxt[x]){
        now[y]+=now[x]/nxt[x].size();
        if(++rd[y]==pre[y].size())  dfs2(y);
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1,x,y;i<=m;i++){
        scanf("%d%d",&x,&y),x++,y++;
        v[x].push_back(y),v[y].push_back(x);
    }
    scanf("%d",&k);
    for(int i=1,x,y;i<=k;i++){
        memset(vis,0,sizeof vis),memset(now,0,sizeof now);
        memset(rd,0,sizeof rd),scanf("%d%d",&x,&y);
        x++,y++,bfs(x),dfs1(y),now[x]=1,dfs2(x);
        for(int i=1;i<=n;i++)  pre[i].clear(),nxt[i].clear(),sum[i]+=now[i];
    }
    for(int i=1;i<=n;i++)  if(sum[i]>sum[num])  num=i;
    printf("%d\n",num-1);
}

T3 独立集

考虑状压 DP,设 \(dp_msk\) 表示 \(msk\) 的子集内独立集的数目,转移时从 \(msk\) 中任选一个元素 \(i\) 出来。

那么转移有两种情况:

第一种情况是 \(i\) 不在独立集中,那么 \(i\) 号点就不会影响其他的点,这个时候从 \(dp_{msk^(1<<i)}\) 转移过来。

第二种情况是 \(i\) 点在独立集中,这时就要求它的邻居都不在独立集中。

\(nei_i\) 表示 \(i\) 自己与邻居的集合,这个时候就从 \(dp_{msk^(msk \& nei_i)}\) 转移过来。

转移就是这样,代码如下(100pts):

#include<bits/stdc++.h>
using namespace std;
#define LL long long
const LL mod=1e9+7;
int n,m,ans,nei[26],dp[1<<26];
int main(){
	scanf("%d%d",&n,&m),dp[0]=1;
	for(int i=0;i<n;i++)  nei[i]|=(1<<i);
	for(int i=1,x,y;i<=m;i++){
		scanf("%d%d",&x,&y);
		nei[x]|=(1<<y),nei[y]|=(1<<x);
	}
	for(int i=1;i<(1<<n);i++)
		for(int j=0;j<n;j++)
			if(i&(1<<j))
				{dp[i]=((LL)dp[i]+dp[i^(1<<j)]+dp[i^(i&nei[j])])%mod;break;}
	for(LL i=1,po=233;i<(1<<n);i++,po=po*233%mod)  ans=(ans+po*dp[i])%mod;
	printf("%d\n",(ans+1)%mod);
}

T4 粉刷匠

Solve1:Hash/Trie 做法(100pts)

首先我们来考虑怎么判断无解:

已知,如果一行/列的颜色全部相同,那把这行/列删去后对是否有解不会产生影响。

拿数组记录每一行/列的 \(R\)\(B\) 各有多少个,先把最开始的能删的行/列加进队列。

接下来每弹出一次,是列就去更新行,是行就去更新列。

更新完后有 \(R\)\(B\)\(0\) 的行/列就再加进队列,每次更新复杂度 \(O(n)/O(m)\),最多弹出 \(n+m\) 次。

所以这个过程的复杂度为 \(O(nm)\),常数为 \(2\),复杂度是满足的。

有无解判断完后,这道题就好做的,因为我们知道肯定行和列总有一种时全部刷完了的。

所以假设现在我们刷完了所有行,我们要求的就是最少要刷多少列。

而我们还可以想到一个性质:就是如果一些列没有被刷过,那它们就一定是相同的。

我们要求的就变成了最多有多少列相同,那这个过程就可以通过 Hash/Trie 来完成了。

代码贴在下面,这里用的是 Hash 做法,总复杂度 \(O(nm)\)(100pts):

#include<bits/stdc++.h>
using namespace std;
#define N 3005
#define ull unsigned long long
char ch[N][N];
map<ull,int> mp;
queue<int> qh,qw;
bool vish[N],visw[N];
int n,m,ans,hr[N],hb[N],wr[N],wb[N];
bool check(){
    for(int i=1;i<=n;i++)  if(!hr[i]||!hb[i])  qh.push(i),vish[i]=1;
    for(int i=1;i<=m;i++)  if(!wr[i]||!wb[i])  qw.push(i),visw[i]=1;
    for(int i=1,tn=n,tm=m;;i++){
        if(!tn||!tm)  return 1;
        if(qh.empty()&&qw.empty())  return 0;
        if(tn<=tm&&!qh.empty()||tn>tm&&qw.empty()){
            int h=qh.front();qh.pop(),tn--;
            for(int j=1;j<=m;j++){
                if(visw[j])  continue;
                if(ch[h][j]=='R')  wr[j]--;
                else  wb[j]--;
                if((!wr[j]||!wb[j])&&!visw[j])  qw.push(j),visw[j]=1;
            }
        }
        else{
            int w=qw.front();qw.pop(),tm--;
            for(int j=1;j<=n;j++){
                if(vish[j])  continue;
                if(ch[j][w]=='R')  hr[j]--;
                else  hb[j]--;
                if((!hr[j]||!hb[j])&&!vish[j])  qh.push(j),vish[j]=1;
            }
        }
    }
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%s",ch[i]+1);
        for(int j=1;j<=m;j++){
            if(ch[i][j]=='R')  hr[i]++,wr[j]++;
            else hb[i]++,wb[j]++;
        }
    }
    if(!check())  return printf("-1\n"),0;
    for(int i=1;i<=n;i++){
        ull sum=0;
        for(int j=1;j<=m;j++)  sum=sum*131+ch[i][j];
        ans=max(ans,++mp[sum]);
    }
    mp.clear();
    for(int j=1;j<=m;j++){
        ull sum=0;
        for(int i=1;i<=n;i++)  sum=sum*131+ch[i][j];
        ans=max(ans,++mp[sum]);
    }
    printf("%d\n",n+m-ans);
}

Solve2:倒推做法(只有 80pts,但是不知道哪里有误)

在 Solve1 判断无解的思路上进行扩展,使得能够直接求出答案。

把题目转换一下:从要刷多少次变为删多少次可以可以删完,删图的要求是可以只能颜色全部相同的一行/列。

既然我们要最快的全部删完,所以相对 Solve1 要改变的就是在队列中的行/列的删除顺序。

先不考虑行与行/列与列,我们先来想行与列的删除顺序。

显然是行少先删行,列少先删列,因为如果整个图的颜色都一样了,那么肯定是直接删少的删完。

不一样的话,不管先删谁,都不会改变对方的可删性。

而如果删多的那一边删完后整个图的颜色都变得一样了,那还是要删少的那一边。

如果颜色没有变得一样,那不管怎么都要删少的,那我先删了也不可能会对答案产生影响。

但是有没有可能删多的一边能直接删完,而删少的一边删不完呢?

肯定是不可能的,因为如果仅靠删多的一边能删完的话,那整个图的颜色肯定就一样了,那就需要删少的边了。

那我们要考虑的就是行与行/列于列间的删除优先级了,我们以行与行举例:

如果行比列少,那不管怎么样,都要把能删的行全部删完,那就不存在优先级的问题了。

所以我们只需要考虑的就是行比列多的情况了。

这种情况我们就需要使删除的行最少,然后就去删除列。

所以我们要考虑的就是如何删最少的行,使得接下来可以删列。

那我们就可以把列中需要删除的最少的 \(R\)\(B\) 的最小值找出来,然后哪个更小就先删那种颜色。

但是如果这种颜色在队列里的数量不够,那我们还是要删另一种颜色的。

而如果两种颜色都不够,那就无解了,因为行删完了也没有新的列可以删,行的删除也不会对其它行产生影响。

那如果这种颜色删了过后,行更小了呢?那就直接继续删行不就行了吗?

反正之后的循环能解决问题,在这里就不用去进行过多的考虑了。

思路大概就是这样,代码贴在下面,希望大家能帮我看看哪里写的有问题,因为没有这种方法的题解(80pts):

#include<bits/stdc++.h>
using namespace std;
#define N 3005
char ch[N][N];
bool vish[N],visw[N];
int qh[N],qw[N],frh=1,frw=1,tah,taw;
int n,m,inhr,inhb,inwr,inwb,hr[N],hb[N],wr[N],wb[N];
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++){
        scanf("%s",ch[i]+1);
        for(int j=1;j<=m;j++){
            if(ch[i][j]=='R')  hr[i]++,wr[j]++;//hr[i]表示第i行有多少个R,wr[j]表示第j列有多少个R
            else  hb[i]++,wb[j]++;//hb[i]表示第i行有多少个B,wb[j]表示第j列有多少个B
        }
    }
    for(int i=1;i<=n;i++){
        if(!hr[i])  qh[++tah]=i,vish[i]=1,inhb++;//inhb表示在队列里的全为B的行的数量
        if(!hb[i])  qh[++tah]=i,vish[i]=1,inhr++;//inhr表示在队列里的全为R的行的数量
    }
    for(int i=1;i<=m;i++){
        if(!wr[i])  qw[++taw]=i,visw[i]=1,inwb++;//inwb表示在队列里的全为B的列的数量
        if(!wb[i])  qw[++taw]=i,visw[i]=1,inwr++;//inwr表示在队列里的全为R的列的数量
    }
    for(int i=0,tn=n,tm=m;;){
        if(!tn||!tm)  return printf("%d\n",i),0;
        if(frh>tah&&frw>taw)  return printf("-1\n"),0;
        if(tn<=tm&&frh<=tah){//行少有行就删行
            int h=qh[frh++];tn--,(!hr[h]?inhb--:inhr--),i++;
            for(int j=1;j<=m;j++){
                if(visw[j])  continue;
                if(ch[h][j]=='R')  wr[j]--;
                else  wb[j]--;
                if(!wr[j]&&!visw[j])  qw[++taw]=j,visw[j]=1,inwb++;
                if(!wb[j]&&!visw[j])  qw[++taw]=j,visw[j]=1,inwr++;
            }
        }
        else if(tm<=tn&&frw<=taw){//列少有列就删列
            int w=qw[frw++];tm--,(!wr[w]?inwb--:inwr--),i++;
            for(int j=1;j<=n;j++){
                if(vish[j])  continue;
                if(ch[j][w]=='R')  hr[j]--;
                else  hb[j]--;
                if(!hr[j]&&!vish[j])  qh[++tah]=j,vish[j]=1,inhb++;
                if(!hb[j]&&!vish[j])  qh[++tah]=j,vish[j]=1,inhr++;
            }
        }
        else if(frh<=tah){//行多没列才删行
            int minr=0x3f3f3f3f,minb=minr;
            for(int j=1;j<=m;j++){
                if(visw[j])  continue;
                minr=min(minr,wr[j]),minb=min(minb,wb[j]);
            }
            if(minr>inhr&&minb>inhb)  return printf("-1\n"),0;
            if(minr<=minb&&inhr>=minr||minb>inhb){//R更少且能删或B更少但不能删
                for(int j=frh,ft=frh;ft-frh<minr;j++)
                    if(!hb[qh[j]])  swap(qh[j],qh[ft++]);
                frh+=minr,tn-=minr,i+=minr,inhr-=minr;
                for(int j=1;j<=m;j++){
                    if(visw[j])  continue;
                    if(!(wr[j]-=minr)&&!visw[j])  qw[++taw]=j,visw[j]=1,inwb++;
                }
            }
            else{//B更少且能删或R更少但不能删
                for(int j=frh,ft=frh;ft-frh<minb;j++)
                    if(!hr[qh[j]])  swap(qh[j],qh[ft++]);
                frh+=minb,tn-=minb,i+=minb,inhb-=minb;
                for(int j=1;j<=m;j++){
                    if(visw[j])  continue;
                    if(!(wb[j]-=minb)&&!visw[j])  qw[++taw]=j,visw[j]=1,inwr++;
                }
            }
        }
        else{
            int minr=0x3f3f3f3f,minb=minr;
            for(int j=1;j<=n;j++){
                if(vish[j])  continue;
                minr=min(minr,hr[j]),minb=min(minb,hb[j]);
            }
            if(minr>inwr&&minb>inwb)  return printf("-1\n"),0;
            if(minr<=minb&&inwr>=minr||minb>inwb){//R更少且能删或B更少但不能删
                for(int j=frw,ft=frw;ft-frw<minr;j++)
                    if(!wb[qw[j]])  swap(qw[j],qw[ft++]);
                frw+=minr,tm-=minr,i+=minr,inwr-=minr;
                for(int j=1;j<=n;j++){
                    if(vish[j])  continue;
                    if(!(hr[j]-=minr)&&!vish[j])  qh[++tah]=j,vish[j]=1,inhb++;
                }
            }
            else{//B更少且能删或R更少但不能删
                for(int j=frw,ft=frw;ft-frw<minb;j++)
                    if(!wr[qw[j]])  swap(qw[j],qw[ft++]);
                frw+=minb,tm-=minb,i+=minb,inwb-=minb;
                for(int j=1;j<=n;j++){
                    if(vish[j])  continue;
                    if(!(hb[j]-=minb)&&!vish[j])  qh[++tah]=j,vish[j]=1,inhr++;
                }
            }
        }
    }
}
posted @ 2024-09-05 16:11  tkdqmx  阅读(116)  评论(0)    收藏  举报