大三第六周学习笔记

周一

D. Weight the Tree(树形dp+输出方案)

这题妙啊

自己想的时候,发现好点是不相邻的,除非只有两个点的情况

那么求最多的好点显然可以树形dp,比较模板,但是我不知道如何保证总和最小。

在给点赋值上,给坏点赋1,好点赋为度数是最小的,因为对于好点,它最小为它的度数,它取到了最小值,同时坏点取1也取到了最小值。

要保证总和最小,就把它加入到dp状态里面即可,dp同时计算好点数量和总和。

输出方案的话,从根节点往下,根据dp的转移推出会选哪些点。如果当前是好点,那么儿子只能选坏点,如果当前是坏点,就看儿子的dp值哪一个大选哪个。不需要额外的数组记录方案

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
struct node
{
	int cnt, sum;
	node operator + (const node& rhs) const
	{
		return {cnt + rhs.cnt, sum + rhs.sum};
	}
	bool operator > (const node& rhs) const
	{
		if(cnt != rhs.cnt) return cnt > rhs.cnt;
		return sum < rhs.sum;
	}
}dp[N][2];
vector<int> g[N];
int w[N], n;

void dfs(int u, int fa)
{
	dp[u][0] = {0, 1};
	dp[u][1] = {1, g[u].size()};

	for(int v: g[u])
	{
		if(v == fa) continue;
		dfs(v, u);
		dp[u][1] = dp[u][1] + dp[v][0];
		if(dp[v][0] > dp[v][1]) dp[u][0] = dp[u][0] + dp[v][0];
		else dp[u][0] = dp[u][0] + dp[v][1];
	}
}

void build(int u, int fa, int p)
{
	w[u] = p ? g[u].size() : 1;
	for(int v: g[u])
	{
		if(v == fa) continue;
		if(p) build(v, u, 0);
		else
		{
			if(dp[v][0] > dp[v][1]) build(v, u, 0);
			else build(v, u, 1);
		}
	}
}

int main()
{
	scanf("%d", &n);
	_for(i, 1, n - 1)
	{
		int u, v;
		scanf("%d%d", &u, &v);
		g[u].push_back(v);
		g[v].push_back(u);
	}
	if(n == 2)
	{
		puts("2 2\n1 1");
		return 0;
	}
	dfs(1, 0);

	int choose = dp[1][1] > dp[1][0] ? 1 : 0;
	printf("%d %d\n", dp[1][choose].cnt, dp[1][choose].sum);
	build(1, 0, choose);
	_for(i, 1, n) printf("%d ", w[i]); puts("");
	
	return 0;
}

B. Linguistics(复杂贪心)

这题的贪心比较复杂。

首先是一些基本的条件判断,判断个数,再考虑其他

重点如何最优的去分配AB,BA

我们把字符串分割成一段段ABABAB

对于长度为奇数,发现可以匹配x个AB,y个BA,其中x+y = len / 2

对于长度为偶数,如果是A开头,显然优先分配AB,因为先分配BA有浪费字符

同理,B开头优先分配BA

但是还有一点,对于偶数段同种类型的,要先用长度小的再用长度大的。

考虑A开头的匹配BA,比如说BABA,只能匹配一个AB,然后浪费2个。BABABA能匹配2个AB,然后浪费两个

可以发现,一段会浪费2个字符,想要浪费最小,那么显然要段数最少。

怎么做到段数最少呢,显然是把长读小的段用了,剩下大段,这样留下了的段数会比较少,就浪费比较少。

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

void deal(int& c, int& d, int x)
{
	if(c >= x) c -= x;
	else
	{
		x -= c + 1;
		c = 0;
		d -= min(d, x);
	}
}

