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