前言
day2相对于day1代码上短了不少,但想拿到200+还是有难度的。
游戏
思考一下有些点:(1)只有当人和钥匙在门的同一侧,才能通过这道门;(2)如果以房间(x)为起始位置,能到达([l,r])(一定是一个连续区间),那么对于其他房间,只要能到达房间(x),能到达的区间一定包含([l,r])。
结合以上两点:不难发现:每次选择一个房间(这里定义房间为两道门之间所有的区域),看一看左右门能不能打开,如果能到达左边门之后的房间(x),由(1),(x)不可能到右边房间,故只能到达([l,x]),那么在起点就一定能到达([l,x])。此时发现访问区间一定是严格的包含关系,故可以记搜解决。之后继续扩展。能继续拓展当且仅当门的钥匙在能到达的区间内。一开始博主一直在想set
合并,后来发现直接判一下在不在区间内就好了,果然我还是tcl
这道题有点细节需要处理好。最后复杂度(mathcal O(n))。
#include <bits/stdc++.h>
#define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
#define per(i, a, b) for (int i = a, i##end = b; i >= i##end; --i)
#define rep0(i, a) for (int i = 0, i##end = a; i < i##end; ++i)
#define per0(i, a) for (int i = a-1; ~i; --i)
#define chkmin(a, b) a = std::min(a, b)
#define chkmax(a, b) a = std::max(a, b)
typedef long long ll;
const int maxn = 1145145;
int n, m, p, l[maxn], r[maxn], door[maxn], z[maxn];
void dfs(int i) {
if (l[i]) return;
l[i] = r[i] = i;
while (l[i] > 1 && !door[l[i]-1]) l[i]--;
while (r[i] < n && !door[r[i]]) r[i]++;
int L = l[i], R = r[i];
for (;;) {
int flag = 0;
while (l[i] > 1 && l[i] <= z[l[i]-1] && z[l[i]-1] <= r[i]) dfs(l[i]-1), l[i] = l[l[i]-1], flag = 1;
while (r[i] < n && l[i] <= z[r[i]] && z[r[i]] <= r[i]) dfs(r[i]+1), r[i] = r[r[i]+1], flag = 1;
if (!flag) break;
}
rep(x, L, R) l[x] = l[i], r[x] = r[i];
}
int main() {
scanf("%d%d%d", &n, &m, &p);
rep(i, 1, m) {
int x, y;
scanf("%d%d", &x, &y);
z[x] = y; door[x] = 1;
}
rep(i, 1, n) dfs(i);
while (p--) {
int s, t;
scanf("%d%d", &s, &t);
if (l[s] <= t && t <= r[s]) printf("YES
"); else printf("NO
");
}
return 0;
}
排列
这道题自己只会暴力做法,猜的(mathcal O(n^2))做法假了,故看题解学习。
把题目翻译一下,不难发现实质就是有若干棵树,父亲对儿子约束先后关系,求一个合法排列使得(sumlimits_{i=1}^n i imes w_{p_i})最大。如果有环,答案显然是(-1)。
想一想发现不好来dp。如果没有约束,显然一顿排序从小到大就好了。可是加上约束似乎变得很棘手。现在有这样的一个小结论:如果当前结点是最小的结点,显然在父亲(如果有)选完之后第一个选择这个结点,这个是显然的。这样子这两个结点就被打包到一起了(不会有其它的结点穿插在之间),这点大概是正确性的前提。那对于其它子结点什么时候被选择呢?那么考虑两个被打包的结点,它们之间有先后顺序。假设两个结点对应的序列为({a_1,a_2,dots,a_n})和({b_1,b_2,dots,b_m}),前者合并时有在前或者在后两种,故得到
若(①>②),即
这里(overline a)表示(a_i)的平均值,即((sum_{i=1}^na_i)/n),另一个同理。我们惊奇地发现合并后可以拿之后的平均值继续合并打包。于是就得到了一个贪心合并的算法,拿个可修改的堆搞一搞就好了,当然也可以像Dijkstra
算法类似不用可修改堆。正确性用前面讲的归纳就行了。复杂度(mathcal O(nlog n))。
最后注意数据范围以及unsigned long long
和long double
就行了(博主当时由于-(ull)是正数WA了许久)
#include <bits/stdc++.h>
#define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
#define per(i, a, b) for (int i = a, i##end = b; i >= i##end; --i)
#define rep0(i, a) for (int i = 0, i##end = a; i < i##end; ++i)
#define per0(i, a) for (int i = a-1; ~i; --i)
#define chkmin(a, b) a = std::min(a, b)
#define chkmax(a, b) a = std::max(a, b)
#define x first
#define y second
typedef unsigned long long ll;
const int maxn = 555555;
int f[maxn];
int fset(int x) { return f[x] == x ? x : f[x] = fset(f[x]); }
void merge(int x, int y) { f[fset(y)] = fset(x); }
int n, a[maxn], inq[maxn], tot[maxn], cnt = 0;
ll sum[maxn], w[maxn];
std::priority_queue<std::pair<long double, int> > Q;
int main() {
scanf("%d", &n);
rep(i, 0, n) f[i] = i;
rep(i, 1, n) {
scanf("%d", &a[i]);
if (fset(i) == fset(a[i])) { printf("-1"); return 0; }
merge(i, a[i]);
}
rep(i, 1, n) scanf("%llu", &w[i]), Q.push(std::make_pair(-(long double)(sum[i] = w[i]), i)), tot[i] = inq[i] = 1;
rep(i, 0, n) f[i] = i;
while (!Q.empty()) {
int u = Q.top().y; Q.pop();
if (!inq[u]) continue;
inq[u] = 0; merge(a[u], u); fset(u);
sum[f[u]] += sum[u], w[f[u]] += w[u]+tot[f[u]]*sum[u], tot[f[u]] += tot[u];
Q.push(std::make_pair(-(long double)sum[f[u]]/tot[f[u]], f[u]));
}
printf("%llu", w[0]);
return 0;
}
道路
一道很水的dp题,按照题意设dp式子。注意到深度不超过40
,这个很重要。设(f_{u,x,y})表示结点(u)上面有(x)条铁路没删,(y)条公路没删,然后转移方程(f_{u,x,y}=max{f_{lson,x,y}+f_{rson,x,y+1},f_{lson,x+1,y}+f_{rson,x,y}})表示选择翻修左边铁路或者右边公路两种决策,边界是叶子结点对应的公式(c_i(a_i+x)(b_i+y))。记忆化即可,复杂度(mathcal O(n imes 40^2))。
#include <bits/stdc++.h>
#define rep(i, a, b) for (int i = a, i##end = b; i <= i##end; ++i)
#define per(i, a, b) for (int i = a, i##end = b; i >= i##end; --i)
#define rep0(i, a) for (int i = 0, i##end = a; i < i##end; ++i)
#define per0(i, a) for (int i = a-1; ~i; --i)
#define chkmin(a, b) a = std::min(a, b)
#define chkmax(a, b) a = std::max(a, b)
typedef long long ll;
const int maxn = 20000 + 5;
ll f[maxn][41][41];
int a[maxn], b[maxn], c[maxn], l[maxn], r[maxn], n;
ll dp(int u, int x, int y) {
if (u < 0) { u = -u; return 1ll*c[u]*(a[u]+x)*(b[u]+y); }
if (~f[u][x][y]) return f[u][x][y];
return f[u][x][y] = std::min(dp(l[u], x, y) + dp(r[u], x, y+1), dp(l[u], x+1, y) + dp(r[u], x, y));
}
int main() {
scanf("%d", &n);
rep(i, 1, n-1) scanf("%d%d", &l[i], &r[i]);
rep(i, 1, n) scanf("%d%d%d", &a[i], &b[i], &c[i]);
memset(f, -1, sizeof f);
printf("%lld", dp(1, 0, 0));
return 0;
}