int main()
{
	int T; scanf("%d", &T);
	while(T--)
	{
		int a, b, c, d;
		scanf("%d%d%d%d", &a, &b, &c, &d);
		string s; cin >> s;
		int len = s.size();
		s = " " + s;

		int cnta = 0, cntb = 0, cntab = 0, cntba = 0;
		_for(i, 1, len)
		{
			if(s[i] == 'A') cnta++;
			if(s[i] == 'B') cntb++;
			if(i < len && s[i] == 'A' && s[i + 1] == 'B') cntab++;
			if(i < len && s[i] == 'B' && s[i + 1] == 'A') cntba++;
 		}
		if(!(cnta == a + c + d && cntb == b + c + d && cntab >= c && cntba >= d))
		{
			puts("NO");
			continue;
		}

		int i = 1, cnt = 0;
		vector<int> va, vb;
		while(i <= len)
		{
			int j = i;
			while(j + 1 <= len && s[j + 1] != s[j]) j++;
			int cur = j - i + 1;
			if(cur > 1)
			{
				if(cur % 2 == 1) cnt += cur / 2;
				else
				{
					if(s[i] == 'A') va.push_back(cur / 2);
					else vb.push_back(cur / 2);
				}
			}
			i = j + 1;
		}

		sort(va.begin(), va.end());
		sort(vb.begin(), vb.end());
		for(int x: va) deal(c, d, x);
		for(int x: vb) deal(d, c, x);
		puts(c + d <= cnt ? "YES" : "NO");
	}
	
	return 0;
}

D. New Year Concert(二分查找+线段树)

对于一个满足要求的区间,新加一个点,以此点为右端点,二分+线段树判断是否存在坏段。

二分查找的写法和二分答案不同,要区分开

#include<bits/stdc++.h>
#define l(k) (k << 1)
#define r(k) (k << 1 | 1)
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
int t[N << 2], a[N], n;

int gcd(int a, int b) { return !b ? a: gcd(b, a % b); }

void up(int k)
{
	t[k] = gcd(t[l(k)], t[r(k)]);
}

void modify(int k, int l, int r, int x, int p)
{
	if(l == r)
	{
		t[k] = p;
		return;
	}
	int m = l + r >> 1;
	if(x <= m) modify(l(k), l, m, x, p);
	else modify(r(k), m + 1, r, x, p);
	up(k);
}

int ask(int k, int l, int r, int L, int R)
{
	if(L <= l && r <= R) return t[k];
	int m = l + r >> 1, res = 0;
	if(L <= m) res = gcd(res, ask(l(k), l, m, L, R));
	if(R > m) res = gcd(res, ask(r(k), m + 1, r, L, R));
	return res;
}

int check(int l, int r)
{
	int t1 = ask(1, 1, n, l, r);
	int t2 = r - l + 1;
	if(t1 > t2) return 1;
	if(t1 == t2) return 0;
	return -1;
}

int main()
{
	scanf("%d", &n);
	_for(i, 1, n)
	{
		scanf("%d", &a[i]);
		modify(1, 1, n, i, a[i]);
	}

	int ans = 0;
	_for(i, 1, n)
	{
		int l = 1, r = i, t = -1;
		while(l <= r)
		{
			int m = l + r >> 1;
			int cur = check(m, i);
			if(cur == 1) r = m - 1;
			else if(cur == -1) l = m + 1;
			else
			{
				t = m;
				break;
			}
		}
		if(t != -1)
		{
			a[i] = 1e9 + 7;
			modify(1, 1, n, i, a[i]);
			ans++;
		}
		printf("%d ", ans);
	}
	puts("");
	
	return 0;
}

周二

D. Shuffle(组合数学)

这题妙啊,我组合数学的题做的很少。

首先对于区间中有k个1,这个条件可以转化。如果整个区间1的个数大于等于k,那么其实每个区间内1个数小于等于k都可以操作,因为更多的部分可以不操作。当然,如果区间1个数小于k,那就一次都不能操作。

然后这道题的难点在于如何不算重,我们可以人为加一些限制使得不同操作中不会重复。

我们规定对当前区间操作时,两端的数字一定改变,这样就操作不同区间时就不会重复

