2024.7.11 暑期训练记录(3)

CF

1388D - Captain Flint and Treasure

  • 因为每一个 i i i 都会对 b i b_i bi 产生影响,这种影响比较复杂,所以考虑建图,从 i i i b i b_i bi 连边
  • 不会受到影响的即为入度为 0 的点,从这些点开始拓扑排序
  • 如果当前点 a i a_i ai 大于 0,直接累加进答案,如果小于 0,由于路径上的 a a a 会相互累加,所以不能直接加,将其存入栈中,倒序输出(消除累加性带来的影响)
#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 = 1e5 + 10;
const int maxn = 1e6 + 10;
const int mod = 998244353;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;

struct cmp
{
	int a, b, idx; // 存储的信息
	bool operator> (const cmp &tmp) const
	{
		return b > tmp.b; // 制定自己的排序规则
	}
};

void solve()
{
	int n; cin >> n;
	vector<int> a(n + 1), ind(n + 1);
	for (int i = 1; i <= n; i ++ ) cin >> a[i];
	vector<vector<int>> g(n + 1);
	for (int i = 1; i <= n; i ++ )
	{
		int x; cin >> x;
		if (x != -1)
		{
			g[i].push_back(x);
			ind[x] ++ ;
		}
	}
	queue<int> q;
	stack<int> stk;
	for (int i = 1; i <= n; i ++ )
	{
		if (ind[i] == 0) q.push(i);
	}
	int res = 0;
	vector<int> ans;
	while (q.size())
	{
		auto t = q.front();
		q.pop();

		if (a[t] >= 0)
		{
			res += a[t];
			ans.push_back(t);
		}
		else stk.push(t);

		for (int i = 0; i < g[t].size(); i ++ )
		{
			int j = g[t][i];
			if (a[t] >= 0) a[j] += a[t];
			ind[j] -- ;
			if (ind[j] == 0) q.push(j);
		}
	}
	while (stk.size())
	{
		auto t = stk.top();
		stk.pop();

		res += a[t];
		ans.push_back(t);
	}
	cout << res << '\n';
	for (auto t : ans) cout << t << ' ';
	cout << '\n';
}

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

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

383C - Propagating tree

  • 首先想到需要区间修改单点查询,所以用线段树
  • 要在线段树上处理一棵树,又想到将树转化成dfs序处理
  • 这是没想到的地方:因为对于层数是奇数还是偶数,处理方式不同,所以建两颗线段树分别处理奇偶
#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 = 2e5 + 10;
const int maxn = 1e6 + 10;
const int mod = 998244353;
const int mod1 = 954169327;
const int mod2 = 906097321;
const int INF = 0x3f3f3f3f3f3f3f3f;

struct Node {
	int l, r, sum, add;
} tr[2][N * 4];

// u表示当前树中结点编号 lr表示树中结点左右子结点
void pushup(Node &u, Node &l, Node &r)
{
    /* 此处用[l]和[r]的值更新[u] */
	u.l = l.l, u.r = r.r;
	u.sum = l.sum + r.sum;
}

void pushup(int u)
{
    pushup(tr[0][u], tr[0][u << 1], tr[0][u << 1 | 1]);
    pushup(tr[1][u], tr[1][u << 1], tr[1][u << 1 | 1]);
}

void pushdown(int u)
{
	for (int i = 0; i < 2; i ++ )
	{
		auto &root = tr[i][u], &left = tr[i][u << 1], &right = tr[i][u << 1 | 1];
    	if (root.add) // 当前结点有懒标记 向下传递
    	{
    	    left.add += root.add, left.sum += (i64)(left.r - left.l + 1) * root.add;
    	    right.add += root.add, right.sum += (i64)(right.r - right.l + 1) * root.add;
    	    root.add = 0;
    	}
	}
}

