P1668 题解
两种做法。
一、最短路
题目要求区间数量最小。如果能建出图来,就可以转换为最短路问题。
具体地,我们从 \(l-1\to r\) 连一条长度为 \(1\) 的边,意味着要多经过 \((l-1,r]\) 这一个区间。这是左开右闭的形式。
现在还有一个问题:通过这种边我们只能到达区间的右端点,如果想向左到达区间内部的其他点或者到达另一个区间,该怎么办呢?
我们可以对于 \(i\in[1,T]\),从 \(i\to i-1\) 连一条长度为 \(0\) 的边,这样既可以向左移动,又不会算错答案(显然点的移动不会增加新的区间)。
然后求 \(0\) 到 \(T\) 的最短路即可(因为左开右闭)。
注意到边权只有 \(0,1\) 两种取值,所以可以使用 01BFS。时间复杂度 \(O(T+n)\)。
代码:
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define gt getchar
#define pt putchar
typedef long long ll;
const int N=1e6+5;
using namespace std;
using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
template<class T> inline void read(T &x){
x=0;bool sgn=0;char ch=gt();
while(!__(ch)&&ch!=EOF) sgn|=(ch=='-'),ch=gt();
while(__(ch)) x=(x<<1)+(x<<3)+(ch&15),ch=gt();
if(sgn) x=-x;
}
template<class T,class ...T1> inline void read(T &x, T1 &...x1){
read(x);
read(x1...);
}
template<class T> inline void print(T x){
static char st[70];short top=0;
if(x<0) pt('-');
do{st[++top]=x>=0?(x%10+48):(-(x%10)+48),x/=10;}while(x);
while(top) pt(st[top--]);
}
template<class T> inline void printsp(T x){
print(x);
putchar(' ');
}
template<class T> inline void println(T x){
print(x);
putchar('\n');
}
inline void put_str(string s){
int siz=s.size();
for(int i=0;i<siz;++i) pt(s[i]);
printf("\n");
}
struct edge{
int to,nxt,w;
}e[N<<1];
int head[N],cnt,n,T,dis[N];
deque<int>dq;
inline void add_edge(int f,int t,int w){
e[++cnt].to=t;
e[cnt].w=w;
e[cnt].nxt=head[f];
head[f]=cnt;
}
inline void BFS_01(int s){
memset(dis,0x3f3f3f3f,sizeof(dis));
dis[s]=0;
dq.push_back(s);
while(dq.size()){
int u=dq.front();
dq.pop_front();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to,w=e[i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(w) dq.push_back(v);
else dq.push_front(v);
}
}
}
}
signed main(){
read(n,T);
for(int i=1;i<=T;++i) add_edge(i,i-1,0);
for(int u,v,i=1;i<=n;++i){
read(u,v);
add_edge(u-1,v,1);
}
BFS_01(0);
println(dis[T]==0x3f3f3f3f?-1:dis[T]);
return 0;
}
现在加强一下,\(T\) 开到 \(10^9\),怎么办?
注意到区间长度统一缩小后对答案没有影响,因此考虑离散化。
细节:注意我们是要从 \(l-1\to r\) 连边,所以应将 \(l-1\) 和 \(r\) 离散化(而不是 \(l\))!并且,起点 \(S=0\) 和终点 \(T\) 也需要离散化,然后跑最短路时直接使用离散化后的结果。
时间复杂度 \(O(n\log n)\),瓶颈在于离散化。
代码:
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define gt getchar
#define pt putchar
typedef long long ll;
const int N=1e6+5;
using namespace std;
using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
template<class T> inline void read(T &x){
x=0;bool sgn=0;char ch=gt();
while(!__(ch)&&ch!=EOF) sgn|=(ch=='-'),ch=gt();
while(__(ch)) x=(x<<1)+(x<<3)+(ch&15),ch=gt();
if(sgn) x=-x;
}
template<class T,class ...T1> inline void read(T &x, T1 &...x1){
read(x);
read(x1...);
}
template<class T> inline void print(T x){
static char st[70];short top=0;
if(x<0) pt('-');
do{st[++top]=x>=0?(x%10+48):(-(x%10)+48),x/=10;}while(x);
while(top) pt(st[top--]);
}
template<class T> inline void printsp(T x){
print(x);
putchar(' ');
}
template<class T> inline void println(T x){
print(x);
putchar('\n');
}
inline void put_str(string s){
int siz=s.size();
for(int i=0;i<siz;++i) pt(s[i]);
printf("\n");
}
struct edge{
int to,nxt,w;
}e[N<<1];
int head[N],cnt,n,T,dis[N],lsh[N<<1],tot,u[N],v[N];
deque<int>dq;
inline void add_edge(int f,int t,int w){
e[++cnt].to=t;
e[cnt].w=w;
e[cnt].nxt=head[f];
head[f]=cnt;
}
inline void BFS_01(int s){
memset(dis,0x3f3f3f3f,sizeof(dis));
dis[s]=0;
dq.push_back(s);
while(dq.size()){
int u=dq.front();
dq.pop_front();
for(int i=head[u];i;i=e[i].nxt){
int v=e[i].to,w=e[i].w;
if(dis[v]>dis[u]+w){
dis[v]=dis[u]+w;
if(w) dq.push_back(v);
else dq.push_front(v);
}
}
}
}
signed main(){
read(n,T);
int S=0;
lsh[++tot]=S;
for(int i=1;i<=n;++i){
read(u[i],v[i]);
lsh[++tot]=u[i]-1,lsh[++tot]=v[i];
}
lsh[++tot]=T;
sort(lsh+1,lsh+tot+1);
tot=unique(lsh+1,lsh+tot+1)-(lsh+1);
for(int i=1;i<=n;++i){
u[i]=lower_bound(lsh+1,lsh+tot+1,u[i]-1)-lsh;// u[i]-1
v[i]=lower_bound(lsh+1,lsh+tot+1,v[i])-lsh;
add_edge(u[i],v[i],1);
}
S=lower_bound(lsh+1,lsh+tot+1,S)-lsh;
T=lower_bound(lsh+1,lsh+tot+1,T)-lsh;
for(int i=S+1;i<=T;++i) add_edge(i,i-1,0);
BFS_01(S);
println(dis[T]==0x3f3f3f3f?-1:dis[T]);
return 0;
}
二、dp
考虑 DP。
设 \(f_i\) 表示 \([1,i]\) 中所有数被覆盖所需要的最小区间数。初始有 \(f_0=0\)。
记 \(mn_i\) 表示以 \(i\) 为右端点的所有区间中左端点最小值(显然长度更长的区间更优),没有则为 \(-1\),分类讨论:
-
\(mn_i=-1\):直接跳过。
-
\(mn_i\not=-1\):考虑在原有基础上添加 \(i\) 所在的区间,有 \(f_i=\min_{j=mn_i-1}^{i-1}f_j+1\)
分析复杂度:最坏情况下就是区间为 \([1,...T-n+1],[1...T-n+2],....,[1,T]\)。复杂度为 \(O(\sum_{i=0}^{n-1}T-i)=O(Tn)\),过不了。
但是由于数据水,所以能过(但是 1.2s 巨慢)。
代码:
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define gt getchar
#define pt putchar
typedef long long ll;
const int N=1e6+5;
using namespace std;
using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
template<class T> inline void read(T &x){
x=0;bool sgn=0;char ch=gt();
while(!__(ch)&&ch!=EOF) sgn|=(ch=='-'),ch=gt();
while(__(ch)) x=(x<<1)+(x<<3)+(ch&15),ch=gt();
if(sgn) x=-x;
}
template<class T,class ...T1> inline void read(T &x, T1 &...x1){
read(x);
read(x1...);
}
template<class T> inline void print(T x){
static char st[70];short top=0;
if(x<0) pt('-');
do{st[++top]=x>=0?(x%10+48):(-(x%10)+48),x/=10;}while(x);
while(top) pt(st[top--]);
}
template<class T> inline void printsp(T x){
print(x);
putchar(' ');
}
template<class T> inline void println(T x){
print(x);
putchar('\n');
}
inline void put_str(string s){
int siz=s.size();
for(int i=0;i<siz;++i) pt(s[i]);
printf("\n");
}
int n,T,l[N],r[N],min_l[N],f[N];
signed main(){
read(n,T);
memset(min_l,0x3f,sizeof(min_l));
for(int i=1;i<=n;++i) read(l[i],r[i]),min_l[r[i]]=min(min_l[r[i]],l[i]);
memset(f,0x3f,sizeof(f));
f[0]=0;
for(int i=1;i<=T;++i){
if(min_l[i]==0x3f3f3f3f) continue;
for(int j=min_l[i]-1;j<i;++j) f[i]=min(f[i],f[j]+1);
}
println(f[T]==0x3f3f3f3f?-1:f[T]);
return 0;
}
考虑优化,注意 DP 的转移方程,实际上就是一个 RMQ。再加上 \(f_i\) 的更新,也就是说我们只需要支持区间求 \(\min\),单点修改的操作即可。用一棵线段树维护即可。时间复杂度 \(O(T\log T)\)。
求出来区间 \(\min\) 之后有一点小细节,具体见代码。
代码:
#include<bits/stdc++.h>
#include<ext/pb_ds/assoc_container.hpp>
#include<ext/pb_ds/tree_policy.hpp>
#include<ext/pb_ds/hash_policy.hpp>
#define gt getchar
#define pt putchar
typedef long long ll;
const int N=1e6+5;
using namespace std;
using namespace __gnu_pbds;
inline bool __(char ch){return ch>=48&&ch<=57;}
template<class T> inline void read(T &x){
x=0;bool sgn=0;char ch=gt();
while(!__(ch)&&ch!=EOF) sgn|=(ch=='-'),ch=gt();
while(__(ch)) x=(x<<1)+(x<<3)+(ch&15),ch=gt();
if(sgn) x=-x;
}
template<class T,class ...T1> inline void read(T &x, T1 &...x1){
read(x);
read(x1...);
}
template<class T> inline void print(T x){
static char st[70];short top=0;
if(x<0) pt('-');
do{st[++top]=x>=0?(x%10+48):(-(x%10)+48),x/=10;}while(x);
while(top) pt(st[top--]);
}
template<class T> inline void printsp(T x){
print(x);
putchar(' ');
}
template<class T> inline void println(T x){
print(x);
putchar('\n');
}
inline void put_str(string s){
int siz=s.size();
for(int i=0;i<siz;++i) pt(s[i]);
printf("\n");
}
int n,T,l[N],r[N],min_l[N],f[N];
struct Node{
int l,r;
int mn;
}node[N<<2];
inline int lson(int x){return x<<1;}
inline int rson(int x){return x<<1|1;}
inline void push_up(int p){
node[p].mn=min(node[lson(p)].mn,node[rson(p)].mn);
}
void build(int p,int l,int r){
node[p].l=l,node[p].r=r;
if(l==r) return node[p].mn=0x3f3f3f3f,void();
int mid=l+((r-l)>>1);
build(lson(p),l,mid);
build(rson(p),mid+1,r);
push_up(p);
}
int query(int p,int l,int r){
if(l<=node[p].l&&node[p].r<=r) return node[p].mn;
int mid=node[p].l+((node[p].r-node[p].l)>>1);
if(r<=mid) return query(lson(p),l,r);
if(l>mid) return query(rson(p),l,r);
return min(query(lson(p),l,r),query(rson(p),l,r));
}
void update(int p,int x,int k){
if(node[p].l==node[p].r) return node[p].mn=k,void();
int mid=node[p].l+((node[p].r-node[p].l)>>1);
if(x<=mid) update(lson(p),x,k);
else update(rson(p),x,k);
push_up(p);
}
signed main(){
read(n,T);
memset(min_l,0x3f,sizeof(min_l));
for(int i=1;i<=n;++i) read(l[i],r[i]),min_l[r[i]]=min(min_l[r[i]],l[i]);
memset(f,0x3f,sizeof(f));
f[0]=0;
build(1,0,T);
update(1,0,0);
for(int i=1;i<=T;++i){
if(min_l[i]==0x3f3f3f3f) continue;
f[i]=query(1,min_l[i]-1,i-1);
if(f[i]!=0x3f3f3f3f) f[i]++;
update(1,i,f[i]);
}
println(f[T]==0x3f3f3f3f?-1:f[T]);
return 0;
}
应该也可以离散化。