题面我就不给出了,大家有兴趣可以自己去看一看。。。毕竟我懒。。。
Solution A
这个题直接按照题目所给的条件去做就是了。
雅兴高的可以建图跑 dfs 。
不过由于位置是单调的,
所以我们直接一遍扫过去就可以了。
时间复杂度为 O(n) 。
Code A
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 300000 + 5
int n, k, Next[N];
int main()
{
scanf("%d%d", &n, &k);
int u = 1;
bool ok = 0;
for (int i = 1; !ok && i < n; i ++)
{
scanf("%d", Next + i);
Next[i] += i;
u = u == i ? Next[i] : u;
if (u == k) ok = 1;
}
puts(ok ? "YES" : "NO");
return 0;
}
Solution B
这个题看起来只有 A[i][j] == 1 的才能换。
如果我们把 A[][] 看做邻接矩阵的话,
实际上只要 u 和 v 在同一个联通块里边,
u 就可以和 v 直接交换。
【证明】
假设有一条从 u 到 v 的路径 u - a - ... - b - v
那么我们用 u 一路换到 v,变成这样:
a - ... - b - v - u
然后再用 v 一路换到 a,变成这样:
v - a - ... - b - u。
这就等价于 u 和 v 的直接交换。
好的,因为要求字典序最小的排列,
那么我们就可以给每个联通块里边的元素排个序,
就可以得到字典序最小的排列了。
时间复杂度为 O(n^2)。
Code B
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 300 + 5
int n, A[N], Fa[N], Ord[N];
bool Map[N][N], Flag[N];
inline int Find(int x)
{
return x == Fa[x] ? x : Fa[x] = Find(Fa[x]);
}
inline bool cmp(int u, int v)
{
return A[u] < A[v];
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++)
{
scanf("%d", A + i);
Ord[i] = Fa[i] = i;
}
sort(Ord + 1, Ord + n + 1, cmp);
char ch = '\n';
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
{
while (ch != '0' && ch != '1')
ch = getchar();
if (ch == '1') Fa[Find(i)] = Find(j);
ch = getchar();
}
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
Map[i][j] = Find(i) == Find(j);
for (int i = 1; i <= n; i ++)
for (int j = 1; j <= n; j ++)
if (Map[i][Ord[j]] && !Flag[j])
{
printf("%d%c", j, i == n ? '\n' : ' ');
Flag[j] = 1;
break ;
}
return 0;
}
Solution C
这个题看起来不那么好弄的样子。
首先,这个题是要找到一个书本的摆放顺序,
使得按照题目所给的操作看书时拿书的总重量最小。
我们可以先考虑这样来确定顺序:
顺序枚举看书顺序表,
如果当前的书还没有被看过,
那么把它放在所有现在已经看完了的书的下方,
否则就不管它。
对于至始至终没有被看过的书,
就按任意顺序放在最下方。
这样子的顺序是最优的。
【伪证明】
首先考虑如果有一本书被看过,
那么这本书之后的位置就随之确定了。
所以我们考虑在某个时刻还没有被看过一次的书。
如果我们能够证明把这本书放下面比放上面优,
那么我们就可以构造一个所有书都满足条件的方案,
于是这个问题就解决了。
如果这本书放在之前要看的书的上面,
那么在拿书的时候,
我们考虑的这本书就要对答案做贡献。
而如果放下面,
则不会产生贡献。
所以之前所说的顺序构造方法是最优的。
(感觉证得好伪。。。)
现在我们得到了这样的书的摆放顺序,
接下来就是直接模拟了。
看一本书所需要的拿的书的数量是 O(n) 的。
要拿 m 本书,
所以我们就直接暴力模拟即可。
反正数据范围小。
时间复杂度 O(nm) 。
Code C
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 1500 + 5
int n, m, ans, A[N], B[N], Ord[N];
bool Flag[N];
int main()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; i ++)
scanf("%d", A + i);
for (int i = 1; i <= m; i ++)
{
scanf("%d", B + i);
if (!Flag[B[i]]) Ord[++ Ord[0]] = B[i];
Flag[B[i]] = 1;
}
for (int i = 1; i <= m; i ++)
{
int j = 1;
for (; Ord[j] != B[i]; j ++) ans += A[Ord[j]];
for (; j > 1; j --) swap(Ord[j], Ord[j - 1]);
}
printf("%d\n", ans);
return 0;
}
Solution D
这个题还是比较漂亮的。
对于链的权值的期望,我们是很难维护的。
因为有 O(n^2) 条链,然后还要带修改。。。
反正我不会。
所以我们可以考虑每一条边对答案的贡献。
首先我们知道,期望是可以加的。
于是我们这么做是可以的。
我们就可以考虑有多少情况下这条边会出现。
设 x 为该边一端的点的个数,另一端则有 (n - x) 个点。
于是有 x * (x - 1) / 2 *(n - x)+ (n - x) * (n - x - 1) / 2 * x 种情况下,
这条边会对答案有贡献。
然后我们注意到一条边要么不被计算,要么就被计算两次。
所以一条边对期望的贡献就是:W * [x * (x - 1) * (n - x) + (n - x) * (n - x - 1) * x] / [n * (n - 1) * (n - 2)]
其中 W 为该边的边权。
于是整个题就轻松愉快了。
可以 O(1) 地处理询问了。
时间复杂度 O(n + q)。
Code D
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
typedef long double LD;
#define N 100000 + 5
int n, tot = 1, Head[N], Size[N], E[N], W[N];
LD ans;
LL A_n_3;
struct Edge
{
int next, node, w;
}h[N << 1];
inline void addedge(int u, int v, int w)
{
h[++ tot].next = Head[u], Head[u] = tot;
h[tot].node = v, h[tot].w = w;
h[++ tot].next = Head[v], Head[v] = tot;
h[tot].node = u, h[tot].w = w;
}
inline void dfs(int z, int fa)
{
Size[z] = 1;
for (int i = Head[z]; i; i = h[i].next)
{
int d = h[i].node;
if (d == fa) continue ;
dfs(d, z);
Size[z] += Size[d];
W[i >> 1] = Size[d];
}
}
inline LD Calc(int pos, int delta)
{
LL t_1 = (LL) W[pos] * (W[pos] - 1) * (n - W[pos]) * 6;
LL t_2 = (LL) W[pos] * (n - W[pos]) * (n - W[pos] - 1) * 6;
LD t = (LD) (t_1 + t_2) / A_n_3;
return t * delta;
}
int main()
{
scanf("%d", &n);
A_n_3 = (LL) n * (n - 1) * (n - 2);
for (int i = 1; i < n; i ++)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
addedge(u, v, w);
E[i] = w;
}
dfs(1, 0);
for (int i = 1; i < n; i ++)
ans += Calc(i, E[i]);
int q;
scanf("%d", &q);
while (q --)
{
int pos, num;
scanf("%d%d", &pos, &num);
ans -= Calc(pos, E[pos]);
ans += Calc(pos, num);
E[pos] = num;
printf("%.7lf\n", (double) ans);
}
return 0;
}
Solution E
如果我们可以枚举 i 并维护左端点为 1 .. i ,右端点 i 的答案。
于是这个题就可以离线做了。
事实上也是如此。
我们可以把一根那啥变成一条线段 [L, L + H],
于是就问题就变成了求 (l, r) 中的线段的空白部分大小。
空白就意味着前一根那啥碰不到后一根那啥。
于是我们就可以根据线段的右端点位置维护一个单调递减栈。
这意味着栈中两个相邻元素之间的那啥都可以统一考虑。
然后对于两个相邻元素之间的那啥都可以统一处理。
我们注意到:
如果某个决策区间的那啥够不到当前枚举的那啥,
那么我们将对这个区间的答案进行区间增量,
然后这个区间就会被删去。
所以:
每个那啥所代表的区间只会进行 O(1) 次操作。
于是这个题再弄个线段树来搞区间增量,单点询问,
时间复杂度就是 O(n log n) 的了。
啊。。。感觉我并没有讲清。。。
那就看看我的代码吧。。。TAT
Code E
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
typedef long long LL;
#define N 200000 + 5
#define M 600000 + 5
#define ls(x) x << 1
#define rs(x) x << 1 | 1
int n, T, size, L[N], R[N], q[N];
LL Ans[N];
struct Querys
{
int l, r, id;
Querys (int _l = 0, int _r = 0, int _id = 0) {l = _l, r = _r, id = _id;}
bool operator < (const Querys a) const
{
return r < a.r;
}
}Ask[N];
struct Segment_Tree
{
LL num, delta;
}h[M];
inline void apply(int x, int d)
{
h[x].num += d;
h[x].delta += d;
}
inline void push(int x)
{
if (h[x].delta)
{
apply(ls(x), h[x].delta);
apply(rs(x), h[x].delta);
h[x].delta = 0;
}
}
inline void Modify(int x, int l, int r, int s, int t, int d)
{
if (l == s && r == t)
{
apply(x, d);
return ;
}
push(x);
int mid = l + r >> 1;
if (t <= mid)
Modify(ls(x), l, mid, s, t, d);
else if (s > mid)
Modify(rs(x), mid + 1, r, s, t, d);
else Modify(ls(x), l, mid, s, mid, d), Modify(rs(x), mid + 1, r, mid + 1, t, d);
}
inline LL Query(int x, int l, int r, int t)
{
if (l == r)
return h[x].num;
push(x);
int mid = l + r >> 1;
if (t <= mid)
return Query(ls(x), l, mid, t);
else return Query(rs(x), mid + 1, r, t);
}
int main()
{
scanf("%d", &n);
for (int i = 1; i <= n; i ++)
{
scanf("%d%d", L + i, R + i);
R[i] += L[i];
}
scanf("%d", &T);
for (int i = 1; i <= T; i ++)
{
int l, r;
scanf("%d%d", &l, &r);
Ask[i] = Querys(l, r, i);
}
sort(Ask + 1, Ask + T + 1);
int t = 1;
for (int i = 1; i <= n; i ++)
{
for (; R[q[size]] <= R[i] && size; size --)
if (R[q[size]] < L[i])
Modify(1, 1, n, q[size - 1] + 1, q[size], L[i] - R[q[size]]);
q[++ size] = i;
while (Ask[t].r == i && t <= T)
{
Ans[Ask[t].id] = Query(1, 1, n, Ask[t].l);
t ++;
}
}
for (int i = 1; i <= T; i ++)
printf("%I64d\n", Ans[i]);
return 0;
}
Solution F
这个题想必是分治吧。
我们可以弄一个 Solve(l, r),
表示处理时间点在 [l, r] 中的询问。
首先,如果某个 item 在 [l, r] 这整个时间区间内都是可以被买的,
那么就把这个物品 Dp 一下,
也就是 01 背包了。
然后给这个物品打上“已经被处理”的标记。
然后再递归进行 Solve(l, mid) 和 Solve(mid + 1, r)。
直到 l == r 的时候记录询问时间点正好为 l 的答案。
一定记得要回溯!
然后最重要的一点:
一个物品只会被处理 O(log n) 次。
类似于每一条线段都对应着线段树的 O(log n) 个区间,
所以时间复杂度为 O(nT + nB log n)
T 为时间的最大值,B 为 预算金额的最大值。
Code F
#include <cmath>
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
#define N 4000 + 5
#define M 20000 + 5
#define INF 0x7fffffff
int n, p, q, Max, _Max, C[N], W[N], A[N], Dp[21][N], Max_Dp[21][N], T[M], B[M], Ans[M], Done[N];
vector <int> Map[M];
inline void Solve(int depth, int l, int r)
{
for (int i = 1; i <= n; i ++)
if (!Done[i] && A[i] <= l && A[i] + p > r)
{
Done[i] = depth;
for (int j = _Max; j >= C[i]; j --)
Dp[depth][j] = max(Dp[depth][j], Dp[depth][j - C[i]] + W[i]);
}
for (int i = 0; i <= _Max; i ++)
Max_Dp[depth][i] = i == 0 ? Dp[depth][i] : max(Dp[depth][i], Max_Dp[depth][i - 1]);
if (l == r)
{
for (int i = 0; i < Map[l].size(); i ++)
Ans[Map[l][i]] = Max_Dp[depth][B[Map[l][i]]];
for (int i = 1; i <= n; i ++)
if (Done[i] == depth) Done[i] = 0;
return ;
}
int mid = l + r >> 1;
for (int i = 0; i <= _Max; i ++)
Dp[depth + 1][i] = Dp[depth][i];
Solve(depth + 1, l, mid);
for (int i = 0; i <= _Max; i ++)
Dp[depth + 1][i] = Dp[depth][i];
Solve(depth + 1, mid + 1, r);
for (int i = 1; i <= n; i ++)
if (Done[i] == depth) Done[i] = 0;
}
int main()
{
scanf("%d%d", &n, &p);
for (int i = 1; i <= n; i ++)
scanf("%d%d%d", C + i, W + i, A + i);
scanf("%d", &q);
for (int i = 1; i <= q; i ++)
{
scanf("%d%d", T + i, B + i);
Max = max(Max, T[i]);
_Max = max(_Max, B[i]);
Map[T[i]].push_back(i);
}
for (int i = 1; i < _Max; i ++)
Dp[0][i] = -INF;
Solve(0, 1, Max);
for (int i = 1; i <= q; i ++)
printf("%d\n", Ans[i]);
return 0;
}
至于 G 题,我太弱不会。于是等我会了的时候再来更新吧。
所以这只能说是部分题解。。。TAT。。。=_=