Atcoder ABC387F Count Arrays 题解 [ 绿 ] [ 基环树 ] [ 树形 dp ] [ 前缀和优化 ]

Count Arrays:一眼秒的计数题。

思路

显然,把小于等于的条件化为大的向小的连单向边,每个数的入度都是 1,就会形成一个基环树森林。

那么考虑这个环上能填什么数。因为所有数都小于等于他后面的数,所以所有数都只能相等。这就启发我们在基环树上缩点之后再进行计数。

那么当缩完点计数时如何计算呢?有个很简单的 dp,定义 dpi,j 表示考虑到节点 i,节点 ij 的方案数,则很容易能写出转移:

dpi,j=k=1|soni|(a=1jdpsoni,k,a)

直接转移是 O(nm2) 的,前缀和优化即可做到 O(nm)

答案计算时将所有基环树的答案乘起来即可。

代码

#include <bits/stdc++.h>
#define fi first
#define se second
#define lc (p<<1)
#define rc ((p<<1)|1)
#define eb(x) emplace_back(x)
#define pb(x) push_back(x)
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ldb;
using pi=pair<int,int>;
using pii=pair<int,pi>;
const ll mod=998244353;
int n,m,a[10005];
int dfn[10005],low[10005],stk[10005],cnt=0,tp=0,scc[10005],tot=0;
bitset<10005>instk,vis,rd;
vector<int>g[10005],tr[10005];
ll ans=1,dp[3005][3005],f[3005][3005];
void tarjan(int u)
{
    dfn[u]=low[u]=++tot;
    instk[u]=1,stk[++tp]=u;
    for(auto v:g[u])
    {
        if(dfn[v]==0)
        {
            tarjan(v);
            low[u]=min(low[u],low[v]);
        }
        else if(instk[v])
        {
            low[u]=min(low[u],dfn[v]);
        }
    }
    if(dfn[u]==low[u])
    {
        int now;
        cnt++;
        do{
            now=stk[tp--];
            instk[now]=0;
            scc[now]=cnt;
        }while(now!=u);
    }
}
void dfs(int u)
{
    for(int i=1;i<=m;i++)dp[u][i]=1;
    for(auto v:tr[u])
    {
        dfs(v);
        for(int i=1;i<=m;i++)
        {
            dp[u][i]=(dp[u][i]*f[v][i])%mod;
        }
    }
    for(int i=1;i<=m;i++)f[u][i]=(f[u][i-1]+dp[u][i])%mod;
}
int main()
{
    //freopen("sample.in","r",stdin);
    //freopen("sample.out","w",stdout);
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        g[a[i]].pb(i);
    }
    for(int i=1;i<=n;i++)if(dfn[i]==0)tarjan(i);
    for(int i=1;i<=n;i++)
    {
        int fu=scc[i];
        for(auto v:g[i])
        {
            int fv=scc[v];
            if(fu!=fv)
            {
                tr[fu].pb(fv);
                rd[fv]=1;
            }
        }
    }
    for(int i=1;i<=cnt;i++)
    {
        if(rd[i]==0)
        {
            dfs(i);
            ans=(ans*f[i][m])%mod;
        }
    }
    cout<<ans;
    return 0;
}
posted @   KS_Fszha  阅读(16)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
点击右上角即可分享
微信分享提示