// u表示当前树中结点编号 lr表示区间左右端点
void build(int u, int l, int r)
{
    if (l == r) // 左右端点相同表示到达叶子结点
    {
        tr[0][u] = {l, r}; // 创建该结点
		tr[1][u] = {l, r};
    }
    else
    {
        tr[0][u].l = l, tr[0][u].r = r;
        int mid = l + r >> 1; // 取中间值
        build(u << 1, l, mid), build(u << 1 | 1, mid + 1, r); // 分别构造左右两棵子树
        pushup(u); // 利用pushup更新该点
    }
}

void modify(int idx, int u, int l, int r, int d)
{
    if (tr[idx][u].l >= l && tr[idx][u].r <= r) // 当前树中结点在所求区间之内
    {
        tr[idx][u].sum += (i64)(tr[idx][u].r - tr[idx][u].l + 1) * d; // 更新区间信息
        tr[idx][u].add += d; // 打上懒标记
    }
    else // 当前树中结点不在所求区间之内
    {
        pushdown(u); // 将懒标记向下传递
        int mid = tr[idx][u].l + tr[idx][u].r >> 1;
        if (l <= mid) modify(idx, u << 1, l, r, d); // 与左半段有重合部分就更新左半段
        if (r > mid) modify(idx, u << 1 | 1, l, r, d); // 与右半段有重合部分就更新右半段
        pushup(u); // 由于modify修改了区间结点的信息,所以被修改的结点的祖先结点都需要重算一遍
    }
}

Node query(int idx, int u, int l, int r)
{
    if (tr[idx][u].l >= l && tr[idx][u].r <= r) return tr[idx][u]; // 当前区间在被查询区间之内 直接返回
    else
    {
		pushdown(u);
        int mid = tr[idx][u].l + tr[idx][u].r >> 1; // 取中间值
        if (r <= mid) return query(idx, u << 1, l, r); // 被查询区间在当前区间左半部分
        else if (l > mid) return query(idx, u << 1 | 1, l, r); // 被查询区间在当前区间右半部分
        else // 被查询区间横跨当前区间的左右两部分
        {
            auto left = query(idx, u << 1, l, r); // 计算出左半部分值
            auto right = query(idx, u << 1 | 1, l, r); // 计算出右半部分值
            Node res;
            pushup(res, left, right); // 更新结果
            return res;
        }
    }
}

void solve()
{
	int n, m;
	cin >> n >> m;
	vector<vector<int>> g(n + 1);
	vector<int> a(n + 1), lt(n + 1), rt(n + 1), dep(n + 1);
	for (int i = 1; i <= n; i ++ ) cin >> a[i];
	for (int i = 0; i < n - 1; i ++ )
	{
		int u, v;
		cin >> u >> v;
		g[u].push_back(v);
		g[v].push_back(u);
	}
	build(1, 1, n);
	int timestamp = 0;
	function<void(int, int, int)> dfs = [&](int u, int fa, int d)
	{
		lt[u] = ++ timestamp;
		dep[u] = d;
		for (int i = 0; i < g[u].size(); i ++ )
		{
			int j = g[u][i];
			if (j == fa) continue;
			dfs(j, u, d + 1);
		}
		rt[u] = timestamp;
	};
	dfs(1, -1, 1);
	while (m -- )
	{
		int op, idx, val;
		cin >> op >> idx;
		if (op == 1)
		{
			cin >> val;
			modify(dep[idx] & 1, 1, lt[idx], rt[idx], val);
			modify(!(dep[idx] & 1), 1, lt[idx], rt[idx], -val);
		}
		else
		{
			auto res = query(dep[idx] & 1, 1, lt[idx], lt[idx]);
			cout << res.sum + a[idx] << '\n';
		}
	}
}

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

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

算法

网络流二分图匹配的两道例题,和无源汇上下界可行流的例题

打算把acwing的课看完之后,先把还没有完成的网络流24题刷完,然后随缘看看区域赛啥的了

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Texcavator

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

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

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

打赏作者

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

抵扣说明:

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

余额充值