題解-12/09 模擬賽
whk 復健賽,手速和腦子都不夠
A - Weather Report「ICPC World Finals 2015」
一開始讀錯了,以為是每種天氣一個,其實是對於 \(4^n\) 種天氣各一個。直接算出每種序列出現的權重,這可以直接列舉每種天氣的天數然後可重接排列數合併。然後考慮哈夫曼樹,每次找到兩個最小的合併起來。這題對於一堆有多個的,如果是偶數顯然是兩兩合併,是奇數就先幹掉一個然後再兩兩合併。
#include <bits/stdc++.h> using namespace std; int n, m = 4; double p[5], q[5] = {0, 1, 2, 3, 3}, ans; priority_queue<pair<double, long long>, vector<pair<double, long long>>, greater<pair<double, long long>>> v; long long calc(int a, int b, int c, int d) { double ret = 1; for (int i = 1; i <= n; i++) ret *= i; for (int i = 1; i <= a; i++) ret /= i; for (int i = 1; i <= b; i++) ret /= i; for (int i = 1; i <= c; i++) ret /= i; for (int i = 1; i <= d; i++) ret /= i; return (long long)(ret); } int main() { scanf("%d", &n); for (int i = 1; i <= m; i++) scanf("%lf", p + i); for (int i = 0; i <= n; i++) { for (int j = 0; j <= n; j++) { for (int k = 0; k <= n; k++) { for (int l = 0; l <= n; l++) { if (i + j + k + l == n) { v.push(make_pair(pow(p[1], i) * pow(p[2], j) * pow(p[3], k) * pow(p[4], l), calc(i, j, k, l))); } } } } } while (v.size()) { pair<double, long long> now = v.top(); v.pop(); if (!v.size() && now.second == 1) continue; if (now.second > 1) { if (now.second & 1) v.push(make_pair(now.first, 1)), now.second--; ans += now.first * now.second; v.push(make_pair(now.first * 2, now.second / 2)); } else { pair<double, long long> nxt = v.top(); v.pop(); ans += now.first + nxt.first; if (nxt.second > 1) v.push(make_pair(nxt.first, nxt.second - 1)); v.push(make_pair(now.first + nxt.first, 1)); } } printf("%.6lf\n", ans); return 0; }
B - Journey from Petersburg to Moscow「NEERC2017」
這種題一般先考慮你得到了一個最優方案,然後你只用付前 \(k\) 大的代價,也就是說小於第 \(k\) 大的代價都不需要管,那麼我們不妨把所有代價減去一個第 \(k\) 大的代價,最後再加上其的 \(k\) 倍即可。
然後考慮列舉哪個是第 \(k\) 大。然後減去其跑最短路,這時我們需要考慮最短路是否恰好是 \(k\) 條路組成。但這顯然太麻煩了,這種題一般都是找一些性質然後得到一個更簡單的方法。考慮直接對算出來的所有答案取 min,然後就發現這東西對了。具體證明就分類討論一下比第 \(k\) 大小或者大的情況。很多曼哈頓距離的那種題也可以用類似的方法做。
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn = 30005; const ll inf = 0x3f3f3f3f3f3f3f3f; template <typename T> void read(T &x) { T flg = 1; char ch = getchar(); for (; !isdigit(ch); ch = getchar()) if (ch == '-') flg = -1; for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0'; x *= flg; } struct zcr{ ll dis; int num; }; bool operator < (zcr a, zcr b) { return make_pair(a.dis, a.num) < make_pair(b.dis, b.num); } zcr operator + (zcr a, zcr b) { return zcr{a.dis + b.dis, a.num + b.num}; } struct EDGE{ int u, v, w; }e[maxn]; struct node{ int pre, to, val, zz; }edge[maxn << 1]; int head[maxn], tot = 1; ll ans = inf; zcr dis[maxn]; int N, M, K; bool vis[maxn]; priority_queue<pair<zcr, int>, vector<pair<zcr, int>>, greater<pair<zcr, int>>> q; void dij() { for (int i = 1; i <= N; i++) dis[i] = zcr{inf, 0}, vis[i] = 0; dis[1] = zcr{0, 0}; q.push(make_pair(dis[1], 1)); while (q.size()) { int u = q.top().second; q.pop(); if (vis[u]) continue; vis[u] = 1; for (int i = head[u]; i; i = edge[i].pre) { int v = edge[i].to; if ((dis[u] + zcr{edge[i].val, 1}) < dis[v]) { dis[v] = dis[u] + zcr{edge[i].val, 1}; q.push(make_pair(dis[v], v)); } } } } void add_edge(int u, int v, int w) { edge[++tot] = node{head[u], v, w, w}; head[u] = tot; } int main() { read(N); read(M); read(K); for (int i = 1; i <= M; i++) read(e[i].u), read(e[i].v), read(e[i].w), add_edge(e[i].u, e[i].v, e[i].w), add_edge(e[i].v, e[i].u, e[i].w); dij(); if (dis[N].num <= K) ans = dis[N].dis; for (int i = 1; i <= M; i++) { for (int j = 2; j <= tot; j++) { edge[j].val = max(edge[j].zz - e[i].w, 0); } dij(); ans = min(ans, dis[N].dis + 1ll * e[i].w * K); } printf("%lld\n", ans); return 0; }
C - Bipartite Blanket「CERC2016」
賽時以為 Hall 定理是個很複雜的東西,然後經過學長點撥發現好像是個挺 naive 的玩意。
首先先不考慮第一個條件,我們發現對於一個點集它可以,僅當其左部點和整個圖的右部點有完美匹配,右部點同理。然後根據慣例考慮必要條件是否充分,然後對邊調整一下發現真的充分了。於是可以 \(O(2^n n)\) 排序雙指標了。至於怎麼判斷,用 Hall 定理即可。
簡單介紹 Hall 定理。還是考慮充分必要條件。顯然對於每個子集,其連出去的邊連到的點所構成的集合的大小一定不小於當前子集的大小。然後發現這必要條件太強了,直接充分。證明可以考慮反正法。這樣乍一看還要 \(O(3^n)\) 列舉子集。其實不需要,稍微修改,對於每個大小為 \(|S|-1\) 的子集存在完美匹配,並且 \(S\) 滿足條件。這樣就可以 \(O(2^n n)\) 了。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
template <typename T>
void read(T &x) {
T flg = 1;
char ch = getchar();
for (; !isdigit(ch); ch = getchar()) if (ch == '-') flg = -1;
for (x = 0; isdigit(ch); ch = getchar()) x = x * 10 + ch - '0';
x *= flg;
}
ll n, m, T;
ll a[25][25];
char s[25];
ll v[25], w[25], id[1 << 20];
ll zz[25], cc[25];
bool mark[1 << 20];
vector<ll> v1, v2;
ll ans;
int main() {
read(n); read(m);
for (ll i = 1, j = 1; i < (1 << 20); i <<= 1, j++) id[i] = j;
for (ll i = 1; i <= n; i++){
scanf("%s", s + 1);
for (ll j = 1; j <= m; j++)
a[i][j] = s[j] - '0', zz[i] |= a[i][j] << (j - 1), cc[j] |= a[i][j] << (i - 1);
mark[1 << (i - 1)] = zz[i] > 0;
}
for (ll i = 1; i <= n; i++) read(v[i]);
for (ll i = 1; i <= m; i++) read(w[i]);
read(T); v1.push_back(0); v2.push_back(0);
for (ll i = 1; i < (1 << n); i++) {
ll now = 0, rr = 0;
if (__builtin_popcount(i) == 1 && mark[i]) {
v1.push_back(v[id[i]]);
continue;
}
mark[i] = 1;
for (ll j = 0; j < n; j++) {
if (i & (1 << j)) {
rr |= zz[j + 1];
mark[i] &= mark[i ^ (1 << j)];
now += v[j + 1];
}
}
mark[i] &= __builtin_popcount(i) <= __builtin_popcount(rr);
if (mark[i]) {
v1.push_back(now);
}
}
memset(mark, 0, sizeof(mark));
for (ll i = 1; i <= m; i++) mark[1 << (i - 1)] = cc[i] > 0;
for (ll i = 1; i < (1 << m); i++) {
ll now = 0, rr = 0;
if (__builtin_popcount(i) == 1 && mark[i]) {
v2.push_back(w[id[i]]);
continue;
}
mark[i] = 1;
for (ll j = 0; j < m; j++) {
if (i & (1 << j)) {
rr |= cc[j + 1];
mark[i] &= mark[i ^ (1 << j)];
now += w[j + 1];
}
}
mark[i] &= __builtin_popcount(i) <= __builtin_popcount(rr);
if (mark[i]) {
v2.push_back(now);
}
}
sort(v1.begin(), v1.end()); sort(v2.begin(), v2.end()); reverse(v2.begin(), v2.end());
for (ll i = 0, j = 0; i < (ll)v2.size(); i++) {
while (j < (ll)v1.size() && v1[j] + v2[i] < T) j++;
if (j < (ll)v1.size()) ans += v1.size() - j;
}
printf("%lld\n", ans);
return 0;
}