具体来说,假设当前有cnt个1,我们要把这cnt个1分配到(l, r)中,如果端点为1,那么正好分配到区间内部,数字改变。如果端点为0,那么就分配1给端点,cnt-1。

那么此时的贡献就是C(len, cnt),也就是在区间内部len个位置分配cnt个1

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 5e3 + 10;
const int mod = 998244353;
int c[N][N], a[N], s[N], n, k;

int main()
{ 
	_for(i, 0, 5e3) c[i][0] = 1;
	_for(i, 1, 5e3)
		_for(j, 1, i)
			c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
	
	scanf("%d%d", &n, &k);
	_for(i, 1, n)
	{
		scanf("%1d", &a[i]);
		s[i] = s[i - 1] + a[i];
	}
	if(s[n] < k)
	{
		puts("1");
		return 0;
	}

	int ans = 1;
	_for(i, 1, n)
		_for(j, i + 1, n)
		{
			int cnt = s[j] - s[i - 1];
			if(cnt > k) continue;
			cnt -= (a[i] == 0) + (a[j] == 0);
			ans = (ans + c[j - i - 1][cnt]) % mod;
		}
	printf("%d\n", ans);
	
	return 0;
}

B. Fibonacci Strings(贪心)

一开始想的是从小的开始贪心,然后样例都过不了

正解是从大的开始贪心。

 首先可以由求和得出为当前为前i项的和

然后我们现在需要把字母分配到这i项上

方法是把当前最大的分配到最大的项上,注意要和上一次分配的字母不同

为什么呢,对于cmax >= f[i]

f[i] >= f[i - 1] + f[i - 3] ……

这个式子可以分i是奇数和偶数讨论

那么cmax如果不分配给f[i],要分配给后面的数,由可能会分配不完。

所以cmax要分配给f[i]

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 110;
map<ll, int> mp;
int c[N], n;
ll f[N], sum;

int main()
{ 
	sum = 2;
	f[0] = f[1] = 1;
	mp[1] = 0, mp[2] = 1;
	_for(i, 2, 60) 
	{
		f[i] = f[i - 1] + f[i - 2];
		sum += f[i];
		mp[sum] = i;
	}

	int T; scanf("%d", &T);
	while(T--)
	{
		sum = 0;
		scanf("%d", &n);
		_for(i, 1, n) scanf("%d", &c[i]), sum += c[i];

		if(!mp.count(sum))
		{
			puts("NO");
			continue;
		}

		int last = -1, flag = 1;
		for(int p = mp[sum]; p >= 0; p--)
		{
			int mx = 0, id;
			_for(i, 1, n)
				if(i != last && mx < c[i])
				{
					mx = c[i];
					id = i;
				}
			if(mx < f[p])
			{
				flag = 0;
				break;
			}
			c[id] -= f[p];
			last = id;
		}
		puts(flag ? "YES" : "NO");
	}
	
	return 0;
}

周六

M. My University Is Better Than Yours(tarjan)

这题的关键在于发现缩点完后是一条链,这样就很好搞了

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 5e5 + 10;
vector<int> g[N], G[N];
int dfn[N], low[N], st[N], co[N], num[N], in[N], ans[N];
int top, n, m, cnt, id;
set<pair<int, int>> s;

void dfs(int u)
{	
	dfn[u] = low[u] = ++cnt;
	st[++top] = u;

	for(int v: g[u])
	{
		if(!dfn[v])
		{
			dfs(v);
			low[u] = min(low[u], low[v]);
		}
		else if(!co[v]) low[u] = min(low[u], dfn[v]);
	}

	if(low[u] == dfn[u])
	{
		id++;
		while(1)
		{
			co[st[top--]] = id;
			num[id]++;
			if(st[top + 1] == u) break;
		}
	}
}

