题目大意
给出
m
m
m个区间
[
l
,
r
]
[l,r]
[l,r](
1
≤
l
≤
r
≤
n
1 \leq l \leq r \leq n
1≤l≤r≤n),所有区间合并后不一定包含
1
∼
n
1 \sim n
1∼n,且每个区间内有且仅有一个点是特殊的,求
1
∼
n
1 \sim n
1∼n内最多有多少个点是特殊的。若数据不合法,输出
−
1
-1
−1,否则输出特殊点最多的个数。(注意:没有被区间包含的数也可以算特殊点)
对于
100
%
100\%
100%的数据,
1
≤
n
≤
2
×
1
0
5
1 \leq n \leq 2 \times 10^5
1≤n≤2×105。
分析
这是一道动态规划( D P DP DP)题,我们可以用差分约束系统做。那么什么是差分约束系统呢?
如果一个系统由 n n n个变量和 m m m个约束条件组成,形成 m m m个形如 a i − a j ≤ k a_i-a_j \leq k ai−aj≤k的不等式( i , j ∈ [ 1 , n ] i,j \in [1,n] i,j∈[1,n], k k k为常数),则称其为差分约束系统( s y s t e m o f d i f f e r e n c e c o n s t r a i n t s system \space of \space difference \space constraints system of difference constraints)。亦即,差分约束系统是求解关于一组变量的特殊不等式组的方法。
——引用自百度百科
简单来说,差分约束系统就是一些像
a
i
−
a
j
≤
k
a_i-a_j \leq k
ai−aj≤k的不等式。
对这道题,我们似乎看不出有什么形如
a
i
−
a
j
≤
k
a_i-a_j \leq k
ai−aj≤k的不等式。但是我们转化一下题目,设
f
i
f_i
fi表示区间
[
1
,
i
]
[1,i]
[1,i]内最多有多少个特殊的点(
f
0
=
0
f_0=0
f0=0),那么不等式就可以被表示出了。
对于
f
i
f_i
fi和
f
i
−
1
f_{i-1}
fi−1,我们可以知道
f
i
≥
f
i
−
1
f_i \geq f_{i-1}
fi≥fi−1,变化此式可得
f
i
−
f
i
−
1
≥
0
f_i-f_{i-1} \geq 0
fi−fi−1≥0,
f
i
−
1
−
f
i
≤
0
f_{i-1}-f_i \leq 0
fi−1−fi≤0。于是,第一个不等式就出来了:对于
i
∈
[
1
,
n
]
i \in [1,n]
i∈[1,n],
f
i
−
1
−
f
i
≤
0
f_{i-1}-f_i \leq 0
fi−1−fi≤0。
因为对于区间
[
a
,
b
]
[a,b]
[a,b],有且仅有一个点是特殊的,所以我们可得
f
b
−
f
a
−
1
=
1
f_b-f_{a-1}=1
fb−fa−1=1——因为如果
f
b
−
f
a
−
1
<
1
f_b-f_{a-1}<1
fb−fa−1<1,那么区间里就没有点是特殊的了;如果
f
b
−
f
a
−
1
>
1
f_b-f_{a-1}>1
fb−fa−1>1,那么区间里就有不止一个点是特殊的了。但
f
b
−
f
a
−
1
=
1
f_b-f_{a-1}=1
fb−fa−1=1是等式,不是不等式,所以我们要转化一下。由
f
b
−
f
a
−
1
=
1
f_b-f_{a-1}=1
fb−fa−1=1可得
{
f
b
−
f
a
−
1
≤
1
f
b
−
f
a
−
1
≥
1
\left \{^{f_b-f_{a-1} \geq 1}_{f_b-f_{a-1} \leq 1} \right.
{fb−fa−1≤1fb−fa−1≥1(两个式子要同时满足,这样一来
f
b
−
f
a
−
1
=
1
f_b-f_{a-1}=1
fb−fa−1=1就成立了)。对于
f
b
−
f
a
−
1
≥
1
f_b-f{a-1} \geq 1
fb−fa−1≥1,变化后可得
f
a
−
1
−
f
b
≤
−
1
f_{a-1}-f_b \leq -1
fa−1−fb≤−1。于是,第二、三个不等式就出来了:对于每个区间
[
a
,
b
]
[a,b]
[a,b],
{
f
b
−
f
a
−
1
≤
1
f
a
−
1
−
f
b
≤
−
1
\left \{^{f_{a-1}-f_b \leq -1}_{f_b-f_{a-1} \leq 1} \right.
{fb−fa−1≤1fa−1−fb≤−1。
因为对于区间
[
a
,
b
]
[a,b]
[a,b],有且仅有一个点是特殊的,所以最后一个不等式是:对于
i
i
i和
i
−
1
i-1
i−1(
i
∈
[
1
,
n
]
i \in [1,n]
i∈[1,n]),一定有
f
i
−
f
i
−
1
≤
1
f_i-f_{i-1} \leq 1
fi−fi−1≤1。
题目中样例连边后的情况如图所示:
找出不等式后,我们要怎么求出答案呢?
(差分约束系统求解时)以每个变量 a i a_i ai为结点,对于约束条件 a j − a i ≤ b k a_j-a_i \leq b_k aj−ai≤bk,连接一条有向边 ( i , j ) (i,j) (i,j),边权为 b k b_k bk。我们再增加一个源点 s s s, s s s与所有定点相连,边权均为 0 0 0。对这个图,以 s s s为源点运行最短路算法,最终 { d i } \{ d_i \} {di}即为一组可行解。
——引用自百度百科
简单来讲,差分约束系统求解时,对于每一个不等式
a
i
−
a
j
≤
k
a_i-a_j \leq k
ai−aj≤k,由
j
j
j向
i
i
i连一条权值为
k
k
k的边,最后以所有定点为源点,求最短路即可。
为什么可以这样做呢?我们观察一下
a
i
−
a
j
≤
k
a_i-a_j \leq k
ai−aj≤k,它移项后可以变成
a
i
≤
a
j
+
k
a_i \leq a_j+k
ai≤aj+k,这是不是很像最短路满足的
d
i
s
j
≤
d
i
s
i
+
w
i
,
j
dis_j \leq dis_i+w_{i,j}
disj≤disi+wi,j(
(
i
,
j
)
∈
E
(i,j) \in E
(i,j)∈E)呢?由此,我们可以想到,把这些不等式转化成有向边,把把一个量转化成点,然后用最短路求解。
根据我们的式子,可以得到连边的方案:
- 对于 f i − 1 − f i ≤ 0 f_{i-1}-f_i \leq 0 fi−1−fi≤0,我们由结点 i i i向 i − 1 i-1 i−1连一条权值为 0 0 0的有向边;
- 对于 f a − 1 − f b ≤ − 1 f_{a-1}-f_b \leq -1 fa−1−fb≤−1,我们由结点 b b b向 a − 1 a-1 a−1连一条权值为 − 1 -1 −1的有向边;
- 对于 f b − f a − 1 ≤ 1 f_b-f_{a-1} \leq 1 fb−fa−1≤1,我们由结点 a − 1 a-1 a−1向 b b b连一条权值为 1 1 1的有向边;
- 对于 f i − f i − 1 ≤ 1 f_i-f_{i-1} \leq 1 fi−fi−1≤1,我们由结点 i − 1 i-1 i−1向 i − 1 i-1 i−1连一条权值为 1 1 1的有向边。
(注意:在连边的时候,有可能会出现
i
−
1
=
0
i-1=0
i−1=0或
a
−
1
=
0
a-1=0
a−1=0的情况,所以我们要新增一个结点
0
0
0)
题目中样例连边后的情况如图所示:
有了边以后,我们怎么求解呢?其实,差分约束系统求解就是给某个(些)量赋予特殊的值后用最短路求出整个图的解。那么本题中也一样。我们通过
f
i
f_i
fi的定义可以知道,
f
0
=
0
f_0=0
f0=0。于是,我们就得到了求解方法:令
最短路径
0
=
0
\text{最短路径} _0=0
最短路径0=0,然后以结点
0
0
0为源点求最短路,最后输出
最短路径
n
\text{最短路径} _n
最短路径n的值即可(要求
f
n
f_n
fn的值)。又因为本题中
n
n
n较大,所以要用
S
P
F
A
SPFA
SPFA。
然而,有时候图中会有负权回路(负环),这时题目无解,于是我们要判断一下是否有负环。又由于题目数据卡
S
P
F
A
SPFA
SPFA,所以我们要用堆优化
S
P
F
A
SPFA
SPFA,并且在求最短路的过程中记录一下每个结点的入堆次数,如果
入堆次数
≥
n
+
1
\text{入堆次数} \geq n+1
入堆次数≥n+1,那么图中有负环。同时我们要卡一下时间,如果总进堆次数过了
1
0
6
10^6
106次,那么我们也认为图中有负环。
代码如下:
#include<cstdio>
void swap(int &a,int &b)//交换函数
{
int t=a;
a=b;
b=t;
return;
}
struct edge{int to/*终点*/,w/*权值*/,next/*下一条边*/;}e[1000001];//边
struct node{bool fl/*是否在堆中*/;int dis/*到结点0的距离(答案)*/,num/*进堆的次数*/,last/*最后一条连出的边*/,pos/*在堆中的位置*/;}a[200002];//点
int len;
void link(int x,int y,int w)//连边函数
{
++len;
e[len].to=y;
e[len].w=w;
e[len].next=a[x].last;
a[x].last=len;
return;
}
int heap[200002];//堆(heap[0]表示堆的大小)
void change(int x,int y)//交换堆中的节点
{
swap(a[heap[x]].pos,a[heap[y]].pos);//交换a数组中记录的两个结点在堆中的位置
swap(heap[x],heap[y]);//交换堆中的两个结点
return;
}
void push(int x)//插入堆
{
int p=(++heap[0]);//新增结点
heap[p]=x;
a[x].pos=p;
while(p>1&&a[heap[p]].dis<a[heap[p/2]].dis)//向上调整
{
change(p,p/2);
p/=2;
}
return;
}
void down(int x)//向下调整
{
while(x*2<=heap[0])
{
int y;
if(a[heap[x*2]].dis<a[heap[x]].dis)
{
y=x*2;
}
else
{
y=x;
}
if(x*2+1<=heap[0]&&a[heap[x*2+1]].dis<a[heap[y]].dis)
{
y=x*2+1;
}
if(y==x)
{
break;
}
change(x,y);
x=y;
}
return;
}
void pop()//弹出堆顶
{
heap[1]=heap[heap[0]--];
down(1);
return;
}
int main()
{
int n,m;
scanf("%d%d",&n,&m);//读入n,m
for(int i=0;i<=n;i++)//初始化a数组
{
a[i].last=-1;
a[i].dis=2147483647;
a[i].num=0;
a[i].fl=0;
}
len=0;
for(int i=1;i<=n;i++)
{
link(i-1,i,1);//对f[i]-f[i-1]<=1连边
link(i,i-1,0);//对f[i-1]-f[i]<=0连边
}
for(int i=1;i<=m;i++)
{
int a,b;
scanf("%d%d",&a,&b);//读入区间左右端点
link(a-1,b,1);//对f[b]-f[a-1]<=1连边
link(b,a-1,-1);//对f[a-1]-f[b]<=-1连边
}
a[0].dis=0;//初始化结点0
heap[0]=0;//初始化堆
push(0);//插入结点0
a[0].num=1;//更新数值
a[0].fl=1;
bool error=0;//是否有负环
int total=1;//总进堆次数
while(heap[0]>=1)//堆中有数
{
int x=heap[1];//取出堆顶
pop();
a[x].fl=0;
for(int i=a[x].last;i!=-1;i=e[i].next)//遍历每一条出边
{
int y=e[i].to;
if(a[y].dis>a[x].dis+e[i].w)//松弛
{
a[y].dis=a[x].dis+e[i].w;
if(a[y].fl)//如果y在堆中
{
down(a[y].pos);//调整
}
else
{
if((++a[y].num)==n+1||(++total)>=1000000)//判断是否有负环
{
error=1;
break;
}
push(y);//插入y
a[y].fl=1;
}
}
}
if(error)
{
break;
}
}
if(error)//如果有负环(无解)
{
printf("-1");
}
else
{
printf("%d",a[n].dis);//输出答案
}
return 0;
}
总结
有时我们要将题目转化一下来得到解题的方法,如果我们想得多一些,可能解题的方法就简单一些。