zoukankan      html  css  js  c++  java
  • 「Luogu P3275」「SCOI2011」糖果

    Description

    Hint

    (1le N, Kle 10^5)

    Soluton 1

    构建 差分约束系统

    约定,(u ightarrow v) 的一条边权为 (w) 的有向边表示 ( ext{val}_v > ext{val}_u + w)(即 (v)(u) 至少大 (w))。

    那么对于五种情况,我们可以这样建图(( ext{connect}(u, v, w)) 表示连一条 (u ightarrow v) 的一条边权为 (w) 的边):

    • (X = 1)( ext{connect}(u, v, 0), ext{connect}(v, u, 0)),表示两点的值大小相同。
    • (X = 2)( ext{connect}(u, v, 1)),表示 (v)(u) 至少大 (1)
    • (X = 3)( ext{connect}(v, u, 0)),表示 (u) 大于等于 (v)
    • (X = 4)( ext{connect}(v, u, 1)),表示 (u)(v) 至少大 (1)
    • (X = 5)( ext{connect}(u, v, 0)),表示 (v) 大于等于 (u)

    最后以 (n + 1) 为超级源点,分别向结点 (1cdots n) 建一条权为 (0) 边,整个图就建好了。

    接下来,从 (n + 1) 开始,用 ( exttt{Spfa})最长路(注意特判正环)。

    为什么不是最短路?因为我们必须确保满足最多的约束条件才行。

    • 注意点:
      • (X in {2,4}) 时,如果 (u = v),那么直接输出 (-`)
      • 不开 long long 见祖宗。

    时间复杂度最坏 (O(nm)),但是由于数据水所以过了。

    Code for solution 1

    /*
     * Author : _Wallace_
     * Source : https://www.cnblogs.com/-Wallace-/
     * Problem : Luogu P3275 SCOI2011 糖果
     */
    #include <cstdio>
    #include <cstring>
    #include <queue>
    #include <vector>
    
    using namespace std;
    const int N = 1e5 + 5;
    
    struct edge {
    	int p, w;
    };
    vector<edge> G[N];
    int n, m;
    inline void connect(int u, int v, int w) {
    	G[u].push_back(edge{v, w});
    }
    
    queue<int> Q;
    int cnt[N], dis[N];
    bool inque[N];
    long long spfa(int s) {
    	memset(cnt, 0, sizeof cnt);
    	memset(inque, 0, sizeof inque);
    	memset(dis, 0, sizeof dis);
    	
    	inque[s] = true, Q.push(s);
    	while (!Q.empty()) {
    		int u = Q.front(); Q.pop();
    		inque[u] = false;
    		if (cnt[u]++ == n) return -1ll;
    		for (auto v : G[u]) if (dis[v.p] < dis[u] + v.w) {
    			dis[v.p] = dis[u] + v.w;
    			if (!inque[v.p]) inque[v.p] = true, Q.push(v.p);
    		}
    	}
    	long long ans = 0ll;
    	for (register int i = 1; i <= n; i++)
    		ans += dis[i];
    	return ans;
    }
    
    signed main() {
    	scanf("%d%d", &n, &m);
    	for (register int i = 1; i <= m; i++) {
    		int k, u, v;
    		scanf("%d%d%d", &k, &u, &v);
    		switch (k) {
    			case 1 : connect(u, v, 0), connect(v, u, 0); break;
    			case 2 : if (u == v) return puts("-1"), 0; connect(u, v, 1); break;
    			case 3 : connect(v, u, 0); break;
    			case 4 : if (u == v) return puts("-1"), 0; connect(v, u, 1); break;
    			case 5 : connect(u, v, 0); break;
    		}
    	}
    	for (register int i = 1; i <= n; i++)
    		connect(n + 1, i, 1);
    	
    	printf("%lld
    ", spfa(n + 1));
    }
    

    Solution 2

    上面的算法虽然简单但是不稳定,可能会被卡。这里介绍一种稳定而且更优秀的算法。

    首先,对于所有 (Xin {1, 3, 5}) 的约束条件(因为含有容许相等的关系,可以缩点),我们先连边:如果 (Ale B),则连上一条 (A ightarrow B) 的边,并标记为边权为 0(就是一个记号)。

    然后可以 Tarjan 缩点一下,并把缩点后的新图(( ext{Gx}))构造好。

    然后再找出所有 (Xin {2,4}) 的约束条件,如果发现在新图上产生了自环(即 ( ext{belong}_A = ext{belong}_B)),那么显然有矛盾出现,输出 (-1) 跑路。如果没有自环,那就在 ( ext{Gx}) 上连边:如果 (A<B),那就连边 (A ightarrow B),边权为 (1)。注意这里的 (A,B) 都是 缩点后 的位置。

    然后就可以直接dp了。然后有两种写法:

    1. 使用拓扑排序,做的时侯顺便 dp:如果边权为 (0),那么在取 (max) 的时候需要将前驱结点的 dp 值 (+1);反之则不用 (+1)。至于为什么是取 (max),这个和上面最长路的解释差不多。如果最后做下来发现了环,那就输出 (-1)

    2. 建反边,Dfs 判环,记忆化搜索:将 ( ext{Gx}) 的边都反着建,然后就可以记搜了。注意做之前需要判断是不是 DAG,这个可以 Dfs 实现。

    算法时间复杂度:(O(n + m)),但由于数据过水以及常数因子的影响优势不大。

    Code for solution 2

    拓扑排序写法

    /*
     * Author : _Wallace_
     * Source : https://www.cnblogs.com/-Wallace-/
     * Problem : Luogu P3275 SCOI2011 糖果
     */
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <vector>
    #include <queue>
    #include <stack>
    
    using namespace std;
    const int N = 1e5 + 5;
    
    struct edge {
    	int p, w;
    };
    vector<edge> G[N], Gx[N];
    int n, m;
    int X[N], A[N], B[N];
    
    inline void connect1(int u, int v, int w) {
    	G[u].push_back(edge{v, w});
    }
    inline void connect2(int u, int v, int w) {
    	Gx[u].push_back(edge{v, w});
    }
    
    int dfn[N], low[N];
    int belong[N], size[N], scc = 0;
    int timer = 0;
    stack<int> st;
    bool inst[N];
    void Tarjan(int x) {
    	dfn[x] = low[x] = ++timer;
    	st.push(x), inst[x] = true;
    	for (auto y : G[x])
    		if (!dfn[y.p]) Tarjan(y.p), low[x] = min(low[x], low[y.p]);
    		else if (inst[y.p]) low[x] = min(low[x], dfn[y.p]);
    	
    	if (low[x] == dfn[x]) for (++scc; ; ) {
    		int k = st.top(); st.pop();
    		belong[k] = scc, size[scc]++, inst[k] = false;
    		if (k == x) break;
    	}
    }
    
    int num[N], in[N];
    bool del[N];
    queue<int> que;
    void calc() {
    	for (register int i = 1; i <= scc; i++)
    		for (auto j : Gx[i]) in[j.p]++;
    	for (register int i = 1; i <= scc; i++)
    		if (!in[i]) que.push(i), del[i] = true, num[i] = 1;
    	while (!que.empty()) {
    		int u = que.front(); que.pop();
    		for (auto v : Gx[u]) {
    			if (!--in[v.p])
    				que.push(v.p), del[v.p] = 1;
    			if (!v.w) num[v.p] = max(num[v.p], num[u] + 1);
    			else num[v.p] = max(num[v.p], num[u]);
    		}
    	}
    	for (register int i = 1; i <= scc; i++)
    		if (!del[i]) puts("-1"), exit(0);
    }
    
    signed main() {
    	scanf("%d%d", &n, &m);
    	for (register int i = 1; i <= m; i++)
    		scanf("%d%d%d", X + i, A + i, B + i);
    	/*u -(0/1)-> v : u <(=) v*/
    	for (register int i = 1; i <= n; i++)
    		switch (X[i]) {
    			case 1 : connect1(A[i], B[i], 1), connect1(B[i], A[i], 1); break;
    			case 3 : connect1(B[i], A[i], 1); break;
    			case 5 : connect1(A[i], B[i], 1); break;
    		}
    	for (register int i = 1; i <= n; i++)
    		if (!dfn[i]) Tarjan(i);
    	
    	for (register int i = 1; i <= n; i++)
    		for (auto j : G[i]) if (belong[i] != belong[j.p])
    			connect2(belong[i], belong[j.p], 1);
    	
    	for (register int i = 1; i <= m; i++)		
    		if (X[i] == 2) {
    			if (belong[A[i]] == belong[B[i]])
    				return puts("-1"), 0;
    			connect2(belong[A[i]], belong[B[i]], 0);
    		} else if (X[i] == 4) {
    			if (belong[A[i]] == belong[B[i]])
    				return puts("-1"), 0;
    			connect2(belong[B[i]], belong[A[i]], 0);
    		}
    	
    	calc();
    	
    	long long ans = 0ll;
    	for (register int i = 1; i <= scc; i++)
    		ans += num[i] * 1LL * size[i];
    	printf("%lld", ans);
    	return 0;
    } 
    

    记忆化搜索写法

    /*
     * Author : _Wallace_
     * Source : https://www.cnblogs.com/-Wallace-/
     * Problem : Luogu P3275 SCOI2011 糖果
     */
    #include <cstdio>
    #include <cstdlib>
    #include <cstring>
    #include <vector>
    #include <stack>
    
    using namespace std;
    const int N = 1e5 + 5;
    
    struct edge {
    	int p, w;
    };
    vector<edge> G[N], Gx[N];
    int n, m;
    int X[N], A[N], B[N];
    
    inline void connect1(int u, int v, int w) {
    	G[u].push_back(edge{v, w});
    }
    inline void connect2(int u, int v, int w) {
    	Gx[v].push_back(edge{u, w});
    }
    
    int dfn[N], low[N];
    int belong[N], size[N], scc = 0;
    int timer = 0;
    stack<int> st;
    bool inst[N];
    void Tarjan(int x) {
    	dfn[x] = low[x] = ++timer;
    	st.push(x), inst[x] = true;
    	for (auto y : G[x])
    		if (!dfn[y.p]) Tarjan(y.p), low[x] = min(low[x], low[y.p]);
    		else if (inst[y.p]) low[x] = min(low[x], dfn[y.p]);
    	
    	if (low[x] == dfn[x]) for (++scc; ; ) {
    		int k = st.top(); st.pop();
    		belong[k] = scc, size[scc]++, inst[k] = false;
    		if (k == x) break;
    	}
    }
    
    int vis[N];
    void judgeRing(int x) {
    	vis[x] = 1;
    	for (auto y : Gx[x]) {
    		if (vis[y.p] == 1) puts("-1"), exit(0);
    		if (vis[y.p] != -1) judgeRing(y.p);
    	}
    	vis[x] = -1;
    }
    
    int num[N];
    int calc(int x) {
    	if (num[x]) return num[x];
    	if (Gx[x].empty()) return num[x] = 1;
    	for (auto y : Gx[x])
    		if (!y.w) num[x] = max(num[x], calc(y.p) + 1);
    		else num[x] = max(num[x], calc(y.p));
    	return num[x];
    }
    
    signed main() {
    	scanf("%d%d", &n, &m);
    	for (register int i = 1; i <= m; i++)
    		scanf("%d%d%d", X + i, A + i, B + i);
    	/*u -(0/1)-> v : u <(=) v*/
    	for (register int i = 1; i <= n; i++)
    		switch (X[i]) {
    			case 1 : connect1(A[i], B[i], 1), connect1(B[i], A[i], 1); break;
    			case 3 : connect1(B[i], A[i], 1); break;
    			case 5 : connect1(A[i], B[i], 1); break;
    		}
    	for (register int i = 1; i <= n; i++)
    		if (!dfn[i]) Tarjan(i);
    	
    	for (register int i = 1; i <= n; i++)
    		for (auto j : G[i]) if (belong[i] != belong[j.p])
    			connect2(belong[i], belong[j.p], 1);
    	
    	for (register int i = 1; i <= m; i++)		
    		if (X[i] == 2) {
    			if (belong[A[i]] == belong[B[i]])
    				return puts("-1"), 0;
    			connect2(belong[A[i]], belong[B[i]], 0);
    		} else if (X[i] == 4) {
    			if (belong[A[i]] == belong[B[i]])
    				return puts("-1"), 0;
    			connect2(belong[B[i]], belong[A[i]], 0);
    		}
    	
    	for (register int i = 1; i <= scc; i++)
    		if (~vis[i]) judgeRing(i);
    	for (register int i = 1; i <= scc; i++)
    		calc(i);
    	
    	long long ans = 0ll;
    	for (register int i = 1; i <= scc; i++)
    		ans += num[i] * 1LL * size[i];
    	printf("%lld", ans);
    	return 0;
    } 
    
  • 相关阅读:
    32位和64位系统区别及int字节数
    C++默认参数不能是一个引用
    sprintf的缓冲区溢出
    linux之cp/scp命令+scp命令详解
    linux文件属性详细说明
    linux tar打包
    sed命令
    常用linux命令
    C++ 类T T t;构造时分配的内存在静态数据区 T t=new T()分配的内存在堆 这样说对吗
    Dom事件的三种绑定方式
  • 原文地址:https://www.cnblogs.com/-Wallace-/p/12831271.html
Copyright © 2011-2022 走看看