CodeForces 327 E.Axis Walking(折半枚举+map)

Description

给出 n 个整数ai,将其位置打乱可以得到 n! 个排列,问这些排列中不存在某个前缀和出现集合 {b1,...,bk} 中的排列个数

Input

第一行一整数 n ,之后输入n个整数 ai ,然后输入一整数 k ,之后输入k个整数 bi (1n24,1ai109,0k2,1bi109)

Output

输出满足条件的排列的个数,结果模 109+7

Sample Input

3
2 3 5
2
5 7

Sample Output

1

Solution

k=0 时答案显然为 n!

k=1 时,问题转化为求 {a1,...,an} 的非空子集个数使得其和为 b1 ,由于 n 只有24,故可以折半枚举前 n/2 个数的所有子集和以及后 n/2 个数的所有子集和,用 map 存一下子集和以及该子集和对应的方案数,假设前 n/2 个数得到子集和为 x 的方案数为num1,那么直接在后一个 map 里找对应子集和为 b1x 的方案数 num2 xnum1num2 即为答案

k=2 时,假设 b1<b2 ( b1=b2 对应 k=1 的情况),那么不合法排列数即为:和为 b1 的非空子集个数 + 和为b2的非空子集个数 和为b2且存在一个真子集其和为 b1 的非空子集个数,前两块类似 k=1 即可得到,考虑求第三块,同样的折半枚举,对于前 n/2 个数,每个数三种状态,要么不拿,要么拿来放在前面作为组成 b1 的部分,要么拿来放在后面作为组成 b2b1 的部分,用 map 存一下第一个和为 x1 ,第二个和为 y1 的子集个数 num1 ,同样的用另一个 map 存一下后 n/2 个数,满足第一个和为 x2 ,第二个和为 y2 的子集个数 num2 ,那么答案即为 x1+x2=b1,y1+y2=b2b1num1num2 ,类似的,枚举第一个 map 里的元素 (x,y,num1) ,从第二个 map 里找 (b1x,b2b1y,num2) 即可

Code

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<ctime>
#include<unordered_map>
using namespace std;
typedef long long ll;
typedef pair<int,int>P;
typedef pair<P,P>PP;
const int INF=0x3f3f3f3f,maxn=25;
#define mod 1000000007
int n,a[maxn],k,b1,b2,f[maxn];
map<P,int>m1,m2;
map<PP,int>M1,M2;
void Solve1(int *a,int n,map<P,int>&m,int b)
{
    int N=1<<n;
    for(int i=0;i<N;i++)
    {
        int num=0,sum=0;
        for(int j=0;j<n;j++)
            if(i&(1<<j))num++,sum+=a[j];
        if(sum>b)continue;
        m[P(sum,num)]++;
    }
}
void Solve2(int *a,int n,map<PP,int>&M)
{
    int g[maxn],res;
    int N=1;
    for(int i=1;i<=n;i++)N*=3;
    for(int i=0;i<N;i++)
    {
        int num1=0,num2=0,sum1=0,sum2=0;
        res=0;
        int temp=i;
        while(temp)g[res++]=temp%3,temp/=3;
        for(int j=0;j<res;j++)
            if(g[j]==1)num1++,sum1+=a[j];
            else if(g[j]==2)num2++,sum2+=a[j];
        if(sum1>b1||sum2>b2)continue;
        M[PP(P(sum1,num1),P(sum2,num2))]++;
    }
}
int Deal(int b)
{
    int ans=0;
    m1.clear(),m2.clear();
    Solve1(a+1,n/2,m1,b);
    Solve1(a+n/2+1,n-n/2,m2,b);
    for(auto it=m1.begin();it!=m1.end();it++)
    {
        P n1=it->first;
        if(n1.first>b)continue;
        int cnt1=n1.second,num1=it->second;
        for(int cnt2=0;cnt2<=n-n/2;cnt2++)
        {
            P n2=P(b-n1.first,cnt2);
            if(m2.find(n2)!=m2.end())
            {
                int num2=m2[n2];
                ans+=(ll)num1*num2%mod*f[cnt1+cnt2]%mod*f[n-cnt1-cnt2]%mod;
                if(ans>=mod)ans-=mod;
            }
        }
    }
    return ans;
}
bool cmp(int x,int y)
{
    return x>y;
}
int main()
{
    f[0]=1;
    for(int i=1;i<=24;i++)f[i]=(ll)i*f[i-1]%mod;
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    sort(a+1,a+n+1,cmp);
    scanf("%d",&k);
    int ans=f[n];
    if(!k)printf("%d\n",ans);
    else if(k==1)
    {
        scanf("%d",&b1);
        ans-=Deal(b1);
        if(ans<0)ans+=mod;
        printf("%d\n",ans);
    }
    else
    {
        scanf("%d%d",&b1,&b2);
        if(b1>b2)swap(b1,b2);
        ans-=Deal(b1);
        if(ans<0)ans+=mod;
        if(b1==b2)printf("%d\n",ans);
        else
        {
            ans-=Deal(b2);
            if(ans<0)ans+=mod;
            b2-=b1;
            Solve2(a+1,n/2,M1);
            Solve2(a+n/2+1,n-n/2,M2);
            for(auto it=M1.begin();it!=M1.end();it++)
            {
                PP t1=it->first;
                P n1=t1.first,n2=t1.second;
                int sum1=n1.first,sum2=n2.first;
                int num1=it->second,cnt1=n1.second,cnt2=n2.second;
                for(int cnt3=0;cnt3<=n-n/2;cnt3++)
                    for(int cnt4=0;cnt3+cnt4<=n-n/2;cnt4++)
                    {
                        P n3=P(b1-sum1,cnt3),n4=P(b2-sum2,cnt4);
                        PP t2=PP(n3,n4);
                        if(M2.find(t2)!=M2.end())
                        {
                            int num2=M2[t2];
                            ans+=(ll)num1*num2%mod*f[cnt1+cnt3]%mod*f[cnt2+cnt4]%mod*f[n-cnt1-cnt2-cnt3-cnt4]%mod;
                            if(ans>=mod)ans-=mod;
                        }
                    }
            }
            printf("%d\n",ans);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值