题解 P5464 【缩小社交圈】

Fading

2019-07-14 21:22:18

Solution

疯掉了,这道题看了大概$20$分种就有思路,但是调起来就是$1$个半小时。 原来是自己考虑太少了,对拍了$30$分种才出错(鬼知道为什么) 深深感到我的信息天赋为$\huge 0$。 ### 约定 $x_i,y_i$分别表示第$i$个区间的左右端点。 ### 算法$1$: 我会状压! 期望得分:$20$分。 ### 算法$2$: 我们先对所有区间按照右端点进行排序。 然后发现连边就成了这样: ![](https://cdn.luogu.com.cn/upload/pic/63750.png) 如果我们按序编号,就变成了这样: ![](https://cdn.luogu.com.cn/upload/pic/63751.png) 发现了什么? #### 如果$l,r(l<r)$之间有边,那么$[l,r-1]$之间的点均与$r$有边。 这就是我们要排序的原因,之后我们可以直接开始$dp$了! #### 情况$1$ 发现排序以后,假设我们$dp$到了第$i$条线段,那么我们枚举$1\sim i-1$的线段$j$。 我们发现,如果$j\cap i=\varnothing(y_j<x_i)$,那么没法转移(不能为森林) 如果$j\cap i \not=j\ (x_j<x_i)$,如图: ![](https://cdn.luogu.com.cn/upload/pic/63754.png) 那么我们可以选择$j$,但是,如果选了$j$,有一些边,比如打上橙色叉叉的边,就不能选。为什么呢?这样会出现三元环! 所以我们不能单纯的用$f_i$表示前$i$个的方案数,我们可以考虑设$f_{i,j}$表示选择$i$,且上一个选择$j$的方案数。 这样,如果选择了$y_j\geq x_i$的$j$,所有满足$y_k\geq x_i$的边都不可以选。 那么我们有转移方程式: $$f_{i,j}=\sum_{k=0,y_k<x_i}^{i-1}f_{j,k}\ \ (y_j\geq x_i,x_j<x_i)$$ #### 情况$2$ 傻逼的我忘记了这种情况^_^,拍了很久才发现。 如果$j\cap i =j\ (x_j\geq x_i)$,如图 ![](https://cdn.luogu.com.cn/upload/pic/63755.png) $j$被$i$包含了。我们可以选择$j$,但是... 情况变得棘手很多。 发现选择$i,j$以及打上绿色勾勾的边是可以构成一棵树的,而选择打上橙色叉叉的则不行(环!)... 那么,仿佛我们又可以转移了! 我们选择了$j$以后,就不可以选择$y_k\geq x_j$的$j!!!$ 好了,我们又可以得到方程了。 $$f_{i,j}=\sum_{k=0,y_k<x_j}^{i-1}f_{j,k}\ \ (y_j\geq x_i,x_j>x_i)$$ 对吗? #### 错了! 这样子$f_{j,k}$不都等于$0$了吗? 其实此时假设选择$k$,那么对于所有的$f_{i,k}$,再接上一个$j$不会有问题。 所以我们的方程应该是这样: $$f_{i,j}=\sum_{k=0,y_k<x_j}^{i-1}f_{i,k}\ \ (y_j\geq x_i,x_j>x_i)$$ 那么我们分两类情况讨论就好了! #### 边界 $f_i[0]=1$ #### 最终答案 就是 $$\sum_{i=1}^n\sum_{j=0}^{i-1}f_{i,j}$$ 期望得分:$60$分。 ### 算法$3$: 看看我们的方程式: $$f_{i,j}=\sum_{k=0,y_k<x_i}^{i-1}f_{j,k}\ \ (y_j\geq x_i,x_j<x_i)$$ $$f_{i,j}=\sum_{k=0,y_k<x_j}^{i-1}f_{i,k}\ \ (y_j\geq x_i,x_j>x_i)$$ 是不是可以树状数组优化呢? 那么这道题就做完了。 时间复杂度$O(n^2log_2n)$ 期望得分:$100$分。 代码: ```cpp #include<bits/stdc++.h> #define ll long long #define ljc 1000000007 using namespace std; #ifdef Fading #define gc getchar #endif #ifndef Fading inline char gc(){ static char now[1<<16],*S,*T; if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;} return *S++; } #endif inline ll read(){ register ll x=0,f=1;char ch=gc(); while (!isdigit(ch)){if(ch=='-')f=-1;ch=gc();} while (isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=gc();} return (f==1)?x:-x; } int f[4011][4011],n,m,L[4021]; struct que{ int x,y; }x[4011]; inline bool cmp(que a,que b){ if (a.y==b.y) return a.x<b.x; return a.y<b.y; } struct tree{ int tr[4021]; inline void add(int a,int b){ a++; for (;a<=4001;a+=a&-a) tr[a]=(tr[a]+b)%ljc; } inline int query(int a){ int ans=0; a++; for (;a;a-=a&-a) ans=(ans+tr[a])%ljc; return ans; } }tr[4021]; signed main(){ n=read(); for (int i=1;i<=n;i++){ x[i].x=read(),x[i].y=read(); } sort(x+1,x+1+n,cmp); for (int i=2;i<=n;i++){ int lb=1,rb=i,ans=-1; while (lb<=rb){ int mid=lb+rb>>1; if (x[mid].y<x[i].x) lb=mid+1; else ans=mid,rb=mid-1; } L[i]=ans;//卡常用的,预处理第一个满足条件的点 } for (int i=1;i<=n;i++) f[i][0]=1,tr[i].add(0,1); for (int i=2;i<=n;i++){ for (int j=L[i];j<i;j++){ if (x[i].x<=x[j].x){ f[i][j]=tr[i].query(x[j].x-1); }else{ f[i][j]=tr[j].query(x[i].x-1); } tr[i].add(x[j].y,f[i][j]); } } int ans=0; for (int i=1;i<=n;i++){ for (int j=0;j<i;j++){ ans=(ans+f[i][j])%ljc; } } printf("%d\n",ans); return 0; } ``` ### 算法$4$: 发现我们对区间进行了排序,那么每一次加入树状数组的点都是递增的。 所以我们可以用前缀和优化时间复杂度。 开一个变量,更新前缀和的时候扫一遍即可。 时间复杂度$O(n^2)$ 期望得分:$100$分 细节见代码,非常多。 ~~也就比树状数组快了100ms~~ ```cpp #include<bits/stdc++.h> #define ll long long #define ljc 1000000007 using namespace std; #ifdef Fading #define gc getchar #endif #ifndef Fading inline char gc(){ static char now[1<<16],*S,*T; if (T==S){T=(S=now)+fread(now,1,1<<16,stdin);if (T==S) return EOF;} return *S++; } #endif inline ll read(){ register ll x=0,f=1;char ch=gc(); while (!isdigit(ch)){if(ch=='-')f=-1;ch=gc();} while (isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=gc();} return (f==1)?x:-x; } int f[4011][4011],n,m,L[4021]; struct que{ int x,y; }x[4011]; inline bool cmp(que a,que b){ if (a.y==b.y) return a.x<b.x; return a.y<b.y; } int sum[4021][4021]; signed main(){ n=read(); for (int i=1;i<=n;i++){ x[i].x=read(),x[i].y=read(); } sort(x+1,x+1+n,cmp); for (int i=2;i<=n;i++){ int lb=1,rb=i,ans=-1; while (lb<=rb){ int mid=lb+rb>>1; if (x[mid].y<x[i].x) lb=mid+1; else ans=mid,rb=mid-1; } L[i]=ans; } for (int i=1;i<=4000;i++) f[i][0]=1,sum[i][0]=1,sum[1][i]=1; for (int i=2,las=0;i<=n;i++){ for (las=1;las<=x[L[i]].x;las++){//为第一种情况做准备 sum[i][las]=sum[i][las-1]; } for (int j=L[i],k;j<i;j++){ if (x[i].x<=x[j].x){ f[i][j]=sum[i][x[j].x-1]; }else{ f[i][j]=sum[j][x[i].x-1]; } for (k=las;k<=x[j].y;k++){//更新前缀和 sum[i][k]=sum[i][k-1]; } sum[i][k-1]=(sum[i][k-1]+f[i][j])%ljc; for (;k<=x[j+1].x-1;k++){//为下一次做准备 sum[i][k]=sum[i][k-1]; } las=k; } for (;las<=4000;las++){//更新完所有前缀和 sum[i][las]=sum[i][las-1]; } } int ans=0; for (int i=1;i<=n;i++){ for (int j=0;j<i;j++){ ans=(ans+f[i][j])%ljc; } } printf("%d\n",ans); return 0; } ```