【差分约束】 P3275 [SCOI2011] 糖果|省选-

ops/2025/3/17 10:56:23/

本文涉及知识点

差分约束

P3275 [SCOI2011] 糖果

题目描述

幼儿园里有 N N N 个小朋友, lxhgww \text{lxhgww} lxhgww 老师现在想要给这些小朋友们分配糖果,要求每个小朋友都要分到糖果。但是小朋友们也有嫉妒心,总是会提出一些要求,比如小明不希望小红分到的糖果比他的多,于是在分配糖果的时候, lxhgww \text{lxhgww} lxhgww 需要满足小朋友们的 K K K 个要求。幼儿园的糖果总是有限的, lxhgww \text{lxhgww} lxhgww 想知道他至少需要准备多少个糖果,才能使得每个小朋友都能够分到糖果,并且满足小朋友们所有的要求。

输入格式

输入的第一行是两个整数 N N N K K K。接下来 K K K 行,表示这些点需要满足的关系,每行 3 3 3 个数字, X X X A A A B B B

  • 如果 X = 1 X=1 X=1, 表示第 A A A 个小朋友分到的糖果必须和第 B B B 个小朋友分到的糖果一样多;
  • 如果 X = 2 X=2 X=2, 表示第 A A A 个小朋友分到的糖果必须少于第 B B B 个小朋友分到的糖果
  • 如果 X = 3 X=3 X=3, 表示第 A A A 个小朋友分到的糖果必须不少于第 B B B 个小朋友分到的糖果
  • 如果 X = 4 X=4 X=4, 表示第 A A A 个小朋友分到的糖果必须多于第 B B B 个小朋友分到的糖果
  • 如果 X = 5 X=5 X=5, 表示第 A A A 个小朋友分到的糖果必须不多于第 B B B 个小朋友分到的糖果

输出格式

输出一行,表示 lxhgww \text{lxhgww} lxhgww 老师至少需要准备的糖果数,如果不能满足小朋友们的所有要求,就输出 − 1 -1 1

输入输出样例 #1

输入 #1

5 7
1 1 2
2 3 2
4 4 1
3 4 5
5 4 5
2 3 5
4 5 1

输出 #1

11

说明/提示

对于 30 % 30\% 30% 的数据,保证 N ≤ 100 N\leq100 N100

对于 100 % 100\% 100% 的数据,保证 N ≤ 100000 N\leq100000 N100000

对于所有的数据,保证 K ≤ 100000 , 1 ≤ X ≤ 5 , 1 ≤ A , B ≤ N K\leq100000, 1\leq X\leq5, 1\leq A, B\leq N K100000,1X5,1A,BN


upd 2022.7.6 \text{upd 2022.7.6} upd 2022.7.6:新添加 21 21 21 组 Hack 数据。

P3275 差分约束 缩点 拓扑排序

利用差分约束求解,解很可能有负数。 令糖果最少得小朋友得到的糖果数量是x1。所有的糖果数 -x1+1。这样糖果数是整数,且最少。
x=1,即 A-B <=0 B - A <=0
x=2 A-B <0 即 A-B <=-1
x=3 A-B >=0 即 B-A <=0
x=4 A-B >0 即B-A<0 即B-A<=-1
x=5即 A-B <= 0
最多1e5个点,边权最多1.故1e6就是极大值。
时间复杂度:O(nn) 时间超过限制

缩点

如果有环,一定是0环或负环。负环返回-1。0环缩点。
并集查找uf记录锁在一起的点。
DFS查环。DFS(pars,pw,w,cur)
cur是当前子树的根,pars记录cur所有祖先 pw[i]记录cur的祖先i到根的边权和,1表示没有祖先i。w表示根到i的边权和。
函数外变量vis记录vis[i]是否访问。
如果vis[cur]为真,返回。
如果pw[cur]不是1,遇到环。
{
w-pw 不是0,本题答案-1。程序结束。
利用pars变量将两个cur直接所有的点和cur在uf中连通。本次DFS结束。
}
pars增加cur pw[cur]= w
枚举临接点next ,边权ew DFS(pars,pw,w+ew,next)
pars.pop_back() pw[cur]=1
vis[cur]=true
时间复杂度:O(M) M是边数。没个边访问一次。
只有第一次DFS(cur)时,会遍历cur的后续节点。如果cur已经访问完毕,vis[cur]为真;如果正在访问cur,pw[cur]不为1;这两种情况都不访问后效节点。

缩点性质

