时限:1000ms 空间限制:524288K
题目链接https://nanti.jisuanke.com/t/39611
外教变身萌翻小学员,VIPKID “AR 变脸” 打造趣味互动课堂,这是在线少儿英语品牌 VIPKID 全新推出的辅助教学功能——AR 变脸,外教在上课过程中可以随意选取合适的表情贴纸。人脸识别和表情识别等技术的应用帮助“AR 变脸”这一教辅功能更好地服务外教,更好地激励学生积极地参与课堂内容,提升在线课堂的趣味性。
现在,外教 Michale 就变身了一只萌萌的大熊猫,给你出来了一道关于数列的题目,请看题:
给定长度为 n 的数列 a,显然这个数列有很多最长上升子序列,我们等概率地取出一个最长上升子序列,求每个数被选中的概率,对 998244353取模。
输入格式
第一行输入一个整数 n 表示数列 a 的长度。
接下来一行输入 n 个整数 ai。
输出格式
输出 n 个整数,表示每个数字被选中的概率。
数据规模
ai≤10^9,n≤5×10^5
输出时每行末尾的多余空格,不影响答案正确性
样例输入
4 1 2 2 4
样例输出
1 499122177 499122177 1
这题是真的教我做人了,本来一遍就差不多可以过了,然而我全程开int,然后到处爆。。。WA了五六发才过了QAQ
首先求概率的话我们只要求出每个元素出现在多少个最长上升子序列上,然后除以总的最长上升子序列的数量求逆元就ok了。最长上升子序列挺好求的,它的数量也不难求,但如何求每个元素出现在多少个最长上升子序列上有点麻烦,我们可以想王简单的想:然后判断该元素出现在了上升子序列上呢?这就很简单了,我们从前往后扫一遍得到以i为结尾的最长上升子序列的长度f[i],然后从后往前扫一遍得到以i为结尾的最长下降子序列长度g[i];那么g[i]+f[i]-1如果等于最长上升子序列的长度的话,我们就可以肯定i在最长上升子序列上了。
至于出现在多少个最长上升子序列上面,我们给每个i记录位置在它前面且值小于它的最长上升子序列长度为f[i]-1的数量,这样我们就可以得到该位置要达到它自身的f[i]有多少种方法记为ff[i],同理,我们从后再扫一遍得到每个位置从后面开始到给位置达到g[i]的方法数量记为gg[i],那么当对于该位置g[i]+f[i]-1=len成立的时候ff[i]*gg[i]就是它所处的最长上升子序列的数量。这就相当于走路一样,把i当做中转站,起始站到中转站有ff[i]种方法,中转站到终点有gg[i]种方法,所以总的方法就是ff[i]*gg[i]种了。
于是所有的问的都解决了,我们可以直接用线段树维护,将a[i]离散化之后将每个a[i]放在线段树的第a[i]个叶子结点,查询的时候在0到a[i]-1中查找长度的最大值记录在f[i]中,接着查询最大值的数量记录在g[i]中,也就是两颗线段树就可以了,当然更新的时候注意向上传值。要判断左儿子和右儿子的长度是否相等进行分类讨论,不能直接暴力加上左右儿子的数量。
以下是AC代码:
#include <bits/stdc++.h>
using namespace std;
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
#define ls rt<<1
#define rs rt<<1|1
#define ll long long
const int mac=5e5+10;
const ll mod=998244353;
void in(int &x)
{
int f=0;
char ch=getchar();
while (ch>'9' || ch<'0') ch=getchar();
while (ch<='9' && ch>='0') f=(f<<3)+(f<<1)+ch-'0',ch=getchar();
x=f;
}
void out(ll x)
{
if (x>=10) out(x/10);
putchar(x%10+'0');
}
ll treelen[mac<<2];
ll treenum[mac<<2];
void build(int l,int r,int rt)
{
treelen[rt]=treenum[rt]=0;
if (l==r) return;
int mid=(l+r)>>1;
build(lson);build(rson);
}
void pushup(int rt)
{
if (treelen[ls]>treelen[rs]) treenum[rt]=treenum[ls];
else if (treelen[ls]<treelen[rs]) treenum[rt]=treenum[rs];
else treenum[rt]=(treenum[ls]+treenum[rs])%mod;
treelen[rt]=max(treelen[ls],treelen[rs]);
}
void update(int l,int r,int rt,int pos,ll len,ll num)
{
if (l==r) {
if (treelen[rt]==len) treenum[rt]=(treenum[rt]+num)%mod;//注意更新的分类
else treenum[rt]=num;
treelen[rt]=len;
return;
}
int mid=(l+r)>>1;
if (mid>=pos) update(lson,pos,len,num);
else update(rson,pos,len,num);
pushup(rt);
}
ll query1(int l,int r,int rt,int L,int R)
{
ll ans=0;
if (l>=L && r<=R){
return treelen[rt];
}
int mid=(l+r)>>1;
if (mid>=L) ans=max(ans,query1(lson,L,R));
if (mid<R) ans=max(ans,query1(rson,L,R));
return ans;
}
ll query2(int l,int r,int rt,int L,int R,ll val)
{
ll ans=0;
if (l>=L && r<=R){
if (treelen[rt]==val) return treenum[rt];
return 0;
}
int mid=(l+r)>>1;
if (mid>=L) ans=(ans+query2(lson,L,R,val))%mod;
if (mid<R) ans=(ans+query2(rson,L,R,val))%mod;
return ans;
}
ll f1[mac],g1[mac];
ll f2[mac],g2[mac];
int b[mac],a[mac];
ll qpow(ll a,ll b)
{
a%=mod;
ll ans=1;
while (b){
if (b&1) ans=ans*a%mod;
b>>=1;
a=a*a%mod;
}
return ans;
}
int main()
{
//freopen("in.txt","r",stdin);
int n;
in(n);
for (int i=1; i<=n; i++) in(a[i]),b[i]=a[i];
sort(b+1,b+1+n);
int p=unique(b+1,b+1+n)-b;
for (int i=1; i<=n; i++) a[i]=lower_bound(b+1,b+1+p,a[i])-b;
for (int i=1; i<=n; i++){
f1[i]=query1(0,n,1,0,a[i]-1)+1;//a[i]-1可能为0,所以树的边界从0开始
f2[i]=query2(0,n,1,0,a[i]-1,f1[i]-1);
f2[i]=max(f2[i],1ll);
update(0,n,1,a[i],f1[i],f2[i]);
}
build(0,n+1,1);
for (int i=n; i>=1; i--){
g1[i]=query1(0,n+1,1,a[i]+1,n+1)+1;//a[i]+1可能大于n,所以树的边界以n+1结束
g2[i]=query2(0,n+1,1,a[i]+1,n+1,g1[i]-1);
g2[i]=max(g2[i],1ll);
update(0,n+1,1,a[i],g1[i],g2[i]);
}
ll len=treelen[1];
ll num=qpow(treenum[1],mod-2);
for (int i=1; i<=n; i++){
if (f1[i]+g1[i]-1==len){
ll ans=(f2[i]*g2[i]%mod)*num%mod;
out(ans);putchar(' ');
}
else putchar('0'),putchar(' ');
}
return 0;
}