Atcoder arc089F

这篇博客介绍了一个关于计数的编程问题,具体是Atcoder上的arc089F题目。作者首先讨论了如何判断一个方案是否可行,通过分析'r'和'b'的分布,提出了一种判定方法。接着,博主探讨了如何有效地进行计数,枚举含有'b'的段数p和仅含'r'的段数q,并提出了一种最优分配策略,该策略可以通过队列实现。最后,博主介绍了如何扩展解决方案并优化复杂度,提出了动态规划(DP)的方法,并讨论了在实际运行时,整数拆分策略可能在某些情况下优于多项式复杂度的解法。
摘要由CSDN通过智能技术生成

挺棒的计数题。
先考虑给一个方案,如何判定它能否被构造出来。显然 ′ w ′ 'w' w会把方案分成若干段,每段是独立的。对于某一段,如果只有 ′ r ′ 'r' r,那么恰好需要一次 ′ r ′ 'r' r操作,否则存在 ′ b ′ 'b' b的话就需要先做一次 ′ r ′ 'r' r操作,接着再做一次 ′ b ′ 'b' b操作,接下来考虑段内的 ′ b ′ 'b' b连续段数目为 d d d,那么我们显然需要再做至少 d − 1 d-1 d1次操作,事实上,这 d − 1 d-1 d1次操作是 ′ r ′ 'r' r还是 ′ b ′ 'b' b是无关紧要的,我们容易构造出一个方案(考虑其中的 ′ r ′ 'r' r操作数目为 d r d_r dr,我们第一个 ′ b ′ 'b' b操作先反色 d r + 1 d_r+1 dr+1 ′ b ′ 'b' b连续段,中间会夹 d r d_r dr ′ r ′ 'r' r连续段,接下来 ′ r ′ 'r' r操作就反色回去, ′ b ′ 'b' b操作就开新段)。这样的话,我们只需要知道段数和每段内的 ‘ b ′ ‘b' b连续段数目就可以判定了。
再考虑如何计数,我们枚举最终有 ′ b ′ 'b' b的段数 p p p和仅含 ′ r ′ 'r' r的段数 q q q。我们需要先给含 ′ b ′ 'b' b的段分配 ′ r b ′ 'rb' rb,给仅含 ′ r ′ 'r' r的段分配 ′ r ′ 'r' r,如果不能分配显然不可行。如果可行的话我们会怎么分配呢?稍加思考的话我们可以证明最优的分配方式是尽可能的往前分配 p p p ′ r b ′ 'rb' rb,注意当遇到某个 ′ b ′ 'b' b前面还有没分配的 ′ r ′ 'r' r的时候也要分配最前的 ′ r ′ 'r' r,然后再选择最前面没分配的 q q q ′ r ′ 'r' r分配,这个容易用队列实现。这样分配好之后,我们考虑每个含 ′ b ′ 'b' b的段中的 ′ b ′ 'b' b连续段数目,我们显然会要求数目更多的分配到前面的 ′ r b ′ 'rb' rb,那么我们的限制可以变成 ∀ i \forall i i,含 ′ b ′ 'b' b的段中 ′ b ′ 'b' b连续段数目最小的 i i i段总数不超过 u p i up_i upi
对一组 ( p , q ) (p,q) (p,q)计数可以用一个DP实现,大概是设 F [ i ] [ j ] [ k ] F[i][j][k] F[i][j][k]表示最小的 i i i段的 ′ b ′ 'b' b连续段数目和为 j j j,最多的用了 k k k段,用一些简单的技巧转移可以做到单次DP O ( K 3 log ⁡ K ) \mathcal O(K^3\log K) O(K3logK)。DP出来后我们还要考虑每个含 ′ b ′ 'b' b的段都可以在左右扩展一个 ′ r ′ 'r' r连续段,这样不增加需要的操作次数。
最后合并答案用组合数计算即可。时间复杂度 O ( K 5 log ⁡ K ) \mathcal O(K^5\log K) O(K5logK),不过常数非常小。题解的做法使用了整数拆分,在 K = 70 K=70 K=70时跑的比这个多项式复杂度做法更快。。。

#include <bits/stdc++.h>
#define MOD 1000000007

using namespace std;

typedef long long ll;

inline void add(int &x,int y) {
  ((x+=y)>=MOD)?x-=MOD:0;
} 

ll C[105][105];

void pre(int n) {
  for(int i=0;i<=n;i++) C[i][0]=1;
  for(int i=1;i<=n;i++)
    for(int j=1;j<=i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%MOD;
}

int f[85][85][85],up[85];
int sum[85];

void dp(int n,int m,int c) {
  memset(f,0,sizeof(f));
  int t=m+2*c-1;
  f[0][0][0]=1;
  for(int i=0;i<m;i++)
    for(int j=0;j<=up[i]&&2*j-m+t<=n;j++) {
      int s=f[i][j][0];
      for(int k=1;k<=up[m]&&j+(m-i)*k<=up[m];k++) {
      	for(int l=1;i+l<=m&&j+l*k<=up[i+l];l++)
      	  add(f[i+l][j+l*k][k],s*C[i+l][l]%MOD);
      	add(s,f[i][j][k]);
	  }
    }
  for(int i=0;i<=up[m]&&2*i-m+t<=n;i++) {
  	int s=0;
  	for(int j=0;j<=i;j++) add(s,f[m][i][j]);
  	if (!s) continue;
  	int v=2*i-m+t;
  	for(int j=0;j<=2*m+2&&v+j<=n;j++)
  	  add(sum[v+j],s*C[m+c][c]%MOD*C[2*m+2][j]%MOD);
  }
}

char str[85];

int q[85],lpos[85];
bool vis[85];

int main() {
  int n,k;
  scanf("%d%d%s",&n,&k,str+1);
  pre(100);
  for(int i=0;2*i<=k;i++) {
  	memset(vis,0,sizeof(vis));
  	int cnt=0;
  	int lx=1,rx=0;
  	for(int j=1;j<=k&&cnt<i;j++)
  	  if (str[j]=='r') q[++rx]=j;
  	  else if (lx<=rx) {
  	  	  vis[q[lx++]]=vis[j]=1;
  	  	  lpos[++cnt]=j;
		}
	if (cnt<i) continue;
	int s=0;
	for(int j=1;j<=k;j++)
	  if (!vis[j]&&str[j]=='r') s++;
	int r=1;
	lpos[i+1]=k+1;up[i+1]=0;
	for(int j=0;j<=s;j++) {
		if (j) {
			while (vis[r]||str[r]!='r') r++;
			vis[r]=1;
		}
		for(int t1=i;t1>0;t1--) {
			up[t1]=up[t1+1];
			for(int t2=lpos[t1+1]-1;t2>lpos[t1];t2--)
			  if (!vis[t2]) up[t1]++;
			up[t1]++;
		}
		reverse(up+1,up+i+1);
		dp(n,i,j);
	}
  }
  int ans=0;
  for(int i=1;i<=n;i++) add(ans,sum[i]*C[n-1][i-1]%MOD);
  printf("%d\n",ans);
  return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值