int main()
{ 
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int pre = 0;
		_for(i, 1, n)
		{
			int x; scanf("%d", &x);
			if(pre) g[pre].push_back(x);
			pre = x;
		}
	}

	_for(i, 1, n)
		if(!dfn[i])
			dfs(i);
	_for(u, 1, n)
		for(int v: g[u])
			if(co[u] != co[v] && !s.count({co[u], co[v]}))
			{
				s.insert({co[u], co[v]});
				G[co[u]].push_back(co[v]);
				in[co[v]]++;
			}

	queue<int> q;
	_for(i, 1, id)
		if(!in[i])
			q.push(i);
	vector<int> topo;
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		topo.push_back(u);
		for(int v: G[u])
			if(--in[v] == 0)
				q.push(v);
	}

	int sum = 0;
	for(int i = topo.size() - 1; i >= 0; i--)
	{
		int cur = topo[i];
		sum += num[cur];
		ans[cur] = sum - 1;
	}

	_for(i, 1, n)
		printf("%d ", ans[co[i]]);
	puts("");
	
	return 0;
}

周日

E - Multigate (二进制)

这题的关键在于发现最优的操作就是将最后k个 与变成或就是最优的

这样的话就维护前缀和后缀,预处理出来一个数组即可

还是要多练思维

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 2e5 + 10;
int a[N], b[N], k[N], n, q;
int ans[35][2][N];

int main()
{ 
	scanf("%d%d", &n, &q);
	_for(i, 1, n) scanf("%d", &a[i]);
	_for(i, 1, n) scanf("%d", &b[i]);

	for(int j = 30; j >= 0; j--)	
		_for(st, 0, 1)
		{
			k[0] = st;
			_for(i, 1, n)
			{
				int cur = (a[i] >> j) & 1;
				if(b[i] == 1) k[i] = k[i - 1] | cur;
				else k[i] = k[i - 1] & cur;
			}

			int cur = 0, cost = 0;
			for(int i = n; i >= 1; i--)
			{
				if(b[i] == 0)
				{
					ans[j][st][cost] = k[i] | cur;
					cost++;
				}
				cur |= (a[i] >> j) & 1; 
			}
			ans[j][st][cost] = st | cur;
		}
	
	int num = 0;
	_for(i, 1, n)
		if(b[i] == 0)
			num++;
	
	while(q--)
	{
		int x, k;
		scanf("%d%d", &x, &k);
		k = min(k, num);

		int sum = 0;
		_for(j, 0, 30)
		{
			int cur = (x >> j) & 1;
			if(ans[j][cur][k]) sum += 1 << j;
		}
		printf("%d\n", sum);
	}

	return 0;
}

B - Potion(easy version)(思维)

这题的关键在于用分数表示,用分数表示可以发现每次增加的数是2的次幂,根据这点可以发现数的二进制特征

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;

ll gcd(ll a, ll b) { return !b ? a : gcd(b, a % b); }

int main()
{ 
	int T; scanf("%d", &T);
	while(T--)
	{
		ll x, y, a, b;
		scanf("%lld%lld%lld%lld", &x, &y, &a, &b);
		ll d = gcd(x, y);
		x /= d; y /= d;

		if(x % 2 == 0 || y % 2 == 0)
		{
			puts("-1");
			continue;
		}

		int mx = 0;
		for(; (1LL << mx) <= x || (1LL << mx) <= y; mx++);
		if((x ^ y) + 2 == 1LL << mx) printf("%d\n", mx + 1);
		else puts("-1");
	}
	
	return 0;
}

P5960 【模板】差分约束算法

差分约束就是n个变量,m个不等式,然后求变量的值或变量的差的最值

像xi - xj <= ak

即xi <= xj + ak

这个式子非常像最短路里面的dv <= du + w

所以我们就从j到i连一条ak的边,跑最短路,就可以得到两个变量xi - xj <= 某个数,即可以求xi - xj的最大值

如果是>=那么就求最长度,可以求变量差的最小值

注意不等式的方向可以根据要求最大值或最小值来确定,方向不对的两边同乘-1即可

因为有负权边,用spfa求,可以用SLF优化

