Codeforces Round #291 (Div. 2) E. Darth Vader and Tree (dp + 矩阵快速幂优化)

题目链接

题面:
在这里插入图片描述

题意:
有棵无限大的有根树,每个节点都有N个孩子,每个孩子距离父亲节点的距离为di.求距离根节点距离<=x的节点个数。

题解:
注意到 di <= 100
我们 设 dp [ i ] 为距离根节点为 i 的点的个数,sum [ i ] 为距离根节点小于等于 i 的点的个数。
cnt [ i ] 为距离父亲节点 为 i 的点的个数。
那么有转移方程。

for(int i=1;i<=100;i++)
{
	for(int j=1;j<=i;j++)
	{
		dp[i]=(dp[i]+dp[i-j]*cnt[j])%MOD;
	}
	sum[i]=(sum[i-1]+dp[i])%MOD;
}

注意到 1<=di<=100。
那么 dp [ x ] 只能由 dp[x - 1 ]----dp [ x - 100 ] 转移而来。
用矩阵快速幂优化。只保存一百项 dp,在转移的过程中同时计算 sum

代码:

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<queue>
#include<bitset>
#include<map>
#include<set>
#define ll long long
#define llu unsigned ll
#define pr make_pair
#define pb push_back
#define ui unsigned int
#define lc (cnt<<1)
#define rc (cnt<<1|1)
#define len(x)  (t[(x)].r-t[(x)].l+1)
#define tmid ((l+r)>>1)
#define forhead(x) for(int i=head[(x)];i;i=nt[i])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
using namespace std;
const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const double dnf=1e18;
const int mod=1e9+7;
const double eps=1e-8;
const double pi=acos(-1.0);
const int maxn=100100;
const int maxm=100100;
const int up=100000;
const int hashp=13331;
const int hashpp=131;

struct node
{
    ll a[110][110];
};

node operator * (const node &a,const node &b)
{
    node c;
    memset(c.a,0,sizeof(c.a));
    for(int i=1;i<=101;i++)
    {
        for(int j=1;j<=101;j++)
        {
            for(int k=1;k<=101;k++)
                c.a[i][j]=(c.a[i][j]+a.a[i][k]*b.a[k][j])%mod;
        }
    }
    return c;
}

node mypow(node a,ll b)
{
    node ans;
    memset(ans.a,0,sizeof(ans.a));
    for(int i=1;i<=101;i++)
        ans.a[i][i]=1;
    while(b)
    {
        if(b&1) ans=ans*a;
        a=a*a;
        b>>=1;
    }
    return ans;
}

ll d[maxn];
ll cnt[110],dp[110],sum[110];

int main(void)
{
    int n,x;
    scanf("%d%d",&n,&x);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&d[i]);
        cnt[d[i]]++;
    }
    sum[0]=dp[0]=1;
    for(int i=1;i<=100;i++)
    {
        for(int j=1;j<=i;j++)
            dp[i]=(dp[i]+dp[i-j]*cnt[j])%mod;
        sum[i]=(sum[i-1]+dp[i])%mod;
    }
    if(x<=100)
    {
        printf("%lld\n",sum[x]);
        return 0;
    }
    node ans;
    memset(ans.a,0,sizeof(ans.a));
    for(int i=1;i<=99;i++)
        ans.a[i][i+1]=1;
    for(int i=1;i<=100;i++)
        ans.a[101][i]=ans.a[100][i]=cnt[100-i+1];
    ans.a[101][101]=1;
    ans=mypow(ans,x-100);
    ll res=0;
    for(int i=1;i<=100;i++)
        res=(res+dp[i]*ans.a[101][i])%mod;
    res=(res+sum[100]*ans.a[101][101])%mod;
    printf("%lld\n",res);
    return 0;
}


于是,我又想到,这个dp和sum都是线性的,那么直接杜教BM可否?
于是直接打到 dp [ 1000 ] , sum [ 1000 ] 然后杜教。
比我矩阵快速幂快多了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<string>
#include<queue>
#include<bitset>
#include<map>
#include<set>
#define ll long long
#define llu unsigned ll
#define pr make_pair
#define pb push_back
#define ui unsigned int
#define lc (cnt<<1)
#define rc (cnt<<1|1)
#define len(x)  (t[(x)].r-t[(x)].l+1)
#define tmid ((l+r)>>1)
#define forhead(x) for(int i=head[(x)];i;i=nt[i])
#define max(x,y) ((x)>(y)?(x):(y))
#define min(x,y) ((x)>(y)?(y):(x))
using namespace std;
const int inf=0x3f3f3f3f;
const ll lnf=0x3f3f3f3f3f3f3f3f;
const double dnf=1e18;
const int mod=1e9+7;
const double eps=1e-8;
const double pi=acos(-1.0);
const int maxn=100100;
const int maxm=100100;
const int up=100000;
const int hashp=13331;


