arc108E - Random IS

题目大意

给出一个排列,定义一个排列是合法的当且仅当标记的数满足单调递增

每次等概率选择一个未标记过的且标记后序列合法的数标记,求期望的标记个数

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;
}
posted @ 2020-11-23 07:24  gmh77  阅读(283)  评论(0编辑  收藏  举报