CodeCraft-19 and Codeforces Round #537 (Div. 2) D. Destroy the Colony(组合数学+退背包)

题目链接:http://codeforces.com/contest/1111/problem/D

题目大意:

这道题题目意思比较模糊。

给定一个字符串,不同字符代表不同种类的坏人,

现在给定若干个询问,每个询问给两个下标,

要求把给定位置上的两个(或一个)类型的人放到同一边,

并且其他相同种类的人均在同一边的方案数。

题目分析: 

首先考虑不加限制的计数方案,

就是说不考虑S_xS_y种类在同一边的情况。

如果单独考虑一边,把半边的情况选好了那另一半的情况也是类似,

但是半边的计数方案和具体的选择种类有关系,就算我们通过组合公式

来执行背包计数也无法把两边的计数方案结合到一起去,那么不妨两边一块考虑,

分母消去所有种类的重复度,分子是两边长度阶乘的贡献:\frac{(\frac{n}{2}!)*(\frac{n}{2}!)}{\prod cnt_i}

对这部分只要处理常用的阶乘逆元即可。

下面先算总体贡献:F(\frac{n}{2}).这部分用普通的背包计数即可。

如何把两个询问下标考虑进去需要利用到退背包的知识,即在集合S中不考虑第i种物品的情况下

对目标值所产生的计数,设为G(i),那么有如下递推方程:k<cnt_i,g(k)=dp(k) , k>=cnt_i,g(k)=dp(k)-g(k-cnt_i)

对于两个查询下标种类一样的情况其实本质差不多,少退一次背包而已。

时间复杂度:O(52*52*N)。

 

#include<bits/stdc++.h>
using namespace std;

#define debug puts("YES");
#define rep(x,y,z) for(int (x)=(y);(x)<(z);(x)++)
#define ll long long

#define lrt int l,int r,int rt
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define root l,r,rt
#define mst(a,b) memset((a),(b),sizeof(a))
#define pii pair<int,int>
#define fi first
#define se second
#define mk(x,y) make_pair(x,y)
const int mod=1e9+7;
const int maxn=1e5+5;
const int ub=1e6;
const double inf=1e-4;
ll powmod(ll x,ll y){ll t; for(t=1;y;y>>=1,x=x*x%mod) if(y&1) t=t*x%mod; return t;}
ll gcd(ll x,ll y){return y?gcd(y,x%y):x;}
/*
题目大意:

*/
char s[maxn];
int len,cnt[52],q,x,y;
ll dp[maxn],g[maxn],ans[52][52];
int idx(char c){
    if(c<='z'&&c>='a') return c-'a';
    return 26+c-'A';
}
ll fac[maxn],inv[maxn];
void init(){
    fac[0]=1;rep(i,1,maxn) fac[i]=fac[i-1]*i%mod;
    inv[maxn-1]=powmod(fac[maxn-1],mod-2);
    for(int i=maxn-2;i>=0;i--) inv[i]=inv[i+1]*(i+1)%mod;
}
ll C(ll p,ll q){
    return fac[p]*inv[q]%mod*inv[p-q]%mod;
}
void Back(int x){
    rep(k,0,x) g[k]=dp[k];
    rep(k,x,len/2+1) g[k]=(dp[k]-g[k-x]+mod)%mod;
}
int main(){
    init();
    scanf("%s",s);len=strlen(s);///
    rep(i,0,len) cnt[idx(s[i])]++;

    mst(dp,0),mst(g,0),dp[0]=1;
    rep(i,0,52) if(cnt[i]) for(int j=len/2;j>=cnt[i];j--)
        (dp[j]+=dp[j-cnt[i]])%=mod;///背包DP计数

    mst(ans,0);///
    rep(i,0,52) rep(j,i,52){
        Back(cnt[i]);///退背包过程
        if(i!=j) rep(k,cnt[j],len/2+1)
            g[k]=(g[k]-g[k-cnt[j]]+mod)%mod;
        ans[i][j]=g[len/2];
    }

    ll xishu=fac[len/2]*fac[len/2]%mod;
    rep(i,0,52) xishu=xishu*inv[cnt[i]]%mod;
    cin>>q;
    rep(i,0,q){
        cin>>x>>y;
        x=idx(s[x-1]),y=idx(s[y-1]);
        ll ret=max(ans[x][y],ans[y][x]);
        ret=ret*2%mod*xishu%mod;
        cout<<ret<<endl;
    }
    return 0;
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值