SLF优化非常像dijsktra的思想,即最短路短的先更新,对于SLF优化,要用双端队列,要加入队列的点v的最短路小于队首的点的最短路时,就把v加到队首,否则照常加到队尾。挺明显的。

如果跑最短路有负环或最长路有正环的就无解,判断的方法是一个点入队的次数超过n。

这题的目标不是求变量差值,而是求每个变量的值,那么我们加入一个超级源点,x0向每个点连一条边权为0的边,这样跑出来di就是xi可能的值

这样子相当于加入了xi - x0 <= 0

求出了xi - x0 <= t 即xi <= x0 + t,令x0=0,得xi <= t

所以是求出了xi<=0的最大的解。

还有一点是,将这些变量同时加减一个数也是解,这是显然的,因为所有的限制都是不等式,都是相对关系,绝对数量是不影响的。

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 5e3 + 10;
vector<pair<int, int>> g[N];
int d[N], cnt[N], vis[N], n, m;

bool spfa()
{
	deque<int> q;
	_for(i, 1, n) d[i] = 1e9;
	q.push_back(0);

	while(!q.empty())
	{
		int u = q.front(); q.pop_front();
		vis[u] = 0;
		for(auto [v, w]: g[u])
		{
			if(d[v] > d[u] + w)
			{
				d[v] = d[u] + w;
				if(!vis[v]) 
				{
					if(++cnt[v] > n) return false;
					if(!q.empty() && d[v] < d[q.front()]) q.push_front(v); //SLF优化
					else q.push_back(v);   
					vis[v] = 1;
				}
			}
		}
	}
	return true;
}

int main()
{ 
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		g[v].push_back({u, w});
	}
	_for(i, 1, n) g[0].push_back({i, 0});
	
	if(!spfa()) puts("NO");
	else
	{
		_for(i, 1, n) printf("%d ", d[i]);
		puts("");
	}
	
	return 0;
}

P4878 [USACO05DEC]Layout G(差分约束)

注意一些细节

隐含的xi+1 >= xi

对于负环,要用超级源点来判断,从1开始判断不一定联通,也就是说要跑两次spfa

跑两次spfa注意初始化

其他基本是模板了

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e3 + 10;
vector<pair<int, int>> g[N];
int d[N], cnt[N], vis[N], n, m1, m2;

bool spfa(int u)
{
	deque<int> q;
	_for(i, 1, n) d[i] = 1e9, vis[i] = 0, cnt[i] = 0;
	d[u] = 0;
	q.push_back(u);
	vis[u] = 1;

	while(!q.empty())
	{
		int u = q.front(); q.pop_front();
		vis[u] = 0;
		for(auto [v, w]: g[u])
			if(d[v] > d[u] + w)
			{
				d[v] = d[u] + w;
				if(!vis[v])
				{
					if(++cnt[v] > n) return false;
					if(!q.empty() && d[v] < d[q.front()]) q.push_front(v);
					else q.push_back(v);
					vis[v] = 1;
				}
			}
	}
	return true;
}

int main()
{ 
	scanf("%d%d%d", &n, &m1, &m2);
	while(m1--)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		g[u].push_back({v, w});
	}
	while(m2--)
	{
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		g[v].push_back({u, -w});
	}
	_for(i, 1, n - 1) g[i + 1].push_back({i, 0});

	_for(i, 1, n) g[0].push_back({i, 0});
	if(!spfa(0)) puts("-1");
	else
	{
		spfa(1);
		printf("%d\n", d[n] == 1e9 ? -2 : d[n]);
	}

	return 0;
}

Integer Intervals(差分约束)

用前缀和表示即可

因为会有负下标,所以都加1

从0开始跑,求出的是smx - s0 >= t

smx - s0正好就是有多少个数

t即是答案

最大值我开始取1e4+1,然后T了

应该输入的时候取最大值

