题意描述
大小为的集合
是满足条件的,当且仅当把其中元素按
降序排序后,对于所有
,满足
或
。给定集合
,其中
,
求
满足条件的子集个数模
的结果。
解题思路
1.基本算法
按降序排列后,
必须在前两个数之间,于是可以想到DP算法:
设表示以
为第一项,
为第二项的满足条件的子集个数,
所以其所有满足条件的情况有两种:
1.只有这两个数
2.后面再找一个在两个数之间的数这作为集合第三项
于是可以想到状态转移方程其中
或
时间复杂度空间复杂度
2.前缀和优化与离散化实现
观察状态转移方程,我们发现涉及到的的值都会在一段连续的区间中,于是想到用前缀和优化
定义其中
状态转移方程就能变为
不过由于范围较大,不能直接用此方法实现,但可以发现,我们只关心
的大小关系,而不需要其数值,所以我们可以将
离散化处理,把用
表示
在
升序排序后的位置,问题就能解决。
时间复杂度,空间复杂度
。
3.一些不太愉快的事情
在OJ中,此题时间卡的很紧,如果直接写可能会被卡掉一两个点,别问我怎么知道的,比起“火车头”这种歪门邪道的方法,对于这道题还有一个卡常的方法:程序中会用到许多加或减后取模运算,而参加运算的数都是取模之后的结果,于是以减代除是一种很好的优化方法。
示例代码
#include<bits/stdc++.h>
using namespace std;
const int N=6005,P=1e9+7;
int n;
struct node
{
int x,y;
bool operator<(const node &w)const
{
return y>w.y;
}
}a[N];
int p[N],q[N];
int sum[N][N];
int f[N][N];
int ans;
bool cmp(int x,int y)
{
return a[x].x<a[y].x;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y);
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)q[i]=i;
sort(q+1,q+n+1,cmp);
for(int i=1;i<=n;i++)p[q[i]]=i;//离散化
for(int i=1;i<=n;i++)f[i][i]=1;
for(int i=n;i>=1;i--)
{
for(int j=i+1;j<=n;j++)
if(a[i].x<a[j].x)
{
f[i][j]=sum[j][p[j]]-sum[j][p[i]-1];
if(f[i][j]<0)f[i][j]+=P;
}
else
{
f[i][j]=sum[j][p[i]]-sum[j][p[j]-1];
if(f[i][j]<0)f[i][j]+=P;
}
//f[i][j]=∑f[j][k](x[i]<x[k]<x[j]或x[j]<x[k]<x[j])
for(int j=i;j<=n;j++)sum[i][p[j]]=f[i][j];
for(int j=1;j<=n;j++)
{
sum[i][j]=sum[i][j]+sum[i][j-1];
if(sum[i][j]>=P)sum[i][j]-=P;
}
//sum[i][j]=∑f[i][k](x[k]<j)
}
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++)
{
ans+=f[i][j];
if(ans>=P)ans-=P;
}
printf("%d",ans);
return 0;
}