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;
}

应该也可以离散化。

posted @ 2024-02-28 15:15  Southern_Dynasty  阅读(5)  评论(0编辑  收藏  举报