2024“钉耙编程”中国大学生算法设计超级联赛(3)

2024“钉耙编程”中国大学生算法设计超级联赛(3)

深度自同构

HDU - 7457

思路

不太会推,赛时队友出的,找到的规律就是 \(f_i\) 等于 \(i\) 的所有因子数的 \(f_d\)

先考虑 \(n\) 个点的合法的树的个数,容易发现根据要求每个节点的所有 子树的形态必定完全相同。因此可以递推,令 \(f_i\) 表示 \(i\) 个点的合法的树的 个数,枚举根的儿子个数,有 \(f_i=\sum\limits_{d|(i-1)}f_d\)。这一转移可以用枚举倍数的 方法加速,复杂度为调和级数。

再考虑合法的森林个数,注意到森林中每棵树必定完全相同,因此 \(ans_i = ∑\limits_{ d|i} f_d\),再用调和级数的复杂度算一遍即可,复杂度 \(O(n log n)\)

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    const int mod = 998244353;

    n ++;
    vector<int> f(n + 1);
    f[1] = 1;
    for (int i = 1; i <= n; i ++) {
        for (int j = i + 1; j <= n; j += i) {
            (f[j] += f[i]) %= mod;
        }
    }

    for (int i = 2; i <= n; i ++)
        cout << f[i] << " \n"[i == n];


    return 0;
}

单峰数列

HDU - 7463

思路

难调的线段树。。

只需要维护区间最大值,最小值,以及升降序即可,判断相同用看区间最大与最小是否相同即可,判断单峰需要二分找到区间最大值的位置,从这个位置判断左边是否升,右边是否降即可。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

template<class Node>
struct SegmentTree {
#define lc u<<1
#define rc u<<1|1
	const int n, N;
	vector<Node> tr;

	SegmentTree(): n(0) {}
	SegmentTree(int n_): n(n_), N(n * 4 + 10) {
		tr.reserve(N);
		tr.resize(N);
	}
	SegmentTree(vector<int> init) : SegmentTree(init.size()) {
		function<void(int, int, int)> build = [&](int u, int l, int r) {
			tr[u].l = l, tr[u].r = r;
			init_lazy(tr[u]);
			if (l == r) {
				tr[u] = {l, r, 0, init[l], init[l], 1, 1};
				return ;
			}
			i64 mid = (l + r) >> 1;
			build(lc, l, mid);
			build(rc, mid + 1, r);
			pushup(tr[u], tr[lc], tr[rc]);
		};
		build(1, 1, n);
	}

	void cal_lazy(Node & fa, Node & ch) {
		i64 b = fa.add;
		ch.Max += b;
		ch.Min += b;
	}

	void tag_union(Node& fa, Node& ch) {
		i64 b = fa.add;
		ch.add += b;
	}

	void init_lazy(Node& u) {
		u.add = 0;
	}

	void pushdown(i64 u) {
		if (tr[u].add != 0) {
			cal_lazy(tr[u], tr[lc]);
			cal_lazy(tr[u], tr[rc]);
			tag_union(tr[u], tr[lc]);
			tag_union(tr[u], tr[rc]);
			init_lazy(tr[u]);
		}
	}

	void pushup(Node& U, Node& L, Node& R) { //上传
		U.Max = max(L.Max, R.Max);
		U.Min = min(L.Min, R.Min);

		if (L.Max < R.Min && L.up && R.up) {
			U.up = 1;
		} else {
			U.up = 0;
		}

		if (L.Min > R.Max && L.down && R.down) {
			U.down = 1;
		} else {
			U.down = 0;
		}

	}

	void modify(int u, int l, int r, int k) {
		if (tr[u].l >= l && tr[u].r <= r) {
			tr[u].add += k;
			tr[u].Max += k;
			tr[u].Min += k;
			return ;
		}
		pushdown(u);
		int mid = (tr[u].l + tr[u].r) >> 1;
		if (l <= mid)
			modify(lc, l, r, k);
		if (r > mid)
			modify(rc, l, r, k);
		pushup(tr[u], tr[lc], tr[rc]);
	}