namespace linear_seq
{
    #define SZ(x) ((int)(x).size())
    typedef vector<int> VI;
    typedef pair<int,int> PII;
    const int N=10010;
    ll res[N],base[N],c[N],md[N];
    vector<int> Md;
    const ll mod=1e9+7;

    ll powmod(ll a,ll b)
    {
        ll res=1;
        a%=mod;
        for(;b;b>>=1)
        {
            if(b&1)
                res=res*a%mod;
            a=a*a%mod;
        }
        return res;
    }

    void mul(ll *a,ll *b,int k)
    {
        for(int i=0;i<k+k;++i) c[i]=0;
        for(int i=0;i<k;++i)
            if(a[i])for(int j=0;j<k ;++j)
                    c[i+j]=(c[i+j]+a[i]*b[j])%mod;

        for (int i=k+k-1;i>=k;i--)
            if(c[i])for(int j=0;j<(int)Md.size();++ j)
                    c[i-k+Md[j]]=(c[i-k+Md[j]]-c[i]*md[Md[j]])%mod;

        for(int i=0;i<k;++i) a[i]=c[i];
    }

    int solve(ll n,VI a,VI b)
    {
        ll ans=0,pnt=0;
        int k=SZ(a);
        for(int i=0;i<k;++i) md[k-1-i]=-a[i];
        md[k]=1;Md.clear();
        for(int i=0;i<k;++i)
            if(md[i]!=0)Md.push_back(i);

        for(int i=0;i<k;++i) res[i]=base[i]=0;
        res[0]=1;

        while ((1ll<<pnt)<=n)
            pnt++;

        for (int p=pnt;p>=0;p--)
        {
            mul(res,res,k);
            if ((n>>p)&1)
            {
                for (int i=k-1;i>=0;i--) res[i+1]=res[i];
                res[0]=0;
                for(int j=0;j<(int)Md.size();++ j)
                    res[Md[j]]=(res[Md[j]]-res[k]*md[Md[j]])%mod;
            }
        }

        for(int i=0;i<k;i++) ans=(ans+res[i]*b[i])%mod;
        if (ans<0) ans+=mod;
        return ans;
    }

    VI BM(VI s)
    {
        VI C(1,1),B(1,1);
        int L=0,m=1,b=1;
        for(int n=0;n<(int)s.size();++n)
        {
            ll d=0;
            for(int i=0;i<L+1;++ i)
                d=(d+(ll)C[i]*s[n-i])%mod;
            if(d==0) ++m;
            else if(2*L<=n)
            {
                VI T=C;
                ll c=mod-d*powmod(b,mod-2)%mod;
                while (SZ(C)<SZ(B)+m)C.push_back(0);
                for(int i=0;i<(int)B.size();++i) C[i+m]=(C[i+m]+c*B[i])%mod;
                L=n+1-L; B=T; b=d; m=1;
            }
            else
            {
                ll c=mod-d*powmod(b,mod-2)%mod;
                while (SZ(C)<SZ(B)+m)C.push_back(0);
                for(int i=0;i<(int)B.size();++ i) C[i+m]=(C[i+m]+c*B[i])%mod;
                ++m;
            }
        }
        return C;
    }

    ll gao(VI a,ll n)
    {
        VI c=BM(a);
        c.erase(c.begin());
        for(int i=0;i<(int)c.size();++i) c[i]=(mod-c[i])%mod;
        return (ll)solve(n,c,VI(a.begin(),a.begin()+SZ(c)));
    }
};

ll d[maxn];
ll cnt[1100],dp[1100],sum[1100];
vector<int>vc;
int main(void)
{
    int n,x;
    scanf("%d%d",&n,&x);
    for(int i=1;i<=n;i++)
    {
        scanf("%lld",&d[i]);
        cnt[d[i]]++;
    }
    sum[0]=dp[0]=1;
    vc.pb(1);
    for(int i=1;i<=1000;i++)
    {
        for(int j=1;j<=min(i,100);j++)
            dp[i]=(dp[i]+dp[i-j]*cnt[j])%mod;
        sum[i]=(sum[i-1]+dp[i])%mod;
        vc.pb(sum[i]);
    }
    printf("%lld\n",linear_seq::gao(vc,x));
    return 0 ;
}








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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值