题目大意
给出一个排列,定义一个排列是合法的当且仅当标记的数满足单调递增
每次等概率选择一个未标记过的且标记后序列合法的数标记,求期望的标记个数
n<=2000
题解
时间不够咋写6题啊
考虑每个数的贡献,枚举数a[x],以(x,a[x])为原点建坐标系,则二四象限的点选了x就必无贡献,选了一三象限的点会把范围往中间缩,范围是一个矩形,并且每次只能选矩形内的点
这样的好处是不用考虑矩形外面的点,因为被分成的矩形之间是独立的,选了其他矩形中的不会影响到当前的
写成式子有f[i,j]=a/(a+b)f[i,j]+1/(a+b)(...),化一下变成f[i,j]=1/b(...),所以只用考虑矩形中间的即可
设f[i,j]表示选到[i,j]且ij已选的答案,转移枚举选择点k,如果k=x就为1,否则为f[i,k]或f[k,j]
直接写是O(n^4)的,发现x的转移长得差不多,所以把f[i,j]变成所有x的答案,转移是f[i,k]+f[k,j]+1->f[i,j]
这样是O(n^3),把转移的条件转化成两个二维偏序,树状数组解决
code
#include <bits/stdc++.h>
#define fo(a,b,c) for (a=b; a<=c; a++)
#define fd(a,b,c) for (a=b; a>=c; a--)
#define add(a,b) a=((a)+(b))%mod
#define low(x) ((x)&-(x))
#define mod 1000000007
#define Mod 1000000005
#define ll long long
//#define file
using namespace std;
int sum[2002][2002],a[2002],n,i,j,k,l;
ll f[2002][2002],w[2002];
struct tree{
int tr[2002];
void change(int t,int s) {while (t<=n+1) add(tr[t],s),t+=low(t);}
ll find(int t) {ll ans=0;while (t) add(ans,tr[t]),t-=low(t); return ans;}
} t1[2002],t2[2002];
int get(int X1,int Y1,int X2,int Y2)
{
int ans=sum[X2][Y2];
if (X1) ans-=sum[X1-1][Y2];
if (Y1) ans-=sum[X2][Y1-1];
if (X1 && Y1) ans+=sum[X1-1][Y1-1];
return ans;
}
int main()
{
#ifdef file
freopen("arc108E.in","r",stdin);
#endif
scanf("%d",&n);
fo(i,1,n) scanf("%d",&a[i]);a[n+1]=n+1;
w[1]=1;
fo(i,2,n+1) w[i]=mod-w[mod%i]*(mod/i)%mod;
fo(i,0,n+1) sum[i][a[i]]=1;
fo(i,0,n+1) fo(j,1,n+1) sum[i][j]+=sum[i][j-1];
fo(i,1,n+1) fo(j,0,n+1) sum[i][j]+=sum[i-1][j];
fo(l,3,n+2)
{
fo(i,0,(n+1)-l+1)
{
j=i+l-1;
if (a[i]<a[j])
{
f[i][j]=(1ll*(get(i,a[i],j,a[j])-2)+t1[i].find(a[j])+t2[j].find(n-a[i]+1))%mod;
f[i][j]=f[i][j]*w[get(i,a[i],j,a[j])-2]%mod;
t1[i].change(a[j],f[i][j]);
t2[j].change(n-a[i]+1,f[i][j]);
}
}
}
printf("%lld\n",(f[0][n+1]+mod)%mod);
fclose(stdin);
fclose(stdout);
return 0;
}