#include<cstdio>
#include<queue>
#include<vector>
#include<utility>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 1e4 + 10;
vector<pair<int, int>> g[N];
int d[N], vis[N], m, mx;

void spfa(int u)
{
	queue<int> q;
	_for(i, 1, mx) d[i] = -1e9;
	d[u] = 0;
	q.push(u);
	vis[u] = 1;

	while(!q.empty())
	{
		int u = q.front(); q.pop();
		vis[u] = 0;
		rep(i, 0, g[u].size())
		{
			int v = g[u][i].first, w = g[u][i].second;
			if(d[v] < d[u] + w)
			{
				d[v] = d[u] + w;
				if(!vis[v])
				{
					q.push(v);
					vis[v] = 1;
				}
			}
		}
	}
}

int main()
{ 
	scanf("%d", &m);
	while(m--)
	{
		int a, b;
		scanf("%d%d", &a, &b);
		a++; b++;
		mx = max(mx, b);
		g[a - 1].push_back(make_pair(b, 2));
	}
	_for(i, 1, 1e4 + 1)
	{
		g[i - 1].push_back(make_pair(i, 0));
		g[i].push_back(make_pair(i - 1, -1));
	}

	spfa(0);
	printf("%d\n", d[mx]);
	
	return 0;
}

P3275 [SCOI2011]糖果(差分约束/缩点+dp)

这题可以用差分约束做,求的是每个变量的值,不是差值,所以建立一个超级源点。因为题目说至少一个糖果,所以超级源点向每个源点连长度为1的边,这样求出的就是每个变量大于等于1的最小解,符合题目要求。

注意统计答案时开long long,我写时感觉不会爆int,结果还是爆了。不是很确定不会爆int的时候要开long long

然后要用SLF优化。

但是最后有两个hack数据还是T了,应该是spfa还是被卡了

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
vector<pair<int, int>> g[N];
int d[N], vis[N], cnt[N], n, m;

bool spfa(int u)
{
	deque<int> q;
	_for(i, 1, n) d[i] = -1e9;
	d[u] = 0;
	q.push_back(u);
	vis[u] = 1;

	while(!q.empty())
	{
		int u = q.front(); q.pop_front();
		vis[u] = 0;
		for(auto [v, w]: g[u])
			if(d[v] < d[u] + w)
			{
				d[v] = d[u] + w;
				if(!vis[v])
				{
					if(++cnt[v] > n + 1) return false;
					if(!q.empty() && d[v] > d[q.front()]) q.push_front(v); //用SLF优化保险
					else q.push_back(v);
					vis[v] = 1;
				}
			}
	}
	return true;
}

int main()
{ 
	scanf("%d%d", &n, &m);
	while(m--)
	{
		int op, a, b;
		scanf("%d%d%d", &op, &a, &b);
		if(op == 1)
		{
			g[a].push_back({b, 0});
			g[b].push_back({a, 0});
		}
		else if(op == 2)
			g[a].push_back({b, 1});
		else if(op == 3)
			g[b].push_back({a, 0});
		else if(op == 4)
			g[b].push_back({a, 1});
		else	
			g[a].push_back({b, 0});
	}
	_for(i, 1, n) g[0].push_back({i, 1});

	if(!spfa(0)) puts("-1");
	else
	{
		ll ans = 0;                 //不要忘了开long long 不确定的时候还是开为好
		_for(i, 1, n)  ans += d[i];
		printf("%lld\n", ans);
	}

	return 0;
}

缩点的来源在于有边权为0的双向边,这样就相当于一个点,所以可以先把边权为0的边加入,然后进行缩点。然后进行拓扑排序,如果有环就是无解。然后要求最长路,注意最长路不是最短路,因为每条边都要满足

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;
vector<pair<int, int>> g[N], G[N];
int x[N], a[N], b[N], in[N], dp[N], n, m;
int dfn[N], low[N], st[N], co[N], num[N], top, cnt, id;
set<pair<int, int>> s;

