题目链接

考虑枚举好点的集合。此时要考虑的问题是如何填入\(1\sim n\)这些数使得恰好我们枚举到的这些点是好点,即:求出有多少种合法的填数方案。

\(1\)号点一定是好点。那么除\(1\)号点外每个点的祖先中必有至少一个好点。定义\(u\)的祖先中离\(u\)最近的好点,为\(u\)的祖先好点\((2\leq u\leq n)\)。

对除\(1\)外的每个点:

  • 如果它是好点,我们就从它的祖先好点向它连一条边。(称为“第一类边”)

  • 如果它是不是好点,就从它向它的祖先好点连一条边。(称为“第二类边”)

那么对于任意一条边\(a\rightarrow b\),它代表的含义是一种限制:\(val_a<val_b\)。显然,一种填数方案合法,当且仅当满足所有这些限制。

这些边的方向有上有下,让我们非常头疼。如果只有一些第一类限制,问题相当于给定一棵树,求有多少种填数方案使得祖先的权值总是小于后代。这是非常简单的经典问题(其答案是\(n!\prod_u\frac{1}{sz_u}\))。

所以我们可以考虑对第二类边做容斥。即:我们强行把一些坏点向其祖先好点连的边的方向反过来,求出此时的填数方案数,再乘以\((-1)^{\text{被反向的坏点个数}}\),加入答案。当然,我们不能暴力枚举使哪些坏点反向,否则总复杂度变为\(O(3^n\times n)\),且没什么优化前途。

考虑DP。设\(dp[u][i]\)表示考虑了\(u\)的子树,共选出\(i\)个有限制的点(既包括所有原本就是好点的点,也包括一些被容斥强行反向的坏点)的填数方案数。注意:
这里的填数方案只考虑了这\(i\)个有限制的点,对于无限制的坏点我们统计答案时再计算。并且对于这\(i\)个有限制的点,我们只考虑它们填的数的相对大小关系,这样方便转移。

把\(u\)的子树的方案合并起来,就是做一个简单的树上背包。也就是大家所熟知的那个看起来是\(O(n^3)\)实际上是\(O(n^2)\)的算法。这里不再赘述。

设我们把\(u\)的所有儿子的DP值合并得到\(g\),即:在\(u\)的所有儿子子树内,选出\(i\)个有限制的点的方案数为\(g[i]\)。

然后转移,考虑\(u\)。

  • 若\(u\)是好点。则\(u\)上填的数必是\(i\)个点中最小的,因为我们这里只考虑相对大小关系,因此\(u\)上一定填\(1\)。故\(dp[u][i]=g[i-1]\)。

  • 若\(u\)不是好点。

    • 若\(u\)被选为有限制的点(即\(u\)向其祖先好点连的边被在容斥中反向了):则\(u\)上可以填任意数(因为\(u\)有祖先好点,这个祖先好点在转移时只能填\(1\),所以它的值必然比\(u\)小),所以\(u\)有\(i\)种填法。再乘上容斥系数。所以我们得到:\(dp[u][i]=(-1)\times i\times g[i-1]\)。

    • 若\(u\)没有被选为有限制的点,则\(dp[u][i]=g[i]\)。

    综上所述:\(dp[u][i]=(-1)\times i\times g[i-1]+g[i]\)。

考虑统计在当前好点集合下的方案数。

设好点集合大小为\(c\)。枚举选出了\(i\)个有限制的点。则方案数为:

\[
x=\sum_{i=c}^{n}dp[1][i]\times{n\choose i}\times(n-i)!
\]

我们把这个方案数贡献到答案里,即\(ans+=x\times k^c\)。

至此,我们实现了\(O(2^n\times n^2)\)的复杂度并喜提\(0\)分。但其实我们离正解只有一步之遥。

我们完全可以不枚举好点集合,在DP转移时讨论\(u\)是不是好点即可。至于\(k^c\)这个东西,我们在转移时,如果认为\(u\)是好点,就把它的DP值乘以一个\(k\)。即:\(dp[u][i]=k\times g[i-1]\)。

时间复杂度\(O(n^2)\)。

参考代码:

//problem:nflsoj489
#include <bits/stdc++.h>
using namespace std;

#define pb push_back
#define mk make_pair
#define lob lower_bound
#define upb upper_bound
#define fst first
#define scd second

typedef unsigned int uint;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int,int> pii;

