2024.7.24 暑期训练记录(8)

  • cf到1755了 开心捏

CF

609E - Minimum spanning tree for each edge(最小生成树+lca+倍增 *2100)

  • 首先肯定会想到求最小生成树,记录用到的边
  • 然后遍历所有边,如果当前边在最小生成树中,就直接输出最小生成树的权值,否则使用该边需要断开 u , v u,v u,v 路径的权值最大的边
  • 路径上权值最大的边怎么求呢,路径一定是 lca 到 u 和 lca 到 v,所以求这两条链上的最大值就可以了,可以在倍增的同时更新最大值,这一题就解决了
#include <bits/stdc++.h>

using namespace std;

#define int long long
using i64 = long long;

typedef pair<int, int> PII;
typedef pair<int, char> PIC;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;
typedef pair<int, pair<int, bool>> PIIB;

const int N = 1e6 + 10;
const int maxn = 1e6 + 10;
const int mod = 1e9 + 7;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;

struct node {
	int a, b, w, idx;
};

void solve()
{
	int n, m;
	cin >> n >> m;
	vector<vector<PII>> g(n + 1);
	vector<node> edge(m);
	vector<bool> st(m); // 这条边在不在最小生成树中
	for (int i = 0; i < m; i ++ )
	{
		int a, b, c;
		cin >> a >> b >> c;
		edge[i] = {a, b, c, i};
	}
	auto cmp1 = [&](node a, node b)
	{
		return a.w < b.w;
	};
	sort(edge.begin(), edge.end(), cmp1);

	// 求最小生成树
	int mst = 0;
	vector<int> p(n + 1);
	for (int i = 1; i <= n; i ++ ) p[i] = i;
	function<int(int)> find = [&](int x)
	{
		if (p[x] != x) p[x] = find(p[x]);
		return p[x];
	};
	auto cal_mst = [&]()
	{
		for (int i = 0; i < m; i ++ )
		{
			int a = edge[i].a, b = edge[i].b, w = edge[i].w, id = edge[i].idx;
			int pa = find(a), pb = find(b);
			if (pa != pb)
			{
				mst += w;
				p[pa] = pb;
				st[id] = true;
				g[a].push_back({b, w});
				g[b].push_back({a, w});
			}
		}
	};
	cal_mst();

	// 求uv到lca的最长边
	vector<int> dep(n + 1, INF);
	vector<vector<int>> fa(n + 1, vector<int>(20)), maxx(n + 1, vector<int>(20));
	auto bfs = [&](int root)
	{
		dep[0] = 0, dep[root] = 1;
		queue<int> q;
		q.push(root);
		while (q.size())
		{
			auto t = q.front();
			q.pop();
			for (int i = 0; i < g[t].size(); i ++ )
			{
				int j = g[t][i].first, w = g[t][i].second;
				if (dep[j] > dep[t] + 1)
				{
					dep[j] = dep[t] + 1;
					q.push(j);
					fa[j][0] = t;
					maxx[j][0] = w;
					for (int k = 1; k < 20; k ++ )
					{
						fa[j][k] = fa[fa[j][k - 1]][k - 1];
						maxx[j][k] = max(maxx[fa[j][k - 1]][k - 1], maxx[j][k - 1]);
					}
				}
			}
		}
	};
	auto lca = [&](int a, int b)
	{
		if (dep[a] < dep[b]) swap(a, b);
		int ans = 0;
		for (int k = 19; k >= 0; k -- )
		{
			if (dep[fa[a][k]] >= dep[b])
			{
				ans = max(ans, maxx[a][k]);
				a = fa[a][k];
			}
		}
		if (a == b) return ans;
		for (int k = 19; k >= 0; k -- )
		{
			if (fa[a][k] != fa[b][k])
			{
				ans = max({ans, maxx[a][k], maxx[b][k]});
				a = fa[a][k];
				b = fa[b][k];
			}
		}
		ans = max({ans, maxx[a][0], maxx[b][0]});
		return ans;
	};
	bfs(1);

	// 输出答案
	auto cmp2 = [&](node a, node b)
	{
		return a.idx < b.idx;
	};
	sort(edge.begin(), edge.end(), cmp2);
	for (int i = 0; i < m; i ++ )
	{
		if (st[i]) cout << mst << '\n';
		else
		{
			int a = edge[i].a, b = edge[i].b, w = edge[i].w;
			cout << mst - lca(a, b) + w << '\n';
		}
	}
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	int t = 1;
	// cin >> t;
	while (t--)
	{
		solve();
	}
}

