【题目链接】
ybt 1511:【SCOI2011】糖果
洛谷 P3275 [SCOI2011] 糖果
【题目考点】
1. 图论:差分约束系统
- 求变量的最大值:
构造不等式形如: x a − x b ≤ c x_a-x_b\le c xa−xb≤c,对应图中一条从顶点b到顶点a的权值为c的有向边。求图中最短路径,即可得到 x x x的一组解。
如果图中存在负环则无解。 - 求变量的最小值:
构造不等式形如: x a − x b ≥ c x_a-x_b\ge c xa−xb≥c,对应图中一条从顶点b到顶点a的权值为c的有向边。求图中最长路径,即可得到 x x x的一组解。
如果图中存在正环则无解。
2. 图论:强连通分量 缩点 tarjan算法
3. 图论:有向无环图动规 拓扑排序
【解题思路】
x
i
x_i
xi表示第i个小朋友分到的糖果
要想知道老师最少需要准备的糖果数,需要得到
x
1
≥
c
1
x_1\ge c_1
x1≥c1
x
2
≥
c
2
x_2\ge c_2
x2≥c2
…
x
n
≥
c
n
x_n\ge c_n
xn≥cn
这一不等式组中
c
1
,
.
.
.
,
c
n
c_1,...,c_n
c1,...,cn的值,
需要建立类似
x
i
−
x
j
≥
c
x_i-x_j\ge c
xi−xj≥c的不等式构成的不等式组。
对于不等式
x
i
−
x
j
≥
c
x_i-x_j\ge c
xi−xj≥c,在图中建立一条从顶点j到顶点i权值为c的边,这样的一条边记作<j,i,c>。
每次循环输入X, a, b
- 如果
X
=
1
X=1
X=1,则
x
a
=
x
b
x_a = x_b
xa=xb,转为不等式
x
a
−
x
b
≥
0
x_a-x_b\ge 0
xa−xb≥0与
x
b
−
x
a
≥
0
x_b-x_a\ge 0
xb−xa≥0
建边<b,a,0>, <a,b,0>。 - 如果 X = 2 X=2 X=2,则 x a < x b x_a<x_b xa<xb,即 x b − x a ≥ 1 x_b-x_a\ge 1 xb−xa≥1,建边<a,b,1>。
- 如果 X = 3 X=3 X=3,则 x a ≥ x b x_a\ge x_b xa≥xb,即 x a − x b ≥ 0 x_a-x_b\ge 0 xa−xb≥0,建边<b,a,0>。
- 如果 X = 4 X=4 X=4,则 x a > x b x_a> x_b xa>xb,即 x a − x b ≥ 1 x_a-x_b\ge 1 xa−xb≥1,建边<b,a,1>。
- 如果 X = 5 X=5 X=5,则 x a ≤ x b x_a\le x_b xa≤xb,即 x b − x a ≥ 0 x_b-x_a\ge 0 xb−xa≥0,建边<a,b,0>。
题中提到,每个小朋友都要分到糖果,因此有
x
1
≥
1
x_1\ge 1
x1≥1,
x
2
≥
1
x_2\ge 1
x2≥1, …,
x
n
≥
1
x_n \ge 1
xn≥1
设超级源点顶点0,顶点0到顶点0的最长路径长度为0,因此
x
0
=
0
x_0=0
x0=0
所以上述不等式组可以写为
x
1
−
x
0
≥
1
x_1-x_0\ge 1
x1−x0≥1
x
2
−
x
0
≥
1
x_2-x_0\ge 1
x2−x0≥1
…
x
n
−
x
0
≥
1
x_n-x_0\ge 1
xn−x0≥1
建边<0,1,1>, <0,2,1>,…<0,n,1>。
求出从顶点0到每个其他顶点的最长路径,即可得到
x
1
,
.
.
.
,
x
n
x_1,...,x_n
x1,...,xn的一组解。
本题顶点数能达到
1
0
5
10^5
105,边数也可以达到
1
0
5
10^5
105,如果使用spfa算法求单源最长路,最差情况下时间复杂度为
O
(
V
E
)
O(VE)
O(VE)(V为顶点数,E为边数),
V
E
VE
VE最大为
1
0
10
10^{10}
1010,会超时。
本题可以考虑使用拓扑排序求单源最长路。
首先调用tarjan算法将原图g缩点,得到缩点后的图tg。
原图g中一个强连通分量中的边一定处于该强连通分量内的一个环中。
由以上分析可知,每条边的权值都大于等于0,不存在负权边。
因此如果一个强连通分量内存在正权边,由于其他的边都是非负的,那么该正权边所在的由该强连通分量内的边所构成的环一定是正权环。
如果该图存在正权环,则无法找到
x
x
x的一组解,即不能满足小朋友们的要求,输出-1,结束程序。
实际上,如果不存在正权环,该图中每个强连通分量中的边的权值都为0。
对缩点后的图tg进行拓扑排序动规求最长路
dis[i]
表示从源点到顶点i的最长路径长度
当顶点u访问邻接点v时
存在一条从源点到顶点u再到顶点v的,长度为dis[u]+w
的路径。
如果源点到顶点u的最长路径长度加上顶点u到顶点v的边权w,大于源点到顶点v的最长路径长度。
那么源点到顶点v的最长路径长度就是源点到顶点u的最长路径长度加上顶点u到顶点v的边权w。
即dis[v] = max(dis[v], dis[u]+w)
在拓扑排序的过程中执行状态转移方程,求出dis数组。
scc[i]
是原图g中顶点i所在强连通分量的编号,也就是在缩点后的图中的顶点编号。
因为顶点i所在强连通分量中各顶点之间的边的权值都为0,源点到一个强连通分量中各顶点的最长路径长度相同。
原图g中源点0到顶点i的最长路径长度,就是在缩点后的图中源点scc[0]
到顶点scc[i]
的最长路径长度。
将原图g中源点到每个顶点的最长路径长度加和,即为老师至少要准备的糖果数。
复杂度分析
顶点数为V,即题中的n,边数为E,图中边的数量大体为
k
+
n
k+n
k+n。
建图
O
(
V
+
E
)
O(V+E)
O(V+E)
调用tarjan算法
O
(
V
+
E
)
O(V+E)
O(V+E)
缩点建图
O
(
V
+
E
)
O(V+E)
O(V+E)
拓扑排序
O
(
V
+
E
)
O(V+E)
O(V+E)
整体时间复杂度为
O
(
V
+
E
)
O(V+E)
O(V+E),语句执行次数为
1
0
5
10^5
105量级
【题解代码】
解法1:差分约束系统
#include <bits/stdc++.h>
using namespace std;
#define N 100005
struct Edge
{
int v, w;
};
vector<Edge> g[N], tg[N];
int n, k, dis[N], degIn[N];
long long sum;
bool inQue[N], inStk[N];
int dfn[N], low[N], ts, scc[N], sn;
stack<int> stk;
void tarjan(int u)
{
int t;
dfn[u] = low[u] = ++ts;
stk.push(u);
inStk[u] = true;
for(Edge e : g[u])
{
int v = e.v;
if(dfn[v] == 0)
{
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if(inStk[v])
low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u])
{
++sn;
do
{
t = stk.top();
stk.pop();
inStk[t] = false;
scc[t] = sn;
}while(t != u);
}
}
void topoSort()
{
queue<int> que;
que.push(scc[0]);
while(!que.empty())
{
int u = que.front();
que.pop();
for(Edge e : tg[u])
{
int v = e.v, w = e.w;
dis[v] = max(dis[v], dis[u]+w);
if(--degIn[v] == 0)
que.push(v);
}
}
}
int main()
{
int x, a, b;
cin >> n >> k;
for(int i = 1; i <= n; ++i)//每个小朋友都分到糖,至少有1块糖,xi >= 1 => xi-x0 >= 1
g[0].push_back(Edge{i, 1});
for(int i = 1; i <= k; ++i)
{
cin >> x >> a >> b;
if(x == 1)//xa == xb => xa-xb >= 0 , xb-xa>=0
{
g[a].push_back(Edge{b, 0});
g[b].push_back(Edge{a, 0});
}
else if(x == 2)//xa < xb => xb-xa > 0 => xb-xa >= 1
g[a].push_back(Edge{b, 1});
else if(x == 3)//xa >= xb => xa-xb >= 0
g[b].push_back(Edge{a, 0});
else if(x == 4)//xa > xb => xa-xb > 0 => xa-xb >= 1
g[b].push_back(Edge{a, 1});
else if(x == 5)//xa <= xb => xb-xa >= 0
g[a].push_back(Edge{b, 0});
}
for(int i = 0; i <= n; ++i) if(dfn[i] == 0)
tarjan(i);
for(int u = 0; u <= n; ++u)
for(Edge e : g[u])
{
int v = e.v, w = e.w;
if(scc[u] == scc[v])//强连通分量中有正权边,一定有正权环
{
if(w > 0)
{
cout << -1;
return 0;
}
}
else
{
tg[scc[u]].push_back(Edge{scc[v], w});
degIn[scc[v]]++;
}
}
topoSort();
for(int i = 1; i <= n; ++i)
sum += dis[scc[i]];
cout << sum;
return 0;
}