namespace Fread{
const int MAXN=1<<20;
char buf[MAXN],*S,*T;
inline char getchar(){
    if(S==T){
        T=(S=buf)+fread(buf,1,MAXN,stdin);
        if(S==T)return EOF;
    }
    return *S++;
}
}//namespace Fread
#ifdef ONLINE_JUDGE
    #define getchar Fread::getchar
#endif
inline int read(){
    int f=1,x=0;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline ll readll(){
    ll f=1,x=0;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
/*  ------  by:duyi  ------  */ // dysyn1314
const int MAXN=5000,MOD=998244353;
inline int mod1(int x){return x<MOD?x:x-MOD;}
inline int mod2(int x){return x<0?x+MOD:x;}
inline void add(int &x,int y){x=mod1(x+y);}
inline void sub(int &x,int y){x=mod2(x-y);}
inline int pow_mod(int x,int i){int y=1;while(i){if(i&1)y=(ll)y*x%MOD;x=(ll)x*x%MOD;i>>=1;}return y;}
int n,K,fac[MAXN+5],invf[MAXN+5],sz[MAXN+5],dp[MAXN+5][MAXN+5],tmp[MAXN+5];
vector<int>G[MAXN+5];
inline int comb(int n,int k){
    if(n<k)return 0;
    else return (ll)fac[n]*invf[k]%MOD*invf[n-k]%MOD;
}
void dfs(int u,int fa){
    sz[u]=1;
    dp[u][0]=1;
    for(int i=0;i<(int)G[u].size();++i){
        int v=G[u][i];
        if(v==fa)continue;
        dfs(v,u);
        for(int j=0;j<=sz[v]+sz[u];++j)tmp[j]=0;
        for(int j=0;j<=sz[v];++j){
            for(int k=0;k<=sz[u];++k){
                add(tmp[j+k],(ll)dp[v][j]*dp[u][k]%MOD*comb(j+k,j)%MOD);
            }
        }
        sz[u]+=sz[v];
        for(int j=0;j<=sz[u];++j)dp[u][j]=tmp[j];
    }
    for(int i=0;i<=sz[u];++i)tmp[i]=dp[u][i];
    for(int i=1;i<=sz[u];++i){
        dp[u][i]=0;
        add(dp[u][i],(ll)K*tmp[i-1]%MOD);//好点
        if(u!=1){
            sub(dp[u][i],(ll)i*tmp[i-1]%MOD);//假好点(边被反向了的坏点)
            add(dp[u][i],tmp[i]);//u被孤立了
        }
    }
}
int main() {
    n=read();K=read();
    fac[0]=1;for(int i=1;i<=n;++i)fac[i]=(ll)fac[i-1]*i%MOD;
    invf[n]=pow_mod(fac[n],MOD-2);
    for(int i=n-1;i>=0;--i)invf[i]=(ll)invf[i+1]*(i+1)%MOD;
    for(int i=1,u,v;i<n;++i)u=read(),v=read(),G[u].pb(v),G[v].pb(u);
    dfs(1,0);
    int ans=0;
    for(int i=n,s=1;i>=1;s=(ll)s*i%MOD,--i){
        add(ans,(ll)dp[1][i]*s%MOD);
    }
    cout<<ans<<endl;
    return 0;
}

题解 nflsoj489 【六校联合训练 CSP #15】小D与随机的更多相关文章

  1. NFLSOJ 1072 - 【2021 六校联合训练 NOIP #1】异或(FWT+插值)

    题面传送门 一道非常不错的 FWT+插值的题 %%%%%%%%%%%% 还是那句话,反正非六校的看不到题对吧((( 方便起见在下文中设 \(n=2^d\). 首先很明显的一点是这题涉及两个维度:异或和 ...

  2. NFLSOJ 1060 - 【2021 六校联合训练 NOI #40】白玉楼今天的饭(子集 ln)

    由于 NFLSOJ 题面上啥也没有就把题意贴这儿了( 没事儿,反正是上赛季的题,你们非六校学生看了就看了,况且看了你们也没地方交就是了 题意: 给你一张 \(n\) 个点 \(m\) 条边的图 \(G ...

  3. 题解 nflsoj553 【六校联合训练 省选 #10】飞

    题目链接 我们称"简要题意"给出的三个要求分别为"条件1","条件2","条件3". 条件3长得比较丑,考虑转化一下.把 ...

  4. 题解 nflsoj550 【六校联合训练 省选 #9】序列

    题目链接 以下把值域(题面里的\(lim\))记做\(m\). 考虑求\(k\)的答案.考虑每个位置对答案的贡献,枚举位置\(i\),再枚举\(a[i]\)的值\(x\).设: \[ F(k)=\su ...

  5. HDU 5358(2015多校联合训练赛第六场1006) First One (区间合并+常数优化)

    pid=5358">HDU 5358 题意: 求∑​i=1​n​​∑​j=i​n​​(⌊log​2​​S(i,j)⌋+1)∗(i+j). 思路: S(i,j) < 10^10 & ...

  6. 2017多校联合训练2—HDU6054--Is Derek lying?(思维题)

    Is Derek lying? Time Limit: 3000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)T ...

  7. 2017ACM暑期多校联合训练 - Team 2 1006 HDU 6050 Funny Function (找规律 矩阵快速幂)

    题目链接 Problem Description Function Fx,ysatisfies: For given integers N and M,calculate Fm,1 modulo 1e ...

  8. hdu 4649 Professor Tian 多校联合训练的题

    这题起初没读懂题意,悲剧啊,然后看了题解写完就AC了 题意是给一个N,然后给N+1个整数 接着给N个操作符(只有三种操作  即  或 ,与 ,和异或 |   &  ^ )这样依次把操作符插入整 ...

  9. HDU 4643 GSM 暑期多校联合训练第五场 1001

    点击打开链接 我就不说官方题解有多坑了 V图那么高端的玩意儿 被精度坑粗翔了 AC前 AC后 简直不敢相信 只能怪自己没注意题目For the distance d1 and d2, if fabs( ...

随机推荐

  1. 堆(c++)

    5分钟速成堆 FBI⚠WARNING 本文要素过多 吐槽 堆是我迄今为止学过最简单的数据结构 我还没学会最小生成树.最短路时就学会了 堆实用高效,值得推荐 (如果你看完了这篇文章还不会,你可以直接Co ...

  2. Centos610无桌面安装Docker-内核升级

    1.查看当前操作系统和系统内核 (此处只需要注意一项centos6的docker源只有64位的,x86_64,32位的直接换系统吧) 查看当前内核版本uname -r 2.6.32-754.el6.x ...

  3. Newtonsoft.Json 版本不一致导致错误

    可以在配置文件添加这部分,其他版本的不一致,也可使用这种方式解决. <runtime> <assemblyBinding xmlns="urn:schemas-micros ...

  4. Python 中的类与对象 初认识

    一:类的声明 1类的关键字: 从第一天第一个项目起我们就接触过关键字,比如False True is not None return for while elif else import等等,这是语言 ...

  5. cin和cout输⼊输出

    写再最前面:摘录于柳神的笔记:  就如同 scanf 和 printf 在 stdio.h 头⽂件中⼀样, cin 和 cout 在头⽂件 iostream ⾥⾯,看名字就知 道, io 是输⼊输出 ...

  6. sparkRDD:第1节 RDD概述;第2节 创建RDD

    RDD的特点: (1)rdd是数据集: (2)rdd是编程模型:因为rdd有很多数据计算方法如map,flatMap,reduceByKey等: (3)rdd相互之间有依赖关系: (4)rdd是可以分 ...

  7. Nacos client 客户端cpu占用100% 问题排查和解决方案

    Nacos version:1.1.3client version:1.0.0 dependency: 'org.springframework.cloud:spring-cloud-alibaba- ...

  8. Django 执行 manage 命令方式

    本人使用的Pycharm作为开发工具,可以在顶部菜单栏的Tools->Run manage.py Task直接打开manager 命令控制台 打开后在底部会有命令窗口: 或者,也可以在Pytho ...

  9. 「HNOI2010」弹飞绵羊

    「HNOI2010」弹飞绵羊 传送门 考虑分块. 每一个位置 \(i\) ,记 \(to[i]\) 表示从这个位置一直往右跳回落在哪个位置. 然后修改的时候直接暴改,查询也是暴跳,复杂度 \(O(n ...

  10. FFmpeg笔记-基本使用

    FFmpeg是目前最牛逼的开源跨平台音视频处理工具. 准备知识 我不是音视频编解码出身的,对于这一块非常的不了解,导致在学习FFmpeg的时候云里雾里的,所以学习之前最好看些资料对音视频编解码有点认识 ...