[APIO2016]划艇
题目描述
在首尔城中,汉江横贯东西。在汉江的北岸,从西向东星星点点地分布着 个划艇学校,编号依次为 到 。每个学校都拥有若干艘划艇。同一所学校的所有划艇颜色相同,不同的学校的划艇颜色互不相同。颜色相同的划艇被认为是一样的。每个学校可以选择派出一些划艇参加节日的庆典,也可以选择不派出任何划艇参加。如果编号为 的学校选择派出划艇参加庆典,那么,派出的划艇数量可以在 至 之间任意选择()。
值得注意的是,编号为 的学校如果选择派出划艇参加庆典,那么它派出的划艇数量必须大于任意一所编号小于它的学校派出的划艇数量。
输入所有学校的 的值,求出参加庆典的划艇有多少种可能的情况,必须有至少一艘划艇参加庆典。两种情况不同当且仅当有参加庆典的某种颜色的划艇数量不同。
输入格式
第一行包括一个整数 ,表示学校的数量。
接下来 行,每行包括两个正整数,用来描述一所学校。其中第 行包括的两个正整数分别表示 。
输出格式
输出一行,一个整数,表示所有可能的派出划艇的方案数除以 得到的余数。
样例
样例输入
2
1 2
2 3
样例输出
7
样例解释
在只有一所学校派出划艇的情况下有4种方案,两所学校都派出划艇的情况下有3种方案,所以答案为7。
神仙dp orz
子任务1:
比较简单的一个dp,而且不需要任何优化,设f[i]为前i个学校,第i个学校必需派出划艇的方案数,则:
1 for (int i = 1; i <= n; i++) 2 { 3 for (int j = 0; j < i; j++) 4 if (a[i] > a[j]) 5 f[i] = (f[i] + f[j]) % mod; 6 ans = (ans + f[i]) % mod; 7 }
子任务2:
首先可以想到一个非常暴力的dp:f[i][j]表示前i所学校,第i所学校派出j艘划艇,但是这样无论是时间还是空间都无法承受,可以考虑用权值线段树优化。
dp[i][j]=1+∑k∑tdp[k][t](1<=k<=i-1,1<=t<=j-1) 即dp[i][j]=sum[j-1]
友情推荐mikufun大神、
标算
后面那俩子任务不知道有什么意义…
这题貌似有两种考虑方法:
第一种是用了组合数去分析(蒟弱表示并没有看懂…)推荐两个博客:某大佬 DeepinC
这里主要说一下第二种考虑方法:
这题区间这么大,但给的数并不多,考虑离散化(逢离散化必挂系列……),注意要改成开区间,拿样例举例,[1,2] [2,3]则改成[1,3) [2,4),离散化后则有1,2,3,4,即[1,2) [2,3) [3,4)三个区间,
设f[i][j][k]表示前i所学校,第i所学校排除的潜艇数在第j段区间,且此时第j段区间有k所学校。
首先明确一个小问题:
在区间[xj,xj+1)中取k个数使之单调递增,有几种取法?显然C(xj+1-xj,k),取k-1个呢?C(xj+1-xj,k-1)啊,
设前者为a,后者为b,则$a=b* \frac{C_{len}^{k}}{C_{len}^{k-1}}$
然后就可以开始dp了:
设离散化后的区间数为cnt
$f[i][j][1]=f[i-1][j][1] + \sum \limits_{x=0}^{j-1} \sum \limits_{y=0}^{i-1}f[i-1][x][y]$
$f[i][j][k]=f[i-1][j][k] + f[i-1][j][k-1]*\frac{C_{len}^{k}}{C_{len}^{k-1}}$
1 for(int i=1;i<=n;i++) 2 { 3 for(int j=a[i];j<b[i];j++) 4 {
6 for(int k=i;k>=2;k--)//注意循环顺序 7 f[j][k]=(f[j][k]+ f[j][k-1]* (t[j+1]-t[j]-k+1)%mod * invv[k] %mod )%mod; 8 f[j][1]=(f[j][1]+sum[j-1]*(t[j+1]-t[j])%mod)%mod;//一定不能放到上边 9 } 10 }
上面的式子可以用前缀和优化,同时f的第一维可以(必须)滚掉,否则求前缀会很麻烦,因为j=a[i];j<b[i],有些f[i][j][]是0,但是这是应该加f[i-1][j][],而把第一位滚掉之后就不需要考虑这个了。
其实这样在LOJ上已经可以AC了,但是在某OJ上会T,所以还要加一个小优化,其实k不需要从i循环到2,只需要再记录一个数组lim[j],每次循环j时lim[j]++,记录第j段区间现在最多有多少学校,在某OJ上时间大概快了一万多ms……
#include<algorithm> #include<iostream> #include<cstdio> #define LL long long #define int LL #define mod 1000000007 using namespace std; int n,a[510],b[510],maxn; int t[1010],cnt; LL tf[501]; bool pd; int f[1010][1010]; LL sum[10010]; int invv[1010]; LL lim[1010]; #define min(a,b) ((a)<(b)?(a):(b)) LL inv(LL a,int b) { LL ans=1; while(b) { if(b&1)ans=(ans*a)%mod; a=(a*a)%mod; b=(b>>1); } return ans; } void solve(); inline int read(); signed main() { // freopen("in.txt","r",stdin); // freopen("1.in","r",stdin); // freopen("0.out","w",stdout); n=read(); for(int i=1;i<=1000;i++)invv[i]=inv(i,mod-2); for(int i=1;i<=n;i++) { a[i]=read(),b[i]=read(); if(a[i]!=b[i])pd=1; t[++cnt]=a[i]; t[++cnt]=b[i]+1; } if(!pd){solve();return 0;} sort(t+1,t+cnt+1); cnt=unique(t+1,t+cnt+1)-t-1; for(int i=1;i<=n;i++) { a[i]=lower_bound(t+1,t+cnt+1,a[i])-t; b[i]=lower_bound(t+1,t+cnt+1,b[i]+1)-t; } f[0][0]=1; for(int j=0;j<cnt;j++)sum[j]=1; for(int i=1;i<=n;i++) { for(int j=a[i];j<b[i];j++) { lim[j]++; for(int k=lim[j];k>=2;k--) f[j][k]=(f[j][k]+ f[j][k-1]* (t[j+1]-t[j]-k+1)%mod * invv[k] %mod )%mod; f[j][1]=(f[j][1]+sum[j-1]*(t[j+1]-t[j])%mod)%mod; } for(int j=1;j<cnt;j++) { sum[j]=sum[j-1]; for(int k=1;k<=lim[j];k++) sum[j]=(sum[j]+f[j][k])%mod; } } printf("%lld\n",((sum[cnt-1]-1)%mod+mod)%mod); } void solve() { LL ans=0;tf[0]=1; for(int i=1;i<=n;i++) { for(int j=0;j<i;j++) if(a[i]>a[j])tf[i]=(tf[i]+tf[j])%mod; ans=(ans+tf[i])%mod; } printf("%lld\n",ans); } inline int read() { int s=0;char a=getchar(); while(a<'0'||a>'9')a=getchar(); while(a>='0'&&a<='9'){s=s*10+a-'0';a=getchar();} return s; }