ATG61C First Come First Serve 学习笔记

ATG61C First Come First Serve 学习笔记

Luogu Link

题意简述

\(n\) 个形如 \((a_i,b_i)\) 的数对。现在令 \(c_i\) 等于 \(a_i,b_i\) 中的一个,并将 \([1,n]\) 中的数字按 \(c_i\) 由小到大重排为 \(P\)。问可能形成多少种 \(P\)

感性理解题意:有 \(n\) 个人来过,第 \(i\) 个人在 \(a_i\) 时刻来,在 \(b_i\) 时刻走,每个人可以在来时或走时登记,问可能的登记顺序有多少种。

\(n\le 5\times 10^5\)\(a_i,b_i\) 取遍 \([1,2n]\),保证 \(a_i<b_i\)\(a_i<a_{i+1}\)\(b_i<b_{i+1}\)

做法解析

答案显然是 \(2^n\) 去一堆重。何以去重?

一种会出现重复的情况是,对于两个完全独立的区间而言,无论怎么选择,只考虑它们的情况下最后都是一种方案。

进一步手玩,你发现对于 \([1,3]\)\([2,4]\) 这两个区间而言,选择 \(\{1,2\}\)\(\{3,4\}\) 也是等价的。

直接算哪些东西需要去重哪些东西不需要似乎比较麻烦。这时候,一个思考方向是考虑钦定某种选择顺序使其能规避重复:“考虑选择 \(b_i\),当且仅当 \((a_i,b_i)\) 之间有元素”。

接下来我们设计一个DP去做这件事。设 \(dp_i\) 为考虑前 \([1,i]\) 个数对的选择方案。那么首先有 \(dp_i=2dp_{i-1}\),然后我们要扣去形如“\((a_i,b_i)\) 间没有元素时还选了 \(b_i\)”的情形。这个东西怎么扣减呢?你去对每一个 \((a_i,b_i)\) 找到最大的 \(b_j\) 满足 \(b_j<a_i\) 和最大的 \(a_k\) 满足 \(a_k<b_i\),然后你在转移到 \(dp_k\) 时有 \(dp_k\gets dp_k-dp_j\)。双指针搞定。

为什么这是对的?这相当于是说:\((j,k]\) 这一批数对的选择有两种最终等价的方案(可以推导,一定有 \(a_j<a_{j+1}<\dots<a_k<b_j<b_{j+1}\dots<b_k\),每种等价方案会乘上之前 \([1,j]\) 的选择(即 \(dp_j\))。

代码实现

#include <bits/stdc++.h>
using namespace std;
using namespace obasic;
using namespace omodint;
using mint=m998;
const int MaxN=5e5+5;
int N,L[MaxN],R[MaxN];
mint dp[MaxN];vector<int> vec[MaxN];
int main(){
    readi(N);
    for(int i=1;i<=N;i++)readis(L[i],R[i]);
    for(int i=1,p=0,q=0;i<=N;i++){
        for(;p<N&&R[p+1]<L[i];p++);
        for(;q<N&&L[q+1]<R[i];q++);
        vec[q].push_back(p);
    }
    dp[0]=1;
    for(int i=1;i<=N;i++){
        dp[i]=2*dp[i-1];
        for(int j : vec[i])dp[i]-=dp[j];
    }
    writi(miti(dp[N]));
    return 0;
}

反思总结

其实这题感性理解地,你看上去就长得颇有双指针之风姿。

一种去重思路是钦定某种顺序。

posted @ 2025-07-11 14:49  矞龙OrinLoong  阅读(4)  评论(0)    收藏  举报