Count Arrays:一眼秒的计数题。
思路
显然,把小于等于的条件化为大的向小的连单向边,每个数的入度都是 \(1\),就会形成一个基环树森林。
那么考虑这个环上能填什么数。因为所有数都小于等于他后面的数,所以所有数都只能相等。这就启发我们在基环树上缩点之后再进行计数。
那么当缩完点计数时如何计算呢?有个很简单的 dp,定义 \(dp_{i,j}\) 表示考虑到节点 \(i\),节点 \(i\) 填 \(j\) 的方案数,则很容易能写出转移:
\[dp_{i,j}=\prod_{k=1}^{\left|son_i\right|}(\sum_{a=1}^{j}dp_{son_{i,k},a})
\]
直接转移是 \(O(nm^2)\) 的,前缀和优化即可做到 \(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;
}