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++;
}
}
}
}
}

浙公网安备 33010602011771号