cf1111D(可逆背包)

题目链接

http://codeforces.com/problemset/problem/1111/D

题意

给定一个长度为偶数的的字符串(仅包含字母),可以随意交换任意两个字符,先要将字符串分成左右两半部分,给定 $q$ 个询问,每个询问包含 $x$ 和 $y$ ,要求满足下述要求的排列方案数:

  1. 同一种字符不同时出现在字符串左右两半
  2. $x$ 、 $y$ 代表的字符应出现在同一侧

题解

事实上只要考虑哪些字符出现在左半部分就可以了然后再对这些字符分别进行排列,而排列方案数总是固定的,为 $\frac{\frac{n}{2}!\frac{n}{2}!}{\prod a_i!}$ (其中$a_i$为i字符出现的个数)。

然后就转化成一个01计数背包了,由于这个背包是可逆的,所以我们把物品装进去之后再去撤销。直接插销复杂度是 $O(qn)$ 不能接受,仔细分析询问种类最多只有 $52*52$ 种,那么直接预处理这些情况就可以了,复杂度为 $O(52^2n)$ 。实际上复杂度没有那么高,因为我们只要求背包里某一个特定的值,而撤销背包的表达式比较简单,所以可以手动算出要求的那一项,这样撤销的复杂度可以降成 $O(n/v)$ ,为了避免极限数据的出现可以排序去重,然后复杂度可以降至 $O(52nlogn)$ 。




代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
/**
*         ┏┓    ┏┓
*         ┏┛┗━━━━━━━┛┗━━━┓
*         ┃       ┃  
*         ┃   ━    ┃
*         ┃ >   < ┃
*         ┃       ┃
*         ┃... ⌒ ...  ┃
*         ┃ ┃
*         ┗━┓ ┏━┛
*          ┃ ┃ Code is far away from bug with the animal protecting          
*          ┃ ┃ 神兽保佑,代码无bug
*          ┃ ┃           
*          ┃ ┃       
*          ┃ ┃
*          ┃ ┃           
*          ┃ ┗━━━┓
*          ┃ ┣┓
*          ┃ ┏┛
*          ┗┓┓┏━━━━━━━━┳┓┏┛
*           ┃┫┫ ┃┫┫
*           ┗┻┛ ┗┻┛
*/

#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<queue>
#include<map>
#include<stack>
#include<cmath>
#include<set>
#include<bitset>
#define inc(i,l,r) for(int i=l;i<=r;i++)
#define dec(i,l,r) for(int i=l;i>=r;i--)
#define link(x) for(edge *j=h[x];j;j=j->next)
#define mem(a) memset(a,0,sizeof(a))
#define ll long long
#define eps 1e-8
#define succ(x) (1<<x)
#define lowbit(x) (x&(-x))
#define sqr(x) ((x)*(x))
#define mid (x+y>>1)
#define NM 100005
#define nm 200005
#define pi 3.1415926535897931
const int inf=1e9+7;
using namespace std;
ll read(){
ll x=0,f=1;char ch=getchar();
while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
return f*x;
}






ll ans,p[NM],inv[NM],invp[NM];
char s[NM];
int n,m,a[53],_x,_y,d[53][53];
struct tmp{
int d[NM];
void operator*=(const int&t){dec(i,n,t)d[i]+=d[i-t],d[i]%=inf;}
void operator/=(const int&t){inc(i,t,n)d[i]-=d[i-t],d[i]%=inf;}
}cnt[53];
int fun(char t){return 'a'<=t&&t<='z'?t-'a'+1:t-'A'+27;}

int main(){
scanf("%s",s+1);n=strlen(s+1);
p[0]=invp[0]=inv[1]=invp[1]=1;
inc(i,1,n)p[i]=p[i-1]*i%inf;
inc(i,2,n)inv[i]=inv[inf%i]*(inf-inf/i)%inf,invp[i]=invp[i-1]*inv[i]%inf;
inc(i,1,n)a[fun(s[i])]++;
n>>=1;
ans=p[n]*p[n]%inf;
inc(i,1,52)ans*=invp[a[i]],ans%=inf;
ans<<=1;ans%=inf;
cnt[0].d[0]=1;
inc(i,1,52)if(a[i])cnt[0]*=a[i];
inc(i,1,52)if(a[i])cnt[i]=cnt[0],cnt[i]/=a[i];
inc(i,1,52)if(a[i])inc(j,i+1,52)if(a[j]){
int t=1;
for(int k=n;k>=0;k-=a[j],t=-t)
d[i][j]+=t*cnt[i].d[k],d[i][j]%=inf;
}
inc(i,1,52)inc(j,1,i-1)d[i][j]=d[j][i];
m=read();while(m--){
_x=read();_y=read();
if(s[_x]==s[_y])printf("%lld\n",(cnt[fun(s[_x])].d[n]+inf)*ans%inf);
else printf("%lld\n",(d[fun(s[_x])][fun(s[_y])]+inf)*ans%inf);
}
return 0;
}