题目:
分析:
按照题意,只需要找到最长上升子序列的数量和每个数对最长上升子序列的贡献次数即可,可以二分找到经过每个数的最长上升子序列的长度,但这样不好统计数量;考虑dp,定义dp[i]为以第i个数结尾的最长上升子序列的长度,DP[i]为其数量,容易想到转移方程:dp[i] = max{dp[j],0<j<i && a[j] < a[i]} + 1,DP[i] = sum{DP[j],0<j<i && dp[j] = dp[i]-1},线段树维护区间最大值及其数量即可快速转移,每次都是在前缀上查询,也可以用树状数组维护;再从后到前来一遍,就可以求出经过每个点的最长上升子序列的长度和数量
代码:
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const int mod = 998244353;
const int maxn = 5e5+55;
struct node{
LL len,num;
}tr[maxn];
int n,m,a[maxn],c[maxn];
node pre[maxn],cur[maxn];
inline int lowbit(int x){
return x&(-x);
}
void add(int x,node e){
while(x <= m){
if(tr[x].len == e.len) tr[x].num = (tr[x].num+e.num)%mod;
else if(tr[x].len < e.len){
tr[x].len = e.len;
tr[x].num = e.num;
}
x += lowbit(x);
}
}
node query(int x){
node res = {0,0};
while(x > 0){
if(res.len == tr[x].len) res.num = (res.num+tr[x].num)%mod;
else if(res.len < tr[x].len){
res.len = tr[x].len;
res.num = tr[x].num;
}
x -= lowbit(x);
}
return res;
}
LL qpow(LL a,LL x){
LL res = 1;
while(x){
if(x&1) res = res * a % mod;
a = a * a % mod;
x >>= 1;
}
return res;
}
int main(){
cin >> n;
for(int i = 0;i < n; ++i) cin >> a[i],c[i] = a[i];
sort(c,c+n); m = unique(c,c+n) - c;
for(int i = 0;i < n; ++i) a[i] = lower_bound(c,c+m,a[i])-c+1;
for(int i = 0;i < n; ++i){
pre[i] = query(a[i]-1);
if(++pre[i].len == 1) pre[i].num = 1;
add(a[i],pre[i]);
}
for(int i = 0; i < n; ++i) a[i] = m-a[i]+1; //反转序号,方便树状数组更新和查询
memset(tr,0,sizeof(tr));
node ans = {0,0}; //整个数组的最长上升子序列的长度及数量
for(int i = n-1; ~i; --i){
cur[i] = query(a[i]-1);
if(++cur[i].len == 1) cur[i].num = 1;
add(a[i],cur[i]);
if(ans.len == cur[i].len) ans.num = (ans.num + cur[i].num)%mod;
else if(ans.len < cur[i].len) ans = cur[i];
}
LL inv = qpow(ans.num,mod-2);
for(int i = 0;i < n; ++i){
if(pre[i].len+cur[i].len==ans.len+1) {
printf("%lld ",pre[i].num*cur[i].num%mod*inv%mod);
}
else printf("0 ");
}
return 0;
}