bzoj4584: [Apio2016]赛艇
真的是无尽接近单刷这题啊。。。。或许这就是差距吧,把大概的思路方向想出来具体就不会了
心态爆炸
首先可以一眼的是这个权值肯定要离散化,然后把每个选择的区间断成一段段
假如枚举到当前学校当前区间,前一个学校比这个区间小的傻子都会转移,主要是处理同区间的转移
我们可以先列一个方程的雏形:f[i][j]表示第i个学校的第j个区间 f[i][j]= ∑∑f[i'][j'](i'<i,j'<j) + cost
可以发现cost是和区间长L和当前联续了几个区间k有关的:
假如现在只连续了两个区间,那么手推一下就可以发现第二个区间的第1个数没有匹配的,第二个区间的第2个数有1个匹配的……
一般的,有s1,all=1,si,j=∑si-1,j'(j'<j)这是一个不断算前缀和的过程,cost=sk,L*D 其中D是一个常数
假如打表或者是手推一下,si,j=si-1,j-1+si,j-1 这个长得有点像组合数的递推式,然后其实它就是一个竖下来的杨辉三角形
其实这个东西的现实意义就是n个学校选1~L的数,选出单调上升的方案数,那么其实相当于在L个数选n个排成一列
对于连续了k个区间转移到k+1,那么其实是D*si,L=>D*si+1,L,相当于乘一个(L-k+1)/k,就可以了
那么f[i][j][k]=∑∑f[i'][j'][k-1]*(L-k+1)/k
特殊处理f[i][j][1]=∑∑∑f[i'][j'][k],这个要拿个数组搞
这样加加优化就可以A了,然而其实我们可以不管这个k
f[i][j]=∑∑f[i'][j']*cost(p)
i倒着枚举,其中p表示枚举到当前有多少个学校涵盖当前区间,cost(p)=∑C(L,p-t)*C(p-1,p-t-1),意思是先选出p-t个出来,第p个学校必须选,其他可选可不选,再把它分配下去
然后这个cost也等于C(L+p-1,p)假如选到了加进去的p-1个,相当于对应的学校不选,选出p个
我们再让j一维取前缀和,就可以省一个for,那么就完成了
把i和j的枚举顺序反过来,先得到区间长外面预处理组合数,用类似背包的做法数组还可以省掉一维j
#include<cstdio> #include<iostream> #include<cstring> #include<cstdlib> #include<algorithm> #include<cmath> using namespace std; typedef long long LL; const LL mod=1e9+7; int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();} return x*f; } LL inv[1100]; void init(){inv[1]=1;for(int i=2;i<=1000;i++)inv[i]=inv[mod%i]*(mod-mod/i)%mod;} LL updC(LL c,LL n,LL m)//C(n,m)=>C(n+1,m+1) { return (c+c*(n-m)%mod*inv[m+1]%mod)%mod; } int le[510],re[510],lslen,ls[1100]; LL f[1100],c[1100]; int main() { freopen("a.in","r",stdin); freopen("a.out","w",stdout); int n;init(); n=read();lslen=0; for(int i=1;i<=n;i++) { le[i]=read(),re[i]=read(),re[i]++; ls[++lslen]=le[i],ls[++lslen]=re[i]; } sort(ls+1,ls+lslen+1); lslen=unique(ls+1,ls+lslen+1)-ls-1; for(int i=1;i<=n;i++) le[i]=lower_bound(ls+1,ls+lslen+1,le[i])-ls, re[i]=lower_bound(ls+1,ls+lslen+1,re[i])-ls; memset(f,0,sizeof(f)); for(int j=1;j<=lslen;j++)f[0]=1; for(int j=1;j<=lslen;j++) { LL L=ls[j]-ls[j-1]; c[1]=L;for(int i=1;i<=n;i++)c[i+1]=updC(c[i],L+i-1,i); for(int i=n;i>=1;i--) { if(le[i]+1<=j&&j<=re[i]) { int p=1; for(int k=i-1;k>=0;k--) { f[i]=(f[i]+f[k]*c[p])%mod; if(le[k]<j&&j<=re[k])p++; } } } } LL ans=0; for(int i=1;i<=n;i++)ans=(ans+f[i])%mod; printf("%lld\n",ans); return 0; }