[luogu5464]缩小社交圈
不难证明合法当且仅当满足一下两个条件:
1.每一个位置最多被覆盖两次(无环)
2.将选择的区间按左端点从小到大排序,对于每一个左端点,其之前的区间的最大右端点不小于其(连通)
(关于第一个的充分性证明可以考虑一个极小环,将环上区间排序即矛盾)
就将所有区间按照左端点从小到大排序,然后依次选择
更具体的,用$f_{i,j}$表示仅考虑左端点小于等于$i$的区间,最大右端点为$j$的选择方案数
转移考虑第$i$个位置上的区间,根据第二个条件可以得到$j\ge i$,再根据第一个条件可以得到最多选择一个区间,接下来,可以直接转移到$f_{\min(j,r_{选择的区间},\max(j,r_{选择的区间})}$
解释一下:由于选择了这个区间,那么剩下的区间左端点必然不能在$[i,\min(j,选择的区间)]$,否则该位置即被覆盖了3次,也就是”已经考虑完了“
(这里并没有改变$f_{i,j}$的定义,只是让原本需要从这里一点点转移上去的部分直接转移)
特别的,第一个区间允许$j<i$,也允许选择多个区间,需要特判
时间复杂度即为$o(L^{2})$($L$为$l_{i},r_{i}$的范围),可以通过
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define N 4005 4 #define mod 1000000007 5 vector<int>v[N]; 6 int n,x,y,ans,f[N][N]; 7 void add(int &x,int y){ 8 x=(x+y)%mod; 9 } 10 int main(){ 11 scanf("%d",&n); 12 int m=0; 13 for(int i=1;i<=n;i++){ 14 scanf("%d%d",&x,&y); 15 m=max(m,y); 16 v[x].push_back(y); 17 } 18 for(int i=1;i<=m;i++){ 19 for(int j=0;j<=m;j++)add(f[i][j],f[i-1][j]); 20 sort(v[i].begin(),v[i].end()); 21 for(int j=0;j<v[i].size();j++){ 22 add(f[i][v[i][j]],1); 23 for(int k=j+1;k<v[i].size();k++)add(f[v[i][j]][v[i][k]],1); 24 for(int k=i;k<=m;k++)add(f[min(k,v[i][j])][max(k,v[i][j])],f[i-1][k]); 25 } 26 } 27 for(int i=0;i<=m;i++)add(ans,f[m][i]); 28 printf("%d",ans); 29 }