洛谷题单指南-最短路-P3275 [SCOI2011] 糖果
原题链接:https://www.luogu.com.cn/problem/P3275
题意解读:n个小朋友分糖果,每个人分得di个,每个人分得的数量满足一组不等式关系,求最少需要多少糖果才能保证所有小朋友都能分到糖果。
解题思路:又是一道典型的差分约束问题,关键在于先确定要求什么,再考虑如何确定不等式关系,进而建图。
题目要求的是最少一共有多少糖果,只需要求出每一个小朋友最少糖果数,再加和即可,也就是求图中每个点的最长路再相加。
为什么每个小朋友的最少糖果数对应最长路?
因为对于一组不等式要求最小值,最终要简化为形如Xi>=a,Xi>=b,Xi>=c...Xi的最小值必然是a、b、c中的最大值,因此要求最长路。
最长路在不等式中的关系是">="。
如果无法满足所有小朋友的要求,表示不等式无可能解,说明图中存在正环。
对于题目中给出的5种关系,可以分别进行如下转化:
1、如果 , 表示第 个小朋友分到的糖果必须和第 个小朋友分到的糖果一样多
关系为da = db,转化为da >= db,db >= da,建立从a->b,b->a的权值为0的边。
2、如果 , 表示第 个小朋友分到的糖果必须少于第 个小朋友分到的糖果
关系为da < db,转化为db >= da + 1,建立从a->b权值为1的边。
3、如果 , 表示第 个小朋友分到的糖果必须不少于第 个小朋友分到的糖果
关系为da >= db,建立从b->a权值为0的边。
4、如果 , 表示第 个小朋友分到的糖果必须多于第 个小朋友分到的糖果
关系为da > db,转化为da <= db + 1,建立从b->a权值为1的边。
5、如果 , 表示第 个小朋友分到的糖果必须不多于第 个小朋友分到的糖果
关系为da <= db,转化为db >= da,建立从a->b权值为0的边。
注意:由于要保证所有小朋友都能分到糖果,这是一个关键的隐藏条件
对于所有的1<=i<=n,有di > 0
我们可以增加一个0号小朋友,让他分得的糖果数为0,这样就有所有的di > d0
转化为di >= d0 + 1,建立从0到所有1~n的点权值为1的边即可。
另外:本题会卡SPFA,通过使用两个hack技巧之后,仍然会有2个测试点无法通过,但还是会首先给出SPFA求最长路的方法,后面给出正解。
方法一 :SPFA
hack1:用栈替换队列,更快找到环
hack2:限定最大松弛次数,更快判断是环
#include <bits/stdc++.h>
using namespace std;
const int N = 100005, M = 300005;
int h[N], e[M], w[M], ne[M], idx;
int dist[N], cnt[N], total;
bool vis[N];
int n, m;
void add(int a, int b, int c)
{
e[++idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx;
}
//求最长路,判正环
bool spfa()
{
memset(dist, -0x3f, sizeof(dist));
dist[0] = 0;
stack<int> q; //hack1:用栈替换队列,更快找到环
q.push(0);
while(q.size())
{
int u = q.top(); q.pop();
vis[u] = false;
for(int i = h[u]; ~i; i = ne[i])
{
int v = e[i];
if(dist[v] < dist[u] + w[i])
{
dist[v] = dist[u] + w[i];
cnt[v] = cnt[u] + 1;
if(++total > 10 * n) return true; //hack2:限定最大松弛次数,更快判断是环
if(cnt[v] >= n + 1) return true; //存在正环
if(!vis[v])
{
q.push(v);
vis[v] = true;
}
}
}
}
return false;
}
int main()
{
cin.tie(0); cout.tie(0); ios::sync_with_stdio(false);
memset(h, -1, sizeof(h));
cin >> n >> m;
while(m--)
{
int x, a, b;
cin >> x >> a >> b;
if(x == 1) add(a, b, 0), add(b, a, 0);
else if(x == 2) add(a, b, 1);
else if(x == 3) add(b, a, 0);
else if(x == 4) add(b, a, 1);
else add(a, b, 0);
}
for(int i = 1; i <= n; i++) add(0, i, 1);
if(spfa()) cout << -1;
else
{
long long ans = 0;
for(int i = 1; i <= n; i++) ans += dist[i];
cout << ans;
}
return 0;
}
方法二:强联通分量
本题的关键在于:1、判断正环; 2、求最长路。
接着分析,本题建图之后所有权值都是>=0,如果有向图存在环,一定在强联通分量中且该强联通分量必须有一条边权值>0;
如果不存在环,则说明有向图的所有强联通分量内边的权值都==0,如此即可判断是否有正环。
既然一个强连通分量中,所有边都是0,说明强联通分量所有点的值相等,也就是所有点的最长路相等,因此可以对强联通分量进行缩点,
这样就得到一个DAG,对于拓扑图可以通过递推求得最长路。
最后,求答案的时候,要注意一个缩点的最长路要乘上该连通分量内节点数量,最后累加得到结果。
100分代码(AC代码):
#include <bits/stdc++.h>
using namespace std;
const int N = 100005, M = 600005;
int h1[N], h2[N], e[M], w[M], ne[M], idx;
int dfn[N], low[N], timestamp;
stack<int> stk;
bool vis[N];
int scc[N], cnt;
int sz[N];
long long dist[N];
int n, m;
void add(int h[], int a, int b, int c)
{
e[++idx] = b;
w[idx] = c;
ne[idx] = h[a];
h[a] = idx;
}
void tarjan(int u)
{
dfn[u] = low[u] = ++timestamp;
stk.push(u);
vis[u] = true;
for(int i = h1[u]; i; i = ne[i])
{
int v = e[i];
if(!dfn[v])
{
tarjan(v);
low[u] = min(low[u], low[v]);
}
else if(vis[v])
{
low[u] = min(low[u], dfn[v]);
}
}
if(low[u] == dfn[u])
{
cnt++;
while(true)
{
int t = stk.top(); stk.pop();
vis[t] = false;
scc[t] = cnt;
sz[cnt]++;
if(t == u) break;
}
}
}
bool build()
{
for(int i = 0; i <= n; i++) //从0开始的点
{
for(int j = h1[i]; j; j = ne[j])
{
int u = scc[i], v = scc[e[j]];
if(u != v)
{
add(h2, u, v, w[j]);
}
else //i,e[j]在一个连通分量里
{
if(w[j] > 0) return true; //存在正环
}
}
}
return false;
}
int main()
{
cin >> n >> m;
while(m--)
{
int x, a, b;
cin >> x >> a >> b;
if(x == 1) add(h1, a, b, 0), add(h1, b, a, 0);
else if(x == 2) add(h1, a, b, 1);
else if(x == 3) add(h1, b, a, 0);
else if(x == 4) add(h1, b, a, 1);
else add(h1, a, b, 0);
}
for(int i = 1; i <= n; i++) add(h1, 0, i, 1);
tarjan(0); //计算强联通分量
if(build()) //缩点,判断是否有正环
{
cout << -1;
return 0;
}
//递推最长路
for(int i = cnt; i >= 1; i--)
{
for(int j = h2[i]; j; j = ne[j])
{
int v = e[j];
if(dist[v] < dist[i] + w[j])
{
dist[v] = dist[i] + w[j];
}
}
}
long long ans = 0;
for(int i = 1; i <= cnt; i++) ans += dist[i] * sz[i];
cout << ans;
return 0;
}
浙公网安备 33010602011771号