[JZOJ]兔兔和蛋蛋

题目大意

以1为根的树有n个点,每个点i有个贡献值为a[i],还可以分配一个b[i],其中 b[i][0,m] 。对于每一对(i,j),若j在i子树中,且b[i]>b[j],则对该局面贡献a[i]。
现在问贡献值为0~K的分配方案分别有多少,模1e9+7。
这里写图片描述
注:开O2

分析

首先40分直接 98 再加爆掉K的剪枝就可以过了。
分析一下m从14跨越到1e8怎么做?那我们肯定只用知道我们的方案的b[i]的种类数,然后组合数就可以了嘛。
n=14,也不可折半,那么应该是个状压DP。直接按DFN做?没用啊。
很机智地,我们从小到大填b[i]。这里的b[i]是离散后的。
那么设f[i][j][s]为,填到i,当前贡献为j,n个点的填数与否状态为s,的方案数。
考虑转移,直接暴力枚举填i+1的点有哪些相当于枚举子集,这样是 O(nk3n) 的,萎成了40分。所以应该要每次只填一个。
那么这时考虑按dfn来做,发现每次只需要填一个数,不管是填i+1还是填i,都没有影响。这样就得出了新的算法:s的状态顺序为dfn,转移的话,对于一个f[i][j][s],我们可以填i,即同层转移,也可以填i+1,转移到新的一层。这样子时间复杂度是 O(nk2nn) 的。就可以过了,注意转移算新状态要O(1)算,即预处理一些东西。
我打的时候想复杂了,常数巨大,枚举了许多无用状态,当然复杂度还是一样的嘛····

代码

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
using namespace std;
#define fo(i,j,k) for(i=j;i<=k;i++)
#define fd(i,j,k) for(i=j;i>=k;i--)
typedef long long ll;
typedef double db;
const int N=17000;
const int mo=1e9+7;
int lim,i,j,k,l,s,ans,kx,n,m,K,ct;
int cnt[N],av[N][25],And[25][25],a[25];
int f[17][22][N],g[17][22][N],rev[25],fac[25];
int tt,b[25],next[25],first[25],dfn[25],ref[25],siz[25],t1;
void cr(int x,int y)
{
    tt++;
    b[tt]=y;
    next[tt]=first[x];
    first[x]=tt;
}
void dfs(int x)
{
    dfn[x]=++t1;
    siz[x]=1;
    ref[t1]=x;
    for(int p=first[x];p;p=next[p]) 
    {
        dfs(b[p]);
        siz[x]+=siz[b[p]];
    }
}
int ksm(int x,int y)
{
    int ret=1;
    while (y)
    {
        if (y&1) ret=1ll*ret*x%mo;
        y>>=1;
        x=1ll*x*x%mo;
    }
    return ret;
}
void predo()
{
    fo(i,0,lim)
        fo(j,0,n-1) 
            if (i&(1<<j)) 
                cnt[i]++;
            else
                av[i][++av[i][0]]=j+1;
    fo(i,0,n-1)
        fo(j,i,n-1)
            And[i][j]=And[i][j-1]+(1<<j);
    fac[0]=1;
    fo(i,1,n) fac[i]=1ll*fac[i-1]*i%mo;
    fo(i,0,n) rev[i]=ksm(fac[i],mo-2);
}
void trans(int i)
{
    int j,k,l,s,x,tmp;
    fo(j,0,K)
        fo(s,0,lim)
            if (f[i][j][s])
            {
                fo(k,1,av[s][0])
                {
                    ct++;
                    x=av[s][k];
                    tmp=a[ref[x]]*cnt[And[x-1][x+siz[ref[x]]-1-1]&s];
                    if (j+tmp<=K)
                    (g[x][j+tmp][s+(1<<(x-1))]+=f[i][j][s])%=mo;
                }
            }
    fo(k,1,n)
        fo(j,0,K)
            fo(s,0,lim)
                if (g[k][j][s])
                {
                    for(l=1;l<=av[s][0]&&av[s][l]<=k;l++);
                    fo(l,l,av[s][0])
                    {
                        ct++;
                        x=av[s][l];
                        tmp=a[ref[x]]*cnt[And[x-1][x+siz[ref[x]]-1-1]&s];
                        if (j+tmp<=K)
                        (g[x][j+tmp][s+(1<<(x-1))]+=g[k][j][s])%=mo;
                    }
                }
}
int c(int n,int m)
{
    int ret=1,l;
    fd(l,m,m-n+1) ret=1ll*ret*l%mo;
    return 1ll*ret*rev[n]%mo;
}
int main()
{
    scanf("%d %d %d\n",&n,&m,&K);
    m++;
    fo(i,1,n) scanf("%d",a+i);
    fo(i,2,n)
    {
        scanf("%d",&j);
        cr(j,i);
    }
    dfs(1);
    f[0][0][0]=1;
    lim=(1<<n)-1;
    predo();
    fo(i,0,min(n,m)-1)
    {
        trans(i);
        fo(k,1,n)
            fo(j,0,K)
                fo(s,0,lim)
                {
                    f[i+1][j][s]=(f[i+1][j][s]+g[k][j][s])%mo;
                    g[k][j][s]=0;
                }
    }
    fo(k,0,K)
    {
        ans=0;
        fo(i,1,min(n,m))
        {
            kx=c(i,m);
            ans=(1ll*ans+1ll*kx*f[i][k][lim])%mo;
        }
        printf("%d\n",ans);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值