2022/8/24 总结
A.寿司
-
考场上暴力都没搞懂怎么打的题……
-
看数据范围,猜应该基本上是个 \(\mathtt{O(n)}\) 的算法,但各种转化化出来也没找到什么好方法,最后用一个前缀和的 \(\mathtt{O(n^2)}\) 算法嫖了点分;
Solution
-
考虑使用前缀和,统计 \(\mathtt{\color{red}{R}}\) 和 \(\mathtt{\color{blue}{B}}\) 的前缀和后缀,由于是环状结构,所以可以将原字符串在后面再接一遍,每次只看长度为 \(n\) 的一段时效果同环等效。
-
我们只看 \(\mathtt{\color{red}{R}}\)(因为移动 \(\mathtt{\color{red}{R}}\) 和移动 \(\mathtt{\color{blue}{B}}\) 等效,所以看 \(\mathtt{\color{blue}{B}}\) 也一样)。
在不考虑序列在环上的循环的情况下,挪动全体 \(\mathtt{\color{red}{R}}\) 有两种情况。一种是全挪到一片,一种是挪到两边。这样做统计答案的算式为\[\sum\limits_{被移到左边的 R}{该字符左边B的个数}+\sum\limits_{被移到右边的 R}{该字符右边的B的个数} \]然后就有显然的策略,将 \(sum_B/2\) (设为 \(pos\))左边的 \(\mathtt{\color{red}{R}}\) 全部挪到左边,右边的 \(\mathtt{\color{red}{R}}\) 全挪到右边。
-
这个算法对于非环状是 \(\mathtt{O(n)}\) 的。但环状结构会导致外围的一层 \(\mathtt{O(n)}\) 的循环,所以这个算法就变成了 \(\mathtt{O(n^2)}\) 的。
我们来考虑一下循环时怎么更快地转移状态。 -
每次循环的长度为 \(n\) 的序列相当于把第一个字符换到了末尾。如果该字符为 \(\mathtt{\color{blue}{B}}\),那么如果 \(pos\) 不动,则原来 \(pos\) 左侧所有的 \(\mathtt{\color{red}{R}}\) 贡献都要 \(-1\),\(pos\) 右边的都要 \(+1\)。
然后考虑移动 \(pos\)。向后移动 \(pos\) 到下一个 \(\mathtt{\color{blue}{B}}\) 处,需要处理的就是从 \(pos\) 右边挪到 \(pos\) 左边的 \(\mathtt{\color{red}R}\) 带来的变化。
另外,根据某同学亲身经历,在加法时加上 \(\mathtt{0ll}\) 会使程序变得更快。
AC code
#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=1e6+10;
int T;
int n;
ll ans,cnt;
char ch[N];
ll lb[N<<1],lr[N<<1],rb[N<<1],rr[N<<1];
int main(){
// freopen("sushi.in","r",stdin);
scanf("%d",&T);
while(T--){
scanf("%s",ch+1);
n=strlen(ch+1);
for(int i=1;i<=(n<<1);++i){
lb[i]=lb[i-1],lr[i]=lr[i-1];
if(ch[(i-1)%n+1]=='R') ++lr[i];
else ++lb[i];
}
for(int i=(n<<1);i;--i){
rb[i]=rb[i+1],rr[i]=rr[i+1];
if(ch[(i-1)%n+1]=='R') ++rr[i];
else ++rb[i];
}
int pos=(lb[n]+1)>>1;
int p=-1;
cnt=0;
for(int i=1;i<=n;++i){
if(lb[i]==pos){
p=i;
for(int j=n;j>=i;--j)
if(ch[j]=='R')
cnt+=0ll+rb[j]-rb[n+1];
break;
}
if(ch[i]=='R')
cnt+=0ll+lb[i];
}
int l=1,r=n;
ans=cnt;
while(l<=n){
if(ch[(l-1)%n+1]=='B'){
cnt-=0ll+lr[p]-lr[l-1];
cnt+=0ll+rr[p]-rr[++r];
++l;
while(ch[(++p-1)%n+1]=='R'){
cnt+=0ll+lb[p]-lb[l-1];
cnt-=0ll+rb[p]-rb[r+1];
}
}
else ++l,++r;
ans=min(ans,cnt);
}
printf("%lld\n",ans);
}
return 0;
}
B.最优序列
-
虽然考场上看出了是状压,但不知道为什么写挂了;
-
而且很离谱的是这题用快读会 T,改成 cin 或 scanf 就行了???真的不是加了什么卡快读的东西吗?
Solution
-
状压 \(\mathtt{DP}\),设 \(f_{i,j}\) 表示在以 \(i\) 为结尾的长度为 \(m\) 的序列中,选择状态为 \(j\) 的最大选择方案。每次新增一位就把原来状态的第 \(m\) 位去掉,整个状态左移一位,然后用最末位表示最新的一位。
-
转移方程为:
- \(f_{i,j\&((1<<m-1)-1)<<1}=\max(f_{i,j\&((1<<m-1)-1)<<1}\ ,f_{i-1,j})\);
- \(f_{i,j\&((1<<m-1)-1)<<1|1}=\max(f_{i,j\&((1<<m-1)-1)<<1|1}\ ,f_{i-1,j})\);
-
注意在转移状态时要筛掉在这 \(m\) 个数中已经选了超过 \(k\) 个的情况;
AC dp
#include<bits/stdc++.h>
using namespace std;
const int N=1e3+100;
int n,m,k;
int a[N];
int f[N][N];
bool num[N];
int g(int x){
int cnt=0;
for(;x;x-=x&-x)
++cnt;
return cnt;
}
int main(){
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
int dx=(1<<(m-1))-1;
int d=1<<m;
for(int i=0;i<d;++i)
num[i]=(g(i)<=k);
int ans=-1;
for(int i=0;i<=n;++i)
for(int j=0;j<d;++j)
f[i][j]=-0x3f3f3f3f;
f[0][0]=0;
int nxt;
for(int i=1;i<=n;++i){
for(int j=0;j<d;++j){
nxt=(j&dx)<<1;
if(num[nxt])
f[i][nxt]=max(f[i][nxt],f[i-1][j]);
nxt=(j&dx)<<1|1;
if(num[nxt])
f[i][nxt]=max(f[i][nxt],f[i-1][j]+a[i]);
}
}
for(int i=0;i<d;++i)
ans=max(ans,f[n][i]);
printf("%d\n",ans);
return 0;
}

浙公网安备 33010602011771号