2024“釘耙程式設計”中國大學生演算法設計超級聯賽(3)

Ke_scholar發表於2024-08-04

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;
}

相關文章