清华冬令营2018模拟【送你一个DAG】

first of all

  蒟蒻不太会写那些运算符什么的,所以关于本题的推导公式可能不那么清晰,推荐一篇好文

 

  点此跳转

1 送你一个 DAG (xmasdag.cpp/in/out, 1s, 512MB)

1.1 Description

  送你一个 n 个点 m 条边的 DAG 和参数 k, 定义一条经过 l 条边的路径的权值为 lk.

  对于 i = 1...n, 求出所有 1 到 i 的路径的权值之和, 对 998244353 取模.

1.2 Input Format

  第一行三个整数 n,m,k, 分别表示 DAG 的点数, 边数和参数.

  接下来 m 行, 每行两个整数 ui,vi, 表示一条从 ui 到 vi 的有向边.

1.3 Output Format

  共输出 n 行, 第 i 行一个整数, 表示 i 号点的答案.

 

分析

  刚看完题目,感叹这topsort一下不就完了吗?开心。诶,手玩样例怎么玩不出来啊。仔细想想,发现是我脑回路太简单了,对于每一个点,从1到它的路径有很多方案,不同的方案长度不一定相同。而我们对于每一个点要求的是Σlen(i)^k,这个。。很恶心吧。

20 points

  很简单的一种思路是,定义一个二维数组f,f【i,j】表示第i个点,从1到它的长度为j的路径条数。这个f数组可以O(m*n)求,然后对于每一个点,我们依次累加就好。

30 points

  数据有一组是k=1的情况,这种情况,只需要记录从1到每个点的路径数,O(n)递推就可以了。

50 points

  你看到k的范围是500,想想怎么把复杂度转到k上面。我们假设到u这个点,有三条路径,长度分别是a,b,c,那它转移到下一个点v的话,长度就分别是a+1,b+1,c+1。

  那么ans v=(a+1)^k+(b+1)^k+(c+1)^k。二次项展开,合并一下,得到ΣC(k,i)*(a^i+b^i+c^i)。发现答案只与上一个点的答案有关,因此可以递推,复杂度为O(n*k^2)。

100 points

  还是最初的式子,ans【i】=Σlen(i)^k。有一个有关第二类斯特林数的公式,n^m=ΣS(m,i)*P(n,i),其中S(i,j)表示将i个不同小球放到j个相同箱子(箱子不为空)的方案数,P是排列数,n^m是将m个不同小球,随意摆放在n个不同的箱子里的方案数,显然等式成立;当i<j的时候,S(i,j)为0;排列数可以转换为组合数乘阶乘,

  所以ans【i】=Σ( Σ S(k,j)*j!* C(len(i),j))=Σ S(k,j)*j!* Σ C(len(i),j)

  首先斯特林数我们可以O(k*k)递推。然后求ans我们只需要再求一个Σ C(len(i),j) 就好。你发现这个东西好像可以通过杨辉三角变化一下,Σ C(len(i),j)=Σ (C(len(i)-1,j)+C(len(i)-1,j-1)),那么我们可以在topsort的时候递推即可。最后直接求答案就行。

#include<bits/stdc++.h>
#define ll long long
#define mod 998244353
#define maxn 1e5+5
using namespace std;
char buf[1<<15],*fs,*ft;
inline char getc(){return (fs==ft&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),fs==ft))?0:*fs++;}
inline int read(){
    int n=1,num=0;  char ch=getc();
    while(!isdigit(ch)) {n=(ch=='-')?-1:1;ch=getc();}
    while(isdigit(ch))  {num=(num<<1)+(num<<3)+(ch^48);ch=getc();}
    return n*num;
}
ll f[100010][505],g[505][505];
struct gg{int y,next;}a[300010];
int ru[100010],h[100010],link[100010];
int m,n,tot,p,z;
ll ans,s;
void add(int x,int y)
{
    a[++tot].y=y;a[tot].next=link[x];link[x]=tot;
}
int main()
{
    freopen("xmasdag.in","r",stdin);
    freopen("xmasdag.out","w",stdout);
    n=read();m=read();p=read();
    for(int i=1;i<=m;++i)
    {
        int x=read(),y=read();
        add(x,y);    ++ru[y];
    }
    h[1]=1;int head=1,tail=0;
    f[1][0]=1;
    while(head>tail)
    {
        int x=h[++tail];
        for(int i=link[x];i;i=a[i].next)
        {
            int v=a[i].y;
            f[v][0]=(f[v][0]+f[x][0])%mod;
            for(int j=1;j<=p;++j)    f[v][j]=(f[v][j]+(f[x][j]+f[x][j-1])%mod)%mod;
            --ru[v];
            if(!ru[v])    h[++head]=v;
        }
    }
    g[0][0]=1;
    for(int i=1;i<=p;++i)
    for(int j=0;j<=p;++j)
    {
        g[i][j]=1ll*j*g[i-1][j]%mod;
        if(j)    g[i][j]=(g[i][j]+g[i-1][j-1])%mod;
    }
    for(int i=1;i<=n;++i)
    {
        s=1;ans=0;
        for(int j=1;j<=p;++j)
        {
            if(j)    s=(s*j)%mod;
            ans=(ans+((s*g[p][j])%mod)*f[i][j])%mod;
        }
        printf("%lld\n",ans);
    }
    
    return 0;
}

 

speech.gif posted on 2019-01-15 19:25 kgxpbqbyt 阅读( ...) 评论( ...) 编辑 收藏
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值