可以发现的是,如果参加了节日\(i\)之后可以参加节日\(j\),当且仅当\(i\)节日的结束时间\(l[i]\)小于等于\(j\)节日的开始时间\(t[j]\)。那么我们对于每个\(i\),可以从\(i\)出发向每个合法的\(j\)连一条边,表示参加完\(i\)之后可以接着参加\(j\)。
题目要求最多参加几场节日,那么我们设边权为1,在我们建出的\(DAG\)上算出最长路的长度就是答案。那么我们可以建一个超级源,用拓扑排序进行\(dp\)。
建图的时间复杂度最坏为\(O(\frac{N(N-1)}{2})\),图的规模也是\(\frac{N(N-1)}{2}\),所以拓扑排序的复杂度和建图一致。
不过我们可以整一个玄学优化:我们可以不用对于每个\(i\)都枚举完\(1\)$n$所有节日。我们先把节日按照开始时间$t$从小到大进行排序,然后对于第$i$个节日,我们二分查找第一个开始时间大于等于$i$的结束时间的节日$j$,那么我们把$i$连向$j$\(n\)即可。这样一般就不用枚举完1~n所有数。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<queue>
#define maxn 10010
using namespace std;
inline int read(){
register int x(0),f(1); register char c(getchar());
while(c<'0'||'9'<c){ if(c=='-') f=-1; c=getchar(); }
while('0'<=c&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar();
return x*f;
}
struct node{ int t,l,id; }a[maxn];
vector<int> to[maxn];
int n,tmp[maxn];
int ind[maxn],dp[maxn],ans;
inline bool cmp(const node &x,const node &y){ return x.t<y.t; }
inline int binary(const int &val){
if(a[n].t<val) return -1;
int l=1,r=n,mid;
while(l<r){
mid=l+r>>1;
if(a[mid].t<val) l=mid+1;
else r=mid;
}
return l;
}
queue<int> q;
inline void topo(){
q.push(0),dp[0]=0;
while(q.size()){
int u=q.front(); q.pop();
for(register int i=0;i<to[u].size();i++){
int v=to[u][i];
dp[v]=max(dp[v],dp[u]+1),ind[v]--;
if(!ind[v]) q.push(v);
}
}
}
int main(){
n=read();
for(register int i=1;i<=n;i++) a[i].t=read(),a[i].l=read(),a[i].id=i;
sort(a+1,a+1+n,cmp);
for(register int i=1;i<=n;i++){
int end=a[i].l+a[i].t;
int j=binary(end);
if(j==-1) continue;
for(register int k=j;k<=n;k++){
to[a[i].id].push_back(a[k].id),ind[a[k].id]++;
}
}
for(register int i=1;i<=n;i++) to[0].push_back(i),ind[i]++;
topo();
for(register int i=1;i<=n;i++) ans=max(ans,dp[i]);
printf("%d\n",ans);
return 0;
}