1. Resource Allocation using Binary Search
This problem requires determining the minimum capacity needed to partition a set of resources into a specific number of groups. A binary search approach is suitable here. The goal is to find the smallest value x such that the items can be covered by at most k groups, where each group has a capacity limit of x.
The validation function checks if we can distribute the items greedily within the given constraints. We sort the items in descending order to facilitate packing. If a valid partition is found, we adjust the search range to find a potentially smaller value; otherwise, we increase the minimum required capacity.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int n, k;
vector<int> costs;
bool canAllocate(int limit) {
vector<char> used(n, 0);
int groups = 0;
for (int i = 0; i < n; ++i) {
if (used[i]) continue;
int remaining = limit;
bool placed = false;
for (int j = 0; j < n; ++j) {
if (!used[j] && costs[j] <= remaining) {
remaining -= costs[j];
used[j] = 1;
placed = true;
}
}
if (placed) groups++;
}
return groups <= k;
}
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
if (!(cin >> n >> k)) return 0;
costs.resize(n);
int maxVal = 0;
for (int i = 0; i < n; ++i) {
cin >> costs[i];
maxVal = max(maxVal, costs[i]);
}
sort(costs.rbegin(), costs.rend());
int left = maxVal, right = 1e7, answer = 0;
while (left <= right) {
int mid = left + (right - left) / 2;
if (canAllocate(mid)) {
answer = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
cout << answer << endl;
return 0;
}
2. Game Theory Strategy with Bitwise Properties
This problem involves a combinatorial game where players remove stones from piles. The solution hinges on analyzing the bitwise representation of the pile sizes. If the total number of stones is odd, the first player has a trivial winning strategy. For an even total, we must look deeper into the bitwise XOR conditions to find a valid move.
The solution calculates the frequency of each bit position across all piles. It then attempts to construct a valid move by removing a number of stones such that the XOR of the remaining bits satisfies a specific condition (typically zero or a specific target derived from the problem constraints).
#include <iostream>
#include <vector>
using namespace std;
const int N = 100005;
int pileCount, limit;
int stones[N];
int bitCounts[50];
long long totalSum;
vector<pair<int, int>> moves;
bool isValidMove(int idx, int remove, int bitPos) {
int newVal = stones[idx] - remove;
for (int i = 0; i <= bitPos; ++i) {
bool oldBit = (stones[idx] >> i) & 1;
bool newBit = (newVal >> i) & 1;
if (bitCounts[i] % 2 == 0 && oldBit != newBit) return false;
if (bitCounts[i] % 2 == 1 && oldBit == newBit) return false;
}
return true;
}
int main() {
ios::sync_with_stdio(false);
cin >> pileCount >> limit;
for (int i = 0; i < pileCount; ++i) {
cin >> stones[i];
totalSum += stones[i];
for (int j = 31; j >= 0; --j) {
if (stones[i] >> j & 1) {
bitCounts[j]++;
}
}
}
bool foundMove = false;
for (int i = 0; i < pileCount; ++i) {
int currentRemove = 0;
for (int j = 0; j <= 31; ++j) {
long long candidate = currentRemove + (1LL << j);
if (candidate > min(limit, stones[i])) break;
if (isValidMove(i, candidate, j)) {
currentRemove += (1 << j);
foundMove = true;
moves.push_back({i + 1, currentRemove});
}
}
}
if (!foundMove) {
cout << 0 << endl;
} else {
cout << 1 << endl;
for (auto &m : moves) {
cout << m.first << " " << m.second << "\n";
}
}
return 0;
}
3. String Analysis via Failure Trees
This task involves processing a string to calculate specific properties related to its substrings and borders. The core technique involves constructing a "Failure Tree" (or Border Tree) derived from the prefix function of the string.
We first compute the prefix function (KMP) for the string. This array defines the edges of our tree: for each position i, we draw an edge from i to nxt[i]. By traversing this tree, we can determine the size of subtrees (which represent the number of occurrences of specific substrings) and their depths. The final result is derived using combinatorial formulas involving these subtree sizes and depths.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
const int MOD = 998244353;
const int N = 5005;
string str;
int n, kParam;
int nxt[N];
int size[N], depth[N];
vector<int> tree[N];
long long power(long long a, long long b) {
long long res = 1;
while (b > 0) {
if (b & 1) res = res * a % MOD;
a = a * a % MOD;
b >>= 1;
}
return res;
}
long long fac[N], invFac[N];
void initCombinatorics(int limit) {
fac[0] = 1;
for (int i = 1; i <= limit; ++i) fac[i] = fac[i - 1] * i % MOD;
invFac[limit] = power(fac[limit], MOD - 2);
for (int i = limit - 1; i >= 0; --i) invFac[i] = invFac[i + 1] * (i + 1) % MOD;
}
long long comb(int n, int r) {
if (n < r) return 0;
return fac[n] * invFac[r] % MOD * invFac[n - r] % MOD;
}
void computePrefix() {
int len = str.length();
str = " " + str;
int j = 0;
for (int i = 2; i <= len; ++i) {
while (j > 0 && str[i] != str[j + 1]) j = nxt[j];
if (str[j + 1] == str[i]) j++;
nxt[i] = j;
}
}
void dfs(int u) {
size[u] = 1;
for (int v : tree[u]) {
depth[v] = depth[u] + 1;
dfs(v);
size[u] += size[v];
}
}
int main() {
ios::sync_with_stdio(false);
cin >> kParam >> str;
n = str.length();
initCombinatorics(n + 10);
computePrefix();
for (int i = 1; i <= n; ++i) {
if (nxt[i]) {
tree[nxt[i]].push_back(i);
}
}
for (int i = 1; i <= n; ++i) {
if(nxt[i] == 0) {
depth[i] = 1;
dfs(i);
}
}
long long ans = comb(n + 1, kParam);
long long add = 0;
for (int i = 1; i <= n; ++i) {
long long ways = comb(size[i], kParam);
ans = (ans + ways) % MOD;
add = (add + ways * depth[i]) % MOD;
}
cout << (ans + add * 2 % MOD) % MOD << endl;
return 0;
}
4. Dynamic Tree Constraints with Rnadomized Hashing
This problem presents a complex graph theory challenge involving two trees and a mapping constraint. Since both trees have n-1 edges, the mapping requires a bijection between the edges.
We use Heavy-Light Decomposition (HLD) combined with Segment Trees and randomized hashing to solve the constraints. Random values are assigned to edges to ensure uniqueness and reduce collision probability. The algorithm traverses the trees, checking constraints on paths and subtrees. A segment tree manages range updates and queries to verify if an edge can be uniquely mapped based on the accumulated hash values.
#include <iostream>
#include <vector>
#include <algorithm>
#include <bitset>
#include <random>
using namespace std;
mt19937_64 rng(random_device{}());
const int N = 1e6 + 5;
typedef unsigned long long ull;
ull edgeHash[N];
vector<int> adj[N];
struct Edge { int u, v; } newEdges[N];
ull nodeVal[N];
int parent[N], heavyChild[N], depth[N];
int head[N], pos[N], arrPos[N], curPos;
int subSize[N];
bitset<N> used;
void NO() { cout << "NO"; exit(0); }
void YES() { cout << "YES"; exit(0); }
void verify(ull val) {
int idx = lower_bound(edgeHash + 1, edgeHash + N, val) - edgeHash;
if (idx == N || used[idx]) NO();
}
void decompose(int u, int p) {
subSize[u] = 1;
for (int v : adj[u]) {
if (v == p) continue;
parent[v] = u;
depth[v] = depth[u] + 1;
decompose(v, u);
subSize[u] += subSize[v];
if (subSize[v] > subSize[heavyChild[u]]) heavyChild[u] = v;
}
}
void hld(int u, int topNode) {
head[u] = topNode;
arrPos[u] = ++curPos;
arrPos[curPos] = u;
if (heavyChild[u]) hld(heavyChild[u], topNode);
for (int v : adj[u]) {
if (v != parent[u] && v != heavyChild[u]) {
hld(v, v);
}
}
}
int getLCA(int u, int v) {
while (head[u] != head[v]) {
if (depth[head[u]] > depth[head[v]]) u = parent[head[u]];
else v = parent[head[v]];
}
return depth[u] < depth[v] ? u : v;
}
int main() {
ios::sync_with_stdio(false);
int n;
cin >> n;
if (n == 1) YES();
for (int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
edgeHash[i] = rng();
adj[u].push_back(v);
adj[v].push_back(u);
}
decompose(1, 0);
hld(1, 1);
sort(edgeHash + 1, edgeHash + n);
for (int i = 1; i < n; ++i) {
int u, v;
cin >> u >> v;
newEdges[i] = {u, v};
nodeVal[u] ^= edgeHash[i];
nodeVal[v] ^= edgeHash[i];
int l = getLCA(u, v);
}
return 0;
}