计蒜之道2019 复赛 A.外教 Michale 变身大熊猫 线段树辅助建分层图dp
题意:给出一个序列,随机取出其中一条最长上升子序列,问你取到每个数的概率是多少。
说起概率,我们可以尝试去求最长上升子序列的个数,显然每个点被取到的概率是 含有这个点的最长上升子序列个数/总共最长上升子序列的个数。
首先我们假设dp[i]为到这个点的最长上升子序列长度。
这个事情我们需要建立一个分层图,第x层含有dp[i]=x的点。
举个例子,对于序列A 1 7 6 8 2 4 3
它的dp数组值是 1 2 2 3 2 3 3
我们就可以建立一个这样的图,来求从前往后取最长上升子序列的方案数,比如8的方案就可以从7、6继承。
点1的方案数显然是1,6和7则从1继承来了这个方案数,所以也是1,8则从7、6继承,于是方案数是2。
当然我们不能直接建边,那样复杂度就不能接受了,我们得利用线段树来求一个点前驱方案数之和。
好了,到了这里,我们求出了从前往后到这个点的方案数的和,但是这显然不是最终答案,比如例子中的2,顺着的方案数显然是1,但是却有1-2-4和1-2-3两种方案!
怎么办呢?很简单,我们只要反着从后往前再求一次这个方案数,将正反方案数相乘,就是包含这个点的方案数总和,例如点2就是1*2=2,这样就符合答案了。
代码:
#include<bits/stdc++.h> using namespace std; const long long mod=998244353; int i,i0,n,m,a[500005],tree[4*500005],dp[500005],d[500005],dd[500005]; long long ans[500005]; vector<int>v,q[500005]; void extgcd(long long a,long long b,long long& d,long long& x,long long& y) { if(!b){d=a;x=1;y=0;} else{extgcd(b,a%b,d,y,x);y-=x*(a/b);} } long long inv(long long a,long long n) { long long d,x,y; extgcd(a,n,d,x,y); return d==1?(x+n)%n:-1; } void c_tree(int l,int r,int p,int a,int v) { if(l==r)tree[p]=v; else { int mid=(l+r)/2; if(a<=mid)c_tree(l,mid,p*2,a,v); else c_tree(mid+1,r,p*2+1,a,v); tree[p]=max(tree[p*2],tree[p*2+1]); } } int q_tree(int l,int r,int p,int a,int b) { if(a==l&&b==r)return tree[p]; int mid=(l+r)/2; if(b<=mid) return q_tree(l,mid,p*2,a,b); else if(a>=mid+1)return q_tree(mid+1,r,p*2+1,a,b); else return max(q_tree(l,mid,p*2,a,mid),q_tree(mid+1,r,p*2+1,mid+1,b)); } void c0_tree(int l,int r,int p,int a,int v) { if(l==r) { tree[p]+=v,tree[p]%=mod; if(tree[p]<0)tree[p]+=mod; } else { int mid=(l+r)/2; if(a<=mid)c0_tree(l,mid,p*2,a,v); else c0_tree(mid+1,r,p*2+1,a,v); tree[p]=(tree[p*2]+tree[p*2+1])%mod; } } int q0_tree(int l,int r,int p,int a,int b) { if(a==l&&b==r)return tree[p]; int mid=(l+r)/2; if(b<=mid) return q0_tree(l,mid,p*2,a,b); else if(a>=mid+1)return q0_tree(mid+1,r,p*2+1,a,b); else return (q0_tree(l,mid,p*2,a,mid)+q0_tree(mid+1,r,p*2+1,mid+1,b))%mod; } int main() { scanf("%d",&n); for(i=1;i<=n;i++)scanf("%d",&a[i]),v.push_back(a[i]); sort(v.begin(),v.end()),v.erase(unique(v.begin(),v.end()),v.end()); for(i=1;i<=n;i++)a[i]=lower_bound(v.begin(),v.end(),a[i])-v.begin()+1; int mx=0; for(i=1;i<=n;i++) { dp[i]=q_tree(0,n,1,0,a[i]-1)+1; q[dp[i]].push_back(i),mx=max(mx,dp[i]); c_tree(0,n,1,a[i],dp[i]); } memset(tree,0,sizeof(tree)); for(int i0:q[mx]) { dd[i0]=1; } memset(tree,0,sizeof(tree)); for(i=mx;i>=2;i--) { for(int x=q[i-1].size()-1,y=q[i].size()-1;x>=0;x--) { while(y>=0&&q[i][y]>q[i-1][x])c0_tree(0,n+1,1,a[q[i][y]],dd[q[i][y]]),y--; dd[q[i-1][x]]=q0_tree(0,n+1,1,a[q[i-1][x]]+1,n+1); } for(int x=q[i-1].size()-1,y=q[i].size()-1;x>=0;x--) { while(y>=0&&q[i][y]>q[i-1][x])c0_tree(0,n+1,1,a[q[i][y]],-dd[q[i][y]]),y--; } } for(int i0:q[1]) { d[i0]=1; } for(i=2;i<=mx;i++) { for(int x=0,y=0;x<q[i].size();x++) { while(y<q[i-1].size()&&q[i-1][y]<q[i][x])c0_tree(0,n+1,1,a[q[i-1][y]],d[q[i-1][y]]),y++; d[q[i][x]]=q0_tree(0,n+1,1,0,a[q[i][x]]-1); } for(int x=0,y=0;x<q[i].size();x++) { while(y<q[i-1].size()&&q[i-1][y]<q[i][x])c0_tree(0,n+1,1,a[q[i-1][y]],-d[q[i-1][y]]),y++; } } for(i=1;i<=mx;i++) { long long sum=0; for(int x:q[i])sum+=(long long)d[x]*dd[x],sum%=mod; for(int x:q[i])ans[x]=(long long)d[x]*dd[x]%mod*inv(sum,mod)%mod; } for(i=1;i<=n;i++)printf("%lld%c",ans[i],i==n?'\n':' '); return 0; }