	Node query(int u, int l, int r) { //区查
		if (l <= tr[u].l && tr[u].r <= r)
			return tr[u];
		i64 mid = tr[u].l + tr[u].r >> 1;
		pushdown(u);
		i64 res = LLONG_MIN >> 1;
		if (r <= mid)
			return query(lc, l, r);
		if (l > mid)
			return query(rc, l, r);
		Node U;
		Node L = query(lc, l, r), R = query(rc, l, r);
		pushup(U, L, R);
		return U;
	}
};

struct Node { //线段树定义
	i64 l, r, add;
	i64 Max, Min;
	bool up, down;
};

int main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int n;
	cin >> n;

	vector<int> a(n + 1);
	for (int i = 1; i <= n; i ++)
		cin >> a[i];

	SegmentTree<Node> A(a);

	int m;
	cin >> m;
	while (m--) {
		int op, l, r;
		cin >> op >> l >> r;
		if (op == 1) {
			int x;
			cin >> x;
			A.modify(1, l, r, x);
		} else if (op == 2) {
			auto res = A.query(1, l, r);
			cout << (res.Max == res.Min) << '\n';
		} else if (op == 3) {
			auto res = A.query(1, l, r);
			cout << res.up << '\n';
		} else if (op == 4) {
			auto res = A.query(1, l, r);
			cout << res.down << '\n';
		} else {
			int L = l + 1, R = r - 1, ans = -1;
			auto ma = A.query(1, l + 1, r - 1).Max;
			while (L <= R) {
				int mid = L + R >> 1;
				if (A.query(1, l, mid).Max >= ma)
					R = mid - 1, ans = mid;
				else
					L = mid + 1;
			}

			auto Lans = A.query(1, l, ans), Rans = A.query(1, ans, r);
			if (ans != -1 && Lans.up && Rans.down) {
				cout << 1 << '\n';
			} else {
				cout << 0 << '\n';
			}
		}
	}

	return 0;
}

比特跳跃

HDU - 7464

思路

考虑到 \(x|y\) 的性质,即 \(x|y ≥\max(x,y)\),所以要使得权值最小,应该尽量从 \(y\) 的子集转移过来,这样就有 \(x|y = y[x\subseteq S_y]\) 其中 \(S\)\(y\) 的子集集合,倘若不是连通图,那么只有从 \(1\) 转移过去时才能使权值最小。

这里 dijkstra 与普通的不一样,因为可能存在多个连通块,所以需要对多个连通块跑最短路,中间需要枚举子集优化,优化完后再跑一次 dijkstra 更新其他最短距离。

枚举子集的方法也有叫 \(SOSdp\),推荐博客:

枚举所有集合的子集(红皮) - pechpo - 博客园 (cnblogs.com)

「学习笔记」SOS DP - cyl06 - 博客园 (cnblogs.com)

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

struct DIJ {
    using i64 = long long;
    using PII = pair<i64, i64>;
    vector<i64> dis;
    vector<vector<PII>> G;

    DIJ() {}
    DIJ(int n) {
        dis.assign(n + 1, 1e18);
        G.resize(n + 1);
    }

    void add(int u, int v, int w) {
        G[u].emplace_back(v, w);
    }

    void dijkstra() {
        priority_queue<PII> que;

        for (int i = 1; i < dis.size(); i ++) {
            if (dis[i] != 1e18) {
                que.push({dis[i], i});
            }
        }

        while (!que.empty()) {
            auto p = que.top();
            que.pop();
            int u = p.second;
            if (dis[u] < -p.first) continue;
            for (auto [v, w] : G[u]) {
                if (dis[v] > dis[u] + w) {
                    dis[v] = dis[u] + w;
                    que.push({ -dis[v], v});
                }
            }
        }
    }
};