B2 - Bouquet (Hard Version)(思维)

  • 昨晚cf的一题
  • 因为同时选 x x x x + 1 x+1 x+1,可以贪心全部选 x x x,剩下的钱贪心选 x + 1 x+1 x+1,如果还有多的钱就把 x x x 换成 x + 1 x+1 x+1
#include <bits/stdc++.h>

using namespace std;

#define int long long
using i64 = long long;

typedef pair<int, int> PII;
typedef pair<int, char> PIC;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;
typedef pair<int, pair<int, bool>> PIIB;

const int N = 1e6 + 10;
const int maxn = 1e6 + 10;
const int mod = 1e9 + 7;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;

void solve()
{
	int n, m;
	cin >> n >> m;
	vector<int> a(n + 1);
	map<int, int> mp;
	for (int i = 1; i <= n; i ++ ) cin >> a[i];
	for (int i = 1; i <= n; i ++ )
	{
		int x; cin >> x;
		mp[a[i]] = x;
	}
	int ans = 0;
	for (auto t : mp)
	{
		int num1 = t.first, cnt1 = t.second;
		int tmp;
		if (mp.count(num1 + 1) == 0)
		{
			int c = m / num1;
			c = min(c, cnt1);
			tmp = c * num1;
		}
		else
		{
			int num2 = num1 + 1;
			int cnt2 = mp[num2];
			int c1 = m / num1;
			c1 = min(c1, cnt1);
			int have = m - c1 * num1;
			int c2 = have / num2;
			c2 = min(c2, cnt2);
			have = have - c2 * num2;
			cnt2 -= c2;
			int tt = min({cnt2, have, c1});
			c1 -= tt, c2 += tt;
			tmp = c1 * num1 + c2 * num2; 
		}
		ans = max(ans, tmp);
	}
	cout << ans << '\n';
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	int t = 1;
	cin >> t;
	while (t--)
	{
		solve();
	}
}

D - Cases(状压dp)

  • 数据范围给的就很状压dp,但是昨晚一直没想到怎么更新
  • 原问题可以转化成,任意连续的 k k k 个子串中必须有一个终止字符,我们求出所有长度为 k k k 的子串中包含的元素集合,用二进制表示,标记为不合法,那么其余的就一定合法,计算1的个数即可
#include <bits/stdc++.h>

using namespace std;

#define int long long
using i64 = long long;

typedef pair<int, int> PII;
typedef pair<int, char> PIC;
typedef pair<double, double> PDD;
typedef pair<int, PII> PIII;
typedef pair<int, pair<int, bool>> PIIB;

const int N = 1e6 + 10;
const int maxn = 1e6 + 10;
const int mod = 1e9 + 7;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;

void solve()
{
	int n, c, k;
	cin >> n >> c >> k;
	string s; cin >> s;
	s = " " + s;
	vector<int> cnt(26);
	vector<bool> dp((1ll << c) + 1);
	dp[1 << (s[n] - 'A')] = 1;
	int state = 0;
	int r = 0;
	for (int i = 1; i + k - 1 <= n; i ++ )
	{
		if (i != 1)
		{
			cnt[s[i - 1] - 'A'] -- ;
			if (cnt[s[i - 1] - 'A'] == 0) state ^= (1 << (s[i - 1] - 'A'));
		}
		while (r < i + k - 1)
		{
			r ++ ;
			cnt[s[r] - 'A'] ++ ;
			if (cnt[s[r] - 'A'] == 1) state ^= (1 << (s[r] - 'A'));
		}
		dp[state] = true;
	}
	for (int i = 0; i < c; i ++ )
	{
		for (int j = 0; j < (1 << c); j ++ )
		{
			if ((j >> i) & 1) dp[j] = (dp[j] | dp[j ^ (1 << i)]);
		}
	}
	int base = ((1ll << c) - 1);
	int ans = INF;
	for (int i = 0; i < (1 << c); i ++ )
	{
		if (!dp[i]) ans = min(ans, (i64)(__builtin_popcount(base ^ i)));
	}
	cout << ans << '\n';
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0), cout.tie(0);

	int t = 1;
	cin >> t;
	while (t--)
	{
		solve();
	}
}
  • 18
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Texcavator

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值