void dfs(int u)
{
	dfn[u] = low[u] = ++cnt;
	st[++top] = u;

	for(auto [v, w]: g[u])
	{
		if(!dfn[v])
		{
			dfs(v);
			low[u] = min(low[u], low[v]);
		}
		else if(!co[v]) low[u] = min(low[u], dfn[v]);
	}

	if(low[u] == dfn[u])
	{
		id++;
		while(1)
		{
			co[st[top--]] = id;
			num[id]++;
			if(st[top + 1] == u) break;
		}
	}
}

int main()
{ 
	scanf("%d%d", &n, &m);
	_for(i, 1, m) scanf("%d%d%d", &x[i], &a[i], &b[i]);

	_for(i, 1, m)
	{
		if(x[i] == 1)
		{
			g[a[i]].push_back({b[i], 0});
			g[b[i]].push_back({a[i], 0});
		}
		else if(x[i] == 3)
			g[b[i]].push_back({a[i], 0});
		else if(x[i] == 5)
			g[a[i]].push_back({b[i], 0});
	}

	_for(i, 1, n)
		if(!dfn[i])
			dfs(i);
	_for(u, 1, n)
		for(auto [v, w]: g[u])
			if(co[u] != co[v] && !s.count({co[u], co[v]}))
			{
				G[co[u]].push_back({co[v], w});
				s.insert({co[u], co[v]});
				in[co[v]]++;
			}
	
	_for(i, 1, m)
	{
		int u = co[a[i]], v = co[b[i]];
		if(x[i] == 2) 
		{
			G[u].push_back({v, 1});
			in[v]++;
		}
		else if(x[i] == 4)
		{
			G[v].push_back({u, 1});
			in[u]++;
		}
	}

	cnt = 0;
	queue<int> q;
	vector<int> topo;
	_for(i, 1, id) dp[i] = -1e9;
	_for(i, 1, id)
		if(!in[i])
		{
			q.push(i);
			dp[i] = 1;
		}
	while(!q.empty())
	{
		int u = q.front(); q.pop();
		cnt++;
		for(auto [v, w]: G[u])
		{
			dp[v] = max(dp[v], dp[u] + w);
			if(--in[v] == 0) q.push(v);
		}
	}

	if(cnt != id) puts("-1");
	else
	{
		ll ans = 0;
		_for(i, 1, id)
			ans += 1LL * num[i] * dp[i];
		printf("%lld\n", ans);
	}
		
	return 0;
}

P2294 [HNOI2005]狡猾的商人(差分约束)

比较裸的差分约束

要判断负环的话要建一个超级源点保证联通

等式转化成两个不等式即可

#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;

const int N = 110;
vector<pair<int, int>> g[N];
int d[N], cnt[N], vis[N], n, m;

bool spfa()
{
	deque<int> q;
	_for(i, 1, n + 1) cnt[i] = 0, d[i] = 1e9, vis[i] = 0;
	q.push_back(0);
	vis[0] = 1;

	while(!q.empty())
	{
		int u = q.front(); q.pop_front();
		vis[u] = 0;
		for(auto [v, w]: g[u])
			if(d[v] > d[u] + w)
			{
				d[v] = d[u] + w;
				if(!vis[v])
				{
					if(++cnt[v] > n + 1) return false;
					if(!q.empty() && d[v] < d[q.front()]) q.push_front(v);
					else q.push_back(v);
					vis[v] = 1;
				}
			}
	}
	return true;
}

int main()
{ 
	int T; scanf("%d", &T);
	while(T--)
	{
		scanf("%d%d", &n, &m);
		_for(i, 0, n + 1) g[i].clear();
		while(m--)
		{
			int l, r, w;
			scanf("%d%d%d", &l, &r, &w);
			l++; r++;
			g[l - 1].push_back({r, w});
			g[r].push_back({l - 1, -w});
		}
		_for(i, 1, n + 1) g[0].push_back({1, 0});
		puts(spfa() ? "true" : "false");
	}
		
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值