void solve() {

    int n, m, k;
    cin >> n >> m >> k;

    DIJ dij(n);

    for (int i = 0; i < m; i ++) {
        int u, v, w;
        cin >> u >> v >> w;
        dij.add(u, v, w);
        dij.add(v, u, w);
    }

    dij.dis[1] = 0;
    dij.dijkstra();

    for (int i = 2; i <= n; i ++) {
        auto& d = dij.dis[i];
        d = min(d, 1ll * k * (1 | i));
        for (auto s = (i - 1)&i; s; s = (s - 1)&i) {
            d = min(d, dij.dis[s] + 1ll * i * k);
        }
    }

    dij.dijkstra();

    for (int i = 2; i <= n; i ++)
        cout << dij.dis[i] << " \n"[i == n];
}

int main() {
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

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

    return 0;
}

抓拍

HDU - 7467

思路

当存在有人往回走的时候,这个时候周长会变小,到达一定界限后,这个周长又会变大,所以用坐标轴表示 周长-时间 的曲线的话是个很显然的单峰函数,而对于单峰函数要找极值就需要用到三分了。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

int main() {
   ios::sync_with_stdio(false);
   cin.tie(nullptr);

   int n;
   cin >> n;

   vector<tuple<i64, i64, char>> a(n);

   for (auto &[x, y, fx] : a) {
      cin >> x >> y >> fx;
   }

   auto check = [&](int t)->i64{
      array<i64, 2> l{LLONG_MAX >> 1, LLONG_MIN >> 1}, r{LLONG_MIN >> 1, LLONG_MAX >> 1};
      auto A = a;
      for (int i = 0; i < n; i ++) {
         auto &[x, y, fx] = A[i];
         if (fx == 'E') {
            x += t;
         } else if (fx == 'W') {
            x -= t;
         } else if (fx == 'S') {
            y -= t;
         } else {
            y += t;
         }
         l[0] = min(l[0], x), l[1] = max(l[1], y);
         r[0] = max(r[0], x), r[1] = min(r[1], y);
      }
      return 2 * (abs(l[0] - r[0]) + abs(l[1] - r[1]));
   };

   i64 l = 0, r = 1e15;
   while (l < r) {
      i64 mid = l + (r - l) / 3;
      i64 midmid = r - (r - l) / 3;
      i64 val = check(mid), valval = check(midmid);
      if (valval > val) {
         r = midmid - 1;
      } else
         l = mid + 1;
   }

   cout << check(l) << "\n";

   return 0;
}

死亡之组

HDU - 7468

思路

分类讨论,首先把 \(a_1\) 从集合中去掉:

如果 \(a_1 ≥ L\),那么选最小的三个。

如果 \(a_1 < L\),那么选最大的,和最小的两个。

如果上述方案依然符合死亡之组的条件那么无解,否则有解。

代码

#include <bits/stdc++.h>

using namespace std;

using i64 = long long;

void solve() {

   int n, l, d;
   cin >> n >> l >> d;

   vector<int> a(n + 1);
   for (int i = 1; i <= n; i ++)
      cin >> a[i];

   bool f = a[1] >= l;

   sort(a.begin() + 2, a.end());

   if (f && (a[4] >= l || max({a[1], a[2], a[3], a[4]}) - min({a[1], a[2], a[3], a[4]}) <= d)) {
      cout << "No\n";
      return ;
   }

   if (!f && (a[n] >= l && a[3] >= l || max({a[1], a[2], a[3], a[n]}) - min({a[1], a[2], a[3], a[n]}) <= d)) {
      cout << "No\n";
      return ;
   }

   cout << "Yes\n";

}

int main() {
   ios::sync_with_stdio(false);
   cin.tie(nullptr);

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

   return 0;
}
posted @ 2024-08-04 19:13  Ke_scholar  阅读(33)  评论(2编辑  收藏  举报