i,j,k等是0环,锁点成i。j,k的临接点变成i的临接点。
性质一:缩点后,不会让原来不连通的点连通。缩点后u → \rightarrow i → \rightarrow v,则缩点前u → \rightarrow → \rightarrow v。
性质二:缩点后,不会让原来连通的点不连通。缩点前u → \rightarrow → \rightarrow v,缩点后u → \rightarrow i → \rightarrow v。
性质三:缩点后,不会产生新环,证明类似性质一。
性质四:缩点后,被缩点的没有边。不影响top排序的正确性。
性质四:缩点会产生重边,不影响top排序的正确性。
性质五:缩点或产生自环。这个影响top排序,必须忽略。

求最小正整数解

利用拓扑排序,求最短路。便是差分约束的一个解。我们求的是最小正整数解。如果div[j] < 1,则将dis[j]即其前置节点全部+(1-dis[j])。如果i有多个直接或间接的后置节点,则dis[i]只需要增加最大值。暴力增加的时间复杂度也是O(nn)。按拓扑序可以到O(n)。

拓扑排序

不求解,直接拓扑序。初始,出度为0的i,dis[i]=1。删除出度为0的点,不断迭代处理新出度为0的点。边权为0,前置节点不小于后者节点;边权为-1,前置节点至少比后续节点大1。

错误解放

求了差分约束的一个解后,求最小值iMin,所有数+(1-iMin)。错误示例:1比2多,2比3多,1比4多。最短距离为:0,-1,-2,-1,全部+3后,就是3,2,1,2,正解是3,2,1,1。

0负1BFS

队列中有cur和cur-1,必须先处理cur-1才是最短路。遇到0边时cur-1,遇到-1边时cur-2。队列中就有了cur,cur-1,cur-2。不符合01BFS的条件。

代码

核心代码

#include <iostream>
#include <sstream>
#include <vector>
#include<map>
#include<unordered_map>
#include<set>
#include<unordered_set>
#include<string>
#include<algorithm>
#include<functional>
#include<queue>
#include <stack>
#include<iomanip>
#include<numeric>
#include <math.h>
#include <climits>
#include<assert.h>
#include<cstring>
#include<list>#include <bitset>
using namespace std;template<class T1, class T2>
std::istream& operator >> (std::istream& in, pair<T1, T2>& pr) {in >> pr.first >> pr.second;return in;
}template<class T1, class T2, class T3 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3>& t) {in >> get<0>(t) >> get<1>(t) >> get<2>(t);return in;
}template<class T1, class T2, class T3, class T4 >
std::istream& operator >> (std::istream& in, tuple<T1, T2, T3, T4>& t) {in >> get<0>(t) >> get<1>(t) >> get<2>(t) >> get<3>(t);return in;
}template<class T = int>
vector<T> Read() {int n;scanf("%d", &n);vector<T> ret(n);for (int i = 0; i < n; i++) {cin >> ret[i];}return ret;
}template<class T = int>
vector<T> Read(int n) {vector<T> ret(n);for (int i = 0; i < n; i++) {cin >> ret[i];}return ret;
}template<int N = 1'000'000>
class COutBuff
{
public:COutBuff() {m_p = puffer;}template<class T>void write(T x) {int num[28], sp = 0;if (x < 0)*m_p++ = '-', x = -x;if (!x)*m_p++ = 48;while (x)num[++sp] = x % 10, x /= 10;while (sp)*m_p++ = num[sp--] + 48;AuotToFile();}void writestr(const char* sz) {strcpy(m_p, sz);m_p += strlen(sz);AuotToFile();}inline void write(char ch){*m_p++ = ch;AuotToFile();}inline void ToFile() {fwrite(puffer, 1, m_p - puffer, stdout);m_p = puffer;}~COutBuff() {ToFile();}
private:inline void AuotToFile() {if (m_p - puffer > N - 100) {ToFile();}}char  puffer[N], * m_p;
};template<int N = 1'000'000>
class CInBuff
{
public:inline CInBuff() {}inline CInBuff<N>& operator>>(char& ch) {FileToBuf();ch = *S++;return *this;}inline CInBuff<N>& operator>>(int& val) {FileToBuf();int x(0), f(0);while (!isdigit(*S))f |= (*S++ == '-');while (isdigit(*S))x = (x << 1) + (x << 3) + (*S++ ^ 48);val = f ? -x : x; S++;//忽略空格换行		return *this;}inline CInBuff& operator>>(long long& val) {FileToBuf();long long x(0); int f(0);while (!isdigit(*S))f |= (*S++ == '-');while (isdigit(*S))x = (x << 1) + (x << 3) + (*S++ ^ 48);val = f ? -x : x; S++;//忽略空格换行return *this;}template<class T1, class T2>inline CInBuff& operator>>(pair<T1, T2>& val) {*this >> val.first >> val.second;return *this;}template<class T1, class T2, class T3>inline CInBuff& operator>>(tuple<T1, T2, T3>& val) {*this >> get<0>(val) >> get<1>(val) >> get<2>(val);return *this;}template<class T1, class T2, class T3, class T4>inline CInBuff& operator>>(tuple<T1, T2, T3, T4>& val) {*this >> get<0>(val) >> get<1>(val) >> get<2>(val) >> get<3>(val);return *this;}template<class T = int>inline CInBuff& operator>>(vector<T>& val) {int n;*this >> n;val.resize(n);for (int i = 0; i < n; i++) {*this >> val[i];}return *this;}template<class T = int>vector<T> Read(int n) {vector<T> ret(n);for (int i = 0; i < n; i++) {*this >> ret[i];}return ret;}template<class T = int>vector<T> Read() {vector<T> ret;*this >> ret;return ret;}
private:inline void FileToBuf() {const int canRead = m_iWritePos - (S - buffer);if (canRead >= 100) { return; }if (m_bFinish) { return; }for (int i = 0; i < canRead; i++){buffer[i] = S[i];//memcpy出错			}m_iWritePos = canRead;buffer[m_iWritePos] = 0;S = buffer;int readCnt = fread(buffer + m_iWritePos, 1, N - m_iWritePos, stdin);if (readCnt <= 0) { m_bFinish = true; return; }m_iWritePos += readCnt;buffer[m_iWritePos] = 0;S = buffer;}int m_iWritePos = 0; bool m_bFinish = false;char buffer[N + 10], * S = buffer;
};template<class T = int, T iDef = INT_MAX / 2>
class CDisNegativeRing //贝尔曼-福特算法
{
public:bool Dis(int N, vector<tuple<int, int, int>> edgeFromToW, int start) {vector<T> pre(N, iDef);pre[start] = 0;for (int t = 0; t < N; t++) {auto cur = pre;for (const auto& [u, v, w] : edgeFromToW) {cur[v] = min(cur[v], pre[u] + w);}if (t + 1 == N) {for (int i = 0; i < N; i++) {if (pre[i] != cur[i]) { return false; }}}pre.swap(cur);}m_vDis = pre;return true;}vector<T> m_vDis;
};class CUnionFind
{
public:CUnionFind(int iSize) :m_vNodeToRegion(iSize){for (int i = 0; i < iSize; i++){m_vNodeToRegion[i] = i;}m_iConnetRegionCount = iSize;}CUnionFind(vector<vector<int>>& vNeiBo) :CUnionFind(vNeiBo.size()){for (int i = 0; i < vNeiBo.size(); i++) {for (const auto& n : vNeiBo[i]) {Union(i, n);}}}int GetConnectRegionIndex(int iNode){int& iConnectNO = m_vNodeToRegion[iNode];if (iNode == iConnectNO){return iNode;}return iConnectNO = GetConnectRegionIndex(iConnectNO);}void Union(int iNode1, int iNode2){const int iConnectNO1 = GetConnectRegionIndex(iNode1);const int iConnectNO2 = GetConnectRegionIndex(iNode2);if (iConnectNO1 == iConnectNO2){return;}m_iConnetRegionCount--;if (iConnectNO1 > iConnectNO2){UnionConnect(iConnectNO1, iConnectNO2);}else{UnionConnect(iConnectNO2, iConnectNO1);}}bool IsConnect(int iNode1, int iNode2){return GetConnectRegionIndex(iNode1) == GetConnectRegionIndex(iNode2);}int GetConnetRegionCount()const{return m_iConnetRegionCount;}vector<int> GetNodeCountOfRegion()//各联通区域的节点数量{const int iNodeSize = m_vNodeToRegion.size();vector<int> vRet(iNodeSize);for (int i = 0; i < iNodeSize; i++){vRet[GetConnectRegionIndex(i)]++;}return vRet;}std::unordered_map<int, vector<int>> GetNodeOfRegion(){std::unordered_map<int, vector<int>> ret;const int iNodeSize = m_vNodeToRegion.size();for (int i = 0; i < iNodeSize; i++){ret[GetConnectRegionIndex(i)].emplace_back(i);}return ret;}
private:void UnionConnect(int iFrom, int iTo){m_vNodeToRegion[iFrom] = iTo;}vector<int> m_vNodeToRegion;//各点所在联通区域的索引,本联通区域任意一点的索引,为了增加可理解性,用最小索引int m_iConnetRegionCount;
};class CMyTopSort
{
public://入度为0的是叶子节点long long TopSort(const int N, const vector<tuple<int, int, int>>& edge, CUnionFind& uf) {m_vDis.assign(N, 1);m_vDis[0] = 1;vector<int> out(N);vector<vector<pair<int, int>>> neiBoBack(N);for (const auto& [u, v, w] : edge) {const int v1 = uf.GetConnectRegionIndex(v);const int u1 = uf.GetConnectRegionIndex(u);if (u1 == v1) { continue; }neiBoBack[v1].emplace_back(u1, -w);out[u1]++;}queue<int> que;for (int i = 1; i < N; i++) {if (0 == out[i]) {if (i != uf.GetConnectRegionIndex(i)) { continue; }que.emplace(i);m_vDis[i] = 1;}}while (que.size()) {const auto cur = que.front();que.pop();for (const auto& [next, w] : neiBoBack[cur]) {m_vDis[next] = max(m_vDis[next], m_vDis[cur] + w);if (0 == --out[next]) {que.emplace(next);}}}long long ans = 0;vector<int> tmp = { 1 };for (int i = 1; i < N; i++) {ans += m_vDis[uf.GetConnectRegionIndex(i)];tmp.emplace_back(m_vDis[uf.GetConnectRegionIndex(i)]);}return ans;}vector<int> m_vDis;
};
class Solution {
public:long long Ans(int N, const vector<tuple<int, int, int>> ope) {vector<tuple<int, int, int>> edge;for (const auto& [x, a, b] : ope) {if (1 == x) {//x=1,即 A-B <=0 B - A <=0edge.emplace_back(a, b, 0);edge.emplace_back(b, a, 0);}else if (2 == x) {//x = 2 A - B < 0 即 A - B <= -1edge.emplace_back(b, a, -1);}else if (3 == x) {//x=3 A-B >=0 即 B-A <=0edge.emplace_back(a, b, 0);}else if (4 == x) {//x=4 A-B >0 即B-A<0 即B-A<=-1edge.emplace_back(a, b, -1);}else if (5 == x) {//x = 5即 A - B <= 0edge.emplace_back(b, a, 0);}}for (int i = 1; i <= N; i++) {edge.emplace_back(0, i, 0);}vector<vector<pair<int, int>>> neiBo(N + 1);for (const auto& [u, v, w] : edge) {neiBo[u].emplace_back(v, w);}CUnionFind uf(N + 1);{vector<bool> vis(N + 1);bool bErr = false;function<void(vector<int>&, vector<int>&, int, int)> DFS = [&](vector<int>& pars, vector<int>& pw, int w, int cur) {if (vis[cur]) { return; }if (1 != pw[cur]) {//找到环if (0 != w - pw[cur]) { bErr = true; return; }for (int i = pars.size() - 1; pars[i] != cur; i--) {uf.Union(cur, pars[i]);}return;}pars.emplace_back(cur);pw[cur] = w;for (const auto& [next, ew] : neiBo[cur]) {DFS(pars, pw, w + ew, next);}pw[cur] = 1;pars.pop_back();vis[cur] = true;};vector<int> pars, pw(N + 1, 1);DFS(pars, pw, 0, 0);if (bErr) { return -1; }}return CMyTopSort().TopSort(N + 1, edge, uf);}
};int main() {
#ifdef _DEBUGfreopen("a.in", "r", stdin);
#endif // DEBUG	ios::sync_with_stdio(0);int n, m ;cin >> n >> m ;	auto ope = Read<tuple<int, int,int>>(m);
#ifdef _DEBUG		//printf("n=%d", n);//Out(ks, "ks=");//Out(ope, ",ope=");//Out(edge2, ",edge2=");/*Out(que, "que=");*/
#endif // DEBUG	auto res = Solution().Ans(n,ope);cout << res;return 0;
}

单元测试

	int n;vector<tuple<int, int, int>> ope;TEST_METHOD(TestMethod1){n = 5, ope = { {1,1,2},{2,3,2},{4,4,1},{3,4,5},{5,4,5},{2,3,5},{4,5,1} };auto res = Solution().Ans(n, ope);AssertEx(11LL, res);}TEST_METHOD(TestMethod2){n = 4, ope = { {1,3,2},{2,2,4},{5,1,3},{3,4,2},{3,2,3},{4,3,1},{5,1,4} };auto res = Solution().Ans(n, ope);AssertEx(8LL, res);}TEST_METHOD(TestMethod3){n = 100000, ope.resize(n-1);for (int i = 1; i < n; i++) {ope[i - 1] = { 2,i,i + 1 };}auto res = Solution().Ans(n, ope);AssertEx(5000050000LL, res);}TEST_METHOD(TestMethod4){n = 700, ope.resize(n - 1);for (int i = 1; i < n; i++) {ope[i - 1] = { 1,i,i + 1 };}auto res = Solution().Ans(n, ope);AssertEx((long long)n, res);}TEST_METHOD(TestMethod5){n = 100000, ope = {};auto res = Solution().Ans(n, ope);AssertEx((long long)n, res);}TEST_METHOD(TestMethod6){n = 4, ope = { {2,1,2},{2,2,3},{2,1,4} };auto res = Solution().Ans(n, ope);AssertEx((long long)8, res);}

扩展阅读

我想对大家说的话
工作中遇到的问题,可以按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。
学习算法:按章节学习《喜缺全书算法册》,大量的题目和测试用例,打包下载。重视操作
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。
如果程序是一条龙,那算法就是他的是睛
失败+反思=成功 成功+反思=成功

视频课程

先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176

测试环境

操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。


http://www.ppmy.cn/ops/166472.html

相关文章

嵌入式八股C语言---面向对象篇

面向对象与面向过程 面向过程 就是把整个业务逻辑分成多个步骤,每步或每一个功能都可以使用一个函数来实现面向对象 对象是类的实例化,此时一个类就内部有属性和相应的方法 封装 在C语言里实现封装就是实现一个结构体,里面包括的成员变量和函数指针,然后在构造函数中,为结构体…

Redis内存淘汰策略

Redis 是一种高性能的键值存储系统&#xff0c;广泛用于缓存、消息队列等场景。由于 Redis 数据存储在内存中&#xff0c;而内存资源有限&#xff0c;因此需要内存淘汰策略来管理内存的使用。Redis 提供了多种内存淘汰策略&#xff0c;可以根据不同的应用场景选择合适的策略。 …

Maven 的核心包

由于前端项目不是核心&#xff0c;阅读 nexus-public 源代码似乎绕远路了。nexus-oss 社区版主要就是集成 maven 的上传包、认证、包解析、包存储这几个核心功能&#xff0c;前端实现重新可以使用新的现代前端工具来提高生产力。故重新疏理一下 maven 的核心机制&#xff0c;即…

TCP/IP原理详细解析

前言 TCP/IP是一种面向连接&#xff0c;可靠的传输&#xff0c;传输数据大小无限制的。通常情况下&#xff0c;系统与系统之间的http连接需要三次握手和四次挥手&#xff0c;这个执行过程会产生等待时间。这方面在日常开发时需要注意一下。 TCP/IP 是互联网的核心协议族&…

R语言零基础系列教程-03-RStudio界面介绍与关键设置

代码、讲义、软件回复【R语言03】获取。 设置位置: 菜单栏 - Tools - Blobal Options 设置 通用设置 设置面板左侧General选项 版本选择: 一般只用一个版本即可 默认工作目录设置: 你希望RStudio打开时是基于哪个目录进行工作可以不设置, 因为脚本一般都是放置在特定项目路…

蓝桥杯刷题——第十五届蓝桥杯大赛软件赛省赛C/C++ 大学 B 组

一、0握手问题 - 蓝桥云课 算法代码&#xff1a; #include <iostream> using namespace std; int main() {int sum0;for(int i49;i>7;i--)sumi;cout<<sum<<endl;return 0; } 直接暴力&#xff0c;题意很清晰&#xff0c;累加即可。 二、0小球反弹 - 蓝…

深圳南柯电子|净水器EMC测试整改:水质安全与电磁兼容性的双赢

在当今注重健康生活的时代&#xff0c;净水器作为家庭用水安全的第一道防线&#xff0c;其性能与安全性备受关注。其中&#xff0c;电磁兼容性&#xff08;EMC&#xff09;测试是净水器产品上市前不可或缺的一环&#xff0c;它直接关系到产品在复杂电磁环境中的稳定运行及不对其…

【备考记录】流水线吞吐率

定义 流水线吞吐率是指单位时间内流水线所完成的指令数量&#xff0c;是衡量流水线性能的重要指标之一。 基本计算公式 吞吐率&#xff08;Throughput&#xff09;&#xff1a; 吞吐率 指令条数 流水线执行时间 吞吐率 \frac{指令条数}{流水线执行时间} 吞吐率流水线执行…