[JZOJ5511] 送你一个DAG

Description

给出一个 n 个点 m 条边的 DAG 和参数 k。
定义一条经过 l 条边的路径的权值为 lk. 对于 i = 1…n, 求出所有 1 到 i 的路径的权值之和, 对 998244353 取模.
对于前 20% 的数据, n ≤ 2000,m ≤ 5000;
对于另 10% 的数据, k = 1;
对于另 20% 的数据, k ≤ 30;
对于 100% 的数据, 1 ≤ n ≤ 100000,1 ≤ m ≤ 200000,0 ≤ k ≤ 500, 保证从 1 出发可以到达每 个点, 可能会有重边.

Solution

Fi,j 表示到第i个点长度为j的路径有多少条
直接转移,最后算答案。
复杂度N^2

考虑优化
假设到u这个点有长度为a,b,c的三条路径
要从 ak+bk+ck 转移到 (a+1)k+(b+1)k+(c+1)k

二项式展开,组合数提出来
变成 i=0kCik(ai+bi+ci)
与a,b,c具体无关,只和a,b,c的i次方和有关,即当k=i时u这个点的答案
那么状态可以改成 Fi,j 表示i这个点当k=j时的答案
转移是O(NK^2)的,即使用NTT优化也过不去

回到最原本的式子
Ansi=len(i)k

此处有关于第二类斯特林数的公式
nm=i=0nS(m,i)Pin

考虑其组合意义,S(m,i)表示m个有差别的球放入i个无差别的盒子中,要求盒子非空的方案数, nm 是m个有差别的球,n个有差别的盒子随便乱放,式子就是枚举多少个盒子非空然后放球

那么

Ansi=j=0len(i)S(k,j)Pjlen(i)

根据斯特林数的意义,j>k时显然S为0,再把P拆成C*阶乘,交换主体

=j=0len(i)j!S(k,j)Cjlen(i)

那么现在改变状态,设 Fi,j 表示第i个点的 Cjlen(i)

根据杨辉三角,可以看出 Fi,j=Fi,j+Fi,j1
于是可以O(1)转移,最后再统计答案

复杂度O(MK)

Code

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
#define N 100005
#define M 505
#define mo 998244353
#define LL long long
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fod(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
LL f[2005][2005],s1[N],h[N];
int n,m,lim,nt[2*N],fs[N],dt[2*N],d[N],rd[N],mx[N],m1;
LL cf[2005];
void make()
{
    int l=0,r=0;
    fo(i,1,n) if(rd[i]==0) d[++r]=i;
    f[1][0]=1;
    while(l<r)
    {
        int k=d[++l];
        for(int i=fs[k];i;i=nt[i])
        {
            int p=dt[i];
            rd[p]--;
            if(rd[p]==0) d[++r]=dt[i];
            mx[p]=max(mx[p],mx[k]+1);
            fo(j,0,mx[k]) 
            {
                (f[p][j+1]+=f[k][j])%=mo;
            }
        }
    }
    fo(i,1,n)
    {
        LL s=0;
        fo(j,1,mx[i]) 
        {
            s=(s+f[i][j]*cf[j]%mo)%mo;
        }
        printf("%lld\n",s);
    }
}
LL ksm(LL k,LL n)
{
    LL s=1;
    for(;n;k=k*k%mo,n>>=1) if(n&1) s=s*k%mo;
    return s;
}
void link(int x,int y)
{
    nt[++m1]=fs[x];
    dt[fs[x]=m1]=y;
}
int main()
{
    cin>>n>>m>>lim;
    fo(i,1,m)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        link(x,y);
        rd[y]++;
    }
    if(lim==1)
    {
        int l=0,r=0;
        fo(i,1,n) if(rd[i]==0) d[++r]=i;
        h[1]=1,s1[1]=0;
        while(l<r)
        {
            int k=d[++l];
            for(int i=fs[k];i;i=nt[i])
            {
                int p=dt[i];
                rd[p]--;
                if(rd[p]==0) d[++r]=dt[i];
                s1[p]=(s1[p]+s1[k]+h[k])%mo;
                h[p]=(h[p]+h[k])%mo;
            }
        }
        fo(i,1,n) printf("%lld\n",s1[i]);
    }
    else
    {
        fo(i,1,n) cf[i]=ksm(i,lim);
        make();
    }   
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值