Codeforces Round #607 (Div. 1)
A
每个位置一旦被赋值就不会再更改。记录当前哪些位置已经赋值,然后暴力更改没赋值的位置。但 (m) 之后的用不到不用管
B
答案只有 (6) 种
(res=0) 初始就全都是 (A)
(res=1) 矩阵的四条边界中有某条边界全是 (A)
(res=2) 某一行(列)全是 (A) 或者四个顶点中某个是 (A)
(res=3) 四条边界上有格子是 (A)
(res=impossible) 没有 (A)
(res=4) 除上述情况外
方案挺好构造的,自己 yy 吧
C
对每条边分开考虑,设这条边断开后树的两部分大小分别为 (x,y)
1、取最大值的时候这条边算了 (min(x,y)) 次
2、对于最小值,如果这条边经过了 (ge 2) 次,这些点对为 ((x_1,y_1),(x_2,y_2)...) 其中 (x_i) 属于同一部分,(y_i) 属于另一部分。那么不妨将 (x_i,y_i) 分别两两配对,更改为 ((x_1,x_2),(x_3,x_4)...(y_1,y_2),(y_3,y_4)...)。这样更改后经过这条边的次数减少了,对于其他边也不会更劣。由此得出,如果 (x\%2=1),要算 (1) 次,否则经过 (0) 次
D
(f_{i,j}) 表示以 (i) 为根的子树中,分了 (j) 块时,符合条件的块数最大值 和 (i) 所在块当前 (w) 和 (b) 差的最大值 (这是一个 (pair))
也就是说,(dp) 的时候用了一个贪心。可以这样理解,有两个最优条件:1、符合条件块数最多 2、当前差值最大
很明显,(1) 的优先级高于 (2),因为差值再大也只能把块数 (+1),所以先让 (1) 最优后再考虑 (2)
转移的时候就和树上背包一样枚举父亲和儿子的 (j)(第二维),分两种情况(父亲和儿子在不在一个块内)讨论,复杂度 (O(n^2))
#include <bits/stdc++.h>
using namespace std;
#define int long long
void read (int &x) {
char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 3005, M = N << 1;
int n, m, a[N], b[N];
int cnt, h[N], nxt[M], to[M], sz[N];
void add (int u, int v) {
to[++cnt] = v, nxt[cnt] = h[u], h[u] = cnt;
}
pair<int, int> f[N][N], t[N];
#define fi first
#define se second
void dfs (int u, int la) {
sz[u] = 1; f[u][1] = {0, a[u]};
for (int i = h[u], v; i; i = nxt[i]) {
if ((v = to[i]) == la) continue;
dfs (v, u);
for (int j = 1; j <= sz[u] + sz[v]; ++j) t[j] = {-1, 0}; // 弄成-1因为有些状态不可达
for (int j = 1; j <= sz[u]; ++j)
for (int k = 1; k <= sz[v]; ++k) {
t[j + k] = max (t[j + k], {f[v][k].fi + f[u][j].fi + (f[v][k].se > 0), f[u][j].se});
t[j + k - 1] = max (t[j + k - 1], {f[v][k].fi + f[u][j].fi, f[v][k].se + f[u][j].se});
}
sz[u] += sz[v];
for (int j = 1; j <= sz[u]; ++j) f[u][j] = t[j];
}
}
signed main() {
int T; read (T);
while (T--) {
read (n), read (m); cnt = 0;
for (int i = 1; i <= n; ++i) h[i] = 0;
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= m; ++j) f[i][j] = {0, 0};
for (int i = 1; i <= n; ++i) read (b[i]);
for (int i = 1; i <= n; ++i) read (a[i]), a[i] -= b[i];
for (int i = 1, u, v; i < n; ++i)
read (u), read (v), add (u, v), add (v, u);
dfs (1, 0);
printf ("%lld
", f[1][m].fi + (f[1][m].se > 0));
}
return 0;
}
E
对于一个形态固定的电阻网络,设 (g(R)) 表示电路阻值为 (R) 时各个电阻阻值和的最小值。把这个带括号的序列对应到树上,要求的就是根节点的 (g) 函数关系式。更进一步,不难发现 (g) 其实是一个正比例函数,设 (f(x)) 为节点 (x) 处的 (g) 函数系数。如果没有整数的限制,考虑如何求得 (f)。
1、如果 (x) 节点的儿子是串联关系,显然,最优的办法是把所有的电阻都加在 (f) 最小的那个儿子上,其他放空。即 (f_x=min(f_y))
2、如果是串联,列出两个式子,(frac{1}{R_x}=sumfrac{1}{R_y},sum=sum f_yR_y,yin son(x)),由第一个式子可得 (R_xsumfrac{1}{R_y}=1),联想到“1”的妙用,把这个东西放入第二个式子:(sum=R_xsumfrac{1}{R_y}sum f_yR_y=R_xsum(frac{1}{sqrt{R_y}})^2sum sqrt{f_yR_y}^2)。套用柯西不等式的取等条件,当取值最小时有 (frac{1}{sqrt{f_yR_y^2}}=m)((m) 为定值)。那么 (frac{1}{R_y}propto sqrt{f_y}),而 (sumfrac{1}{R_y}) 为定值,那么 (frac{1}{R_y}) 按照比例分配。可得 (R_y=R_xfrac{sumsqrt{f_z}}{sqrt{f_y}})。带入可得 (sqrt{f_x}=sumsqrt{f_y})
再回来看整数的限制
1、对于叶子节点有 (f_x=1),是完全平方数
2、对于第一种转移,如果 (f_y) 全是完全平方数那么 (f_x) 也一定是
3、对于第二种转移,(f_y) 是完全平方数,(sqrt{f_y}) 是整数,(sqrt{f_x}) 是整数,(f_x) 依然是一个完全平方数
那么,整数的限制作废了
计算具体阻值的时候可以按照构造方法往里面带,更简洁的做法:情况 (1) 的构造方法意味着多个串联的电阻中只会选一个。根据电路分析的基础知识可以发现,最后选出的电阻都可以看作并联关系,那么看一下有几个电阻用到然后...
#include <bits/stdc++.h>
using namespace std;
#define int long long
void read (int &x) {
char ch = getchar(); x = 0; while (!isdigit(ch)) ch = getchar();
while (isdigit(ch)) x = x * 10 + ch - 48, ch = getchar();
} const int N = 2e5 + 5, M = 5e5 + 5;
int n, dfn, st[N], tag[N], to[N], f[N], vis[N], id[N];
char a[M]; vector<int> g[N];
#define pb push_back
void build () {
int num = 0, tp = 0;
for (int i = 1; i <= n; ++i) {
if (a[i] == '(') {
g[++num].clear();
if (tp) g[st[tp]].pb (num);
st[++tp] = num;
to[num] = vis[num] = f[num] = 0;
} else if (a[i] == '*') {
g[++num].clear();
if (tp) g[st[tp]].pb (num);
to[num] = vis[num] = f[num] = 0;
}
else if (a[i] == 'S') tag[st[tp]] = 0;
else if (a[i] == 'P') tag[st[tp]] = 1;
else if (a[i] == ')') --tp;
}
}
void dfs (int u) {
if (!g[u].size()) { f[u] = 1, id[u] = ++dfn; return; }
for (int v : g[u]) dfs (v);
if (tag[u] == 0) { // 串联直接找系数最小的
int mn = 0;
for (int v : g[u]) if (f[mn] > f[v]) mn = v;
to[u] = mn, f[u] = f[mn];
} else { // 串联直接加和
for (int v : g[u]) f[u] += f[v];
}
} int cnt;
void getcnt (int u) {
if (!g[u].size()) { vis[id[u]] = 1, ++cnt; return; }
if (!tag[u] && to[u]) getcnt (to[u]);
else for (int v : g[u]) getcnt (v);
}
signed main() {
int T; read (T);
while (T--) {
int R; scanf ("%lld", &R);
cin.getline (a, 5e5); n = strlen (a + 1);
build (); f[0] = 2e9;
dfn = 0, dfs (1);
cnt = 0; getcnt (1);
printf ("REVOLTING ");
for (int i = 1; i <= dfn; ++i)
printf ("%lld ", vis[i] ? cnt * R : 0ll);
putchar ('
');
}
return 0;
}
F
这个游戏可以看成有三个可以转动的环,左右分别一个小的,整体是一个大的。说“转动”是因为把 (E) 沿着环转一圈相当于把换上的数字转动一个位置
何时无解?设 (A) 为按照行列顺次取数后得到的排列,怎样操作 (A) 的逆序对个数的奇偶性都不变,最终状态中显然没有逆序对,所以逆序对个数为奇数时无解。为偶数时如何构造答案?
我们规定 (E) 在第二行中间的位置时为标准状态,每次转动完依旧是标准状态。现在要做的就是把逆序对个数减少为 (0),然后再把 (E) 移到最右边
设第一行中间位置为 (W)。引进两个成套操作
1、对于左半部分的任意位置两个点 (a,b) 和右部任意一个点 (c)
转动左轮,使 (b) 到 (W)
转动右轮,使 (c) 到 (W)
转动左轮,使 (a) 到 (W)
转动右轮,使 (b) 到 (W),此时 (a) 到达原来 (c) 的位置
转动左轮,使 (b) 到原来 (a) 的位置,此时 (c) 一定在原来 (b) 的位置(距离相同)
这样,((a,b,c)) 成了 ((c,a,b)),左一右二的类似
通过这个操作,可以把应该在左部的数换到左部,该在右边的扔到右边
2、对于左部两个点 (a,b),和右部两个点 (c,d),可以通过 (2) 次 (1) 操作将 ((a,b)(c,d)) 变成 ((b,a)(d,c))
通过这个操作可以消除同一个部分之间的逆序对
具体的就不说了,咕咕咕