cf1111D

dp好题!

首先我们可以求出所有字母都在一侧的方案数dp[n/2];

假设左半部分为集合S,右半部分为T,那么假设选出的a,b已经同属一个集合时

S中可以乱放,T中也可以乱放

那么S中乱放的方案数有:(n/2)!/(num[i]!*num[j]!*.....) (i...j属于S);  1

同样T中乱放的方案数有:(n/2)!/(num[i]!*num[j]!*.....) (i...j属于T);   2

那么总的方案数就是(1式*2式)为:((n/2)!*(n/2)!)/(num[1]!*num[2]!*....*num[n]!)  3

3式我们可以先预处理出来为now

那么最后,a,b在同侧时的答案就是dp[a][b]*now

那么考虑dp[a][b]怎么预处理呢(有一侧选就是另一侧不选,这里我们考虑不选的情况)

我们采用退背包

我们先考虑f[i]为无限制选放满i的方案数(1<=i<=n/2)

g[i]表示不选a放满g[i]的方案数

当i<num[a]时  g[i]=f[i]

当i>=num[a]时  g[i]=f[i]-g[i-num[a]];

h[i]表示不选b放满h[i]的方案数 (在上面的基础上再退一层,表示a,b都不选)

当i<num[b]时 h[i]=f[i]

当i>=num[b]时 h[i]=g[i]-g[i-num[b]];

最后每次询问的答案就是:dp[a][b]*now了

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#include <map>
using namespace std;
const int mo=1e9+7;
const int maxn=100010;
int n,cnt,v[maxn],q,m;
long long p[maxn],inv[maxn],now,num[55],dp[55][55];
long long f[maxn>>1],g[maxn>>1],h[maxn>>1];
char s[maxn];
map<char,int>lc;
long long po(long long a,int b)
{
	if (b==0) return 1ll;
	if (b==1) return a%mo;
	long long c=po(a,b/2);
	if (b&1) return c*c%mo*a%mo;else return c*c%mo;
}
int main()
{
	scanf("%s%d",s+1,&q); n=strlen(s+1); p[0]=1ll; m=n/2;
	for (int i=1;i<=100000;i++) p[i]=1ll*p[i-1]*i%mo;
	inv[100000]=po(p[100000],mo-2);
	for (int i=99999;i>=0;i--) inv[i]=1ll*inv[i+1]*(i+1)%mo;
	for (char i='A';i<='Z';i++) lc[i]=++cnt;
	for (char i='a';i<='z';i++) lc[i]=++cnt;
	for (int i=1;i<=n;i++) v[i]=lc[s[i]],num[v[i]]++;
	now=p[n/2]%mo*p[n/2]%mo;
	for (int i=1;i<=cnt;i++) now=now*inv[num[i]]%mo;
	f[0]=1;
	for (int j=1;j<=cnt;j++)
	if (num[j])
	{
	 for (int i=m;i>=num[j];i--)
	  if (f[i-num[j]]) f[i]=(f[i]+f[i-num[j]])%mo;
	}
	for (int i=1;i<=cnt;i++)
	if (num[i])
	{
		dp[i][i]=f[m];
		for (int j=0;j<num[i];j++) g[j]=f[j];
		for (int j=num[i];j<=m;j++) g[j]=(f[j]-g[j-num[i]]+mo)%mo;
		for (int j=i+1;j<=cnt;j++)
		if (num[j])
		{
			for (int k=0;k<num[j];k++) h[k]=g[k];
			for (int k=num[j];k<=m;k++) h[k]=(g[k]-h[k-num[j]]+mo)%mo;
			dp[i][j]=dp[j][i]=2ll*h[m]%mo;
		}
	}
	while (q--)
	{
		int xx,yy; scanf("%d%d",&xx,&yy); int aa=v[xx],bb=v[yy];
		printf("%lld\n",now*dp[aa][bb]%mo);
	}
return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值