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;
}
posted @ 2022-08-24 21:26  Star_LIcsAy  阅读(67)  评论(0)    收藏  举报