Atcoder Regular Contest 97
Problem F. Monochrome Cat
若原树中存在一个黑色的叶子,那么我们可以将其删去而不影响答案。
重复这个过程,我们会得到一棵仅包含白色叶子的树,显然,这棵树中所有的节点都会被访问至少一次。
対该树进行树形 DP ,记:
$ dp_{i,0} $ 表示从 $ i $ 号点的父亲出发,完成对 $ i $ 子树的访问后回到 $ i $ 号点的父亲的最小用时。
$ dp_{i,1} $ 表示从 $ i $ 号点的父亲出发,完成对 $ i $ 子树的访问后停在 $ i $ 号点的子树内部的最小用时。
$ dp_{i,2} $ 表示从 $ i $ 号点出发,完成对 $ i $ 号点父亲所在的 $ i $ 号点的子树的访问后回到 $ i $ 号点的最小用时。
获取答案时枚举路径两端的 LCA ,通过上述 DP 结果更新答案。
时间复杂度 $ O(N) $ 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
const long long INF = 1e18;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
vector <int> a[MAXN], b[MAXN];
int n, d[MAXN]; char col[MAXN];
long long dp[MAXN][3], ans;
bool vis[MAXN];
void work(int pos, int fa) {
for (unsigned i = 0; i < a[pos].size(); i++)
if (fa != a[pos][i] && !vis[a[pos][i]]) work(a[pos][i], pos);
bool now = col[pos] == 'B';
long long tans = 2;
for (unsigned i = 0; i < a[pos].size(); i++)
if (fa != a[pos][i] && !vis[a[pos][i]]) {
tans += dp[a[pos][i]][0];
now ^= true;
}
dp[pos][0] = tans + now;
dp[pos][1] = tans + now - 1;
for (unsigned i = 0; i < a[pos].size(); i++)
if (fa != a[pos][i] && !vis[a[pos][i]]) {
chkmin(dp[pos][1], tans + !now - 1 - dp[a[pos][i]][0] + dp[a[pos][i]][1]);
}
}
void getans(int pos, int fa) {
if (fa == 0) {
bool now = col[pos] == 'W'; int cnt = 0;
long long tans = 0;
for (unsigned i = 0; i < a[pos].size(); i++)
if (fa != a[pos][i] && !vis[a[pos][i]]) {
tans += dp[a[pos][i]][0];
now ^= true;
cnt++;
}
for (unsigned i = 0; i < a[pos].size(); i++)
if (fa != a[pos][i] && !vis[a[pos][i]]) {
dp[a[pos][i]][2] = dp[pos][0] - dp[a[pos][i]][0] - !now + now;
getans(a[pos][i], pos);
}
if (cnt == 1) {
for (unsigned i = 0; i < a[pos].size(); i++)
if (fa != a[pos][i] && !vis[a[pos][i]]) {
chkmin(ans, dp[a[pos][i]][1] + 1);
return;
}
}
chkmin(ans, tans + now);
long long Max = -1e18, Nax = -1e18;
for (unsigned i = 0; i < a[pos].size(); i++)
if (fa != a[pos][i] && !vis[a[pos][i]]) {
long long tmp = dp[a[pos][i]][0] - dp[a[pos][i]][1];
if (tmp > Max) {
Nax = Max;
Max = tmp;
} else chkmax(Nax, tmp);
}
chkmin(ans, 1 + tans + !now - Max - Nax);
} else {
for (unsigned i = 0; i < a[pos].size(); i++)
if (fa != a[pos][i] && !vis[a[pos][i]]) {
dp[a[pos][i]][2] = dp[pos][0] - dp[a[pos][i]][0] + dp[pos][2];
getans(a[pos][i], pos);
}
bool now = col[pos] == 'B';
long long tans = dp[pos][2];
for (unsigned i = 0; i < a[pos].size(); i++)
if (fa != a[pos][i] && !vis[a[pos][i]]) {
tans += dp[a[pos][i]][0];
now ^= true;
}
chkmin(ans, tans + now);
long long Max = -1e18, Nax = -1e18;
for (unsigned i = 0; i < a[pos].size(); i++)
if (fa != a[pos][i] && !vis[a[pos][i]]) {
long long tmp = dp[a[pos][i]][0] - dp[a[pos][i]][1];
if (tmp > Max) {
Nax = Max;
Max = tmp;
} else chkmax(Nax, tmp);
}
chkmin(ans, 1 + tans + !now - Max - Nax);
}
}
int main() {
read(n);
for (int i = 1; i <= n - 1; i++) {
int x, y; read(x), read(y);
d[x]++, d[y]++;
a[x].push_back(y);
a[y].push_back(x);
}
scanf("%s", col + 1);
static int q[MAXN];
int l = 0, r = -1;
for (int i = 1; i <= n; i++)
if (d[i] <= 1) q[++r] = i;
while (l <= r) {
int tmp = q[l++];
if (col[tmp] == 'B') {
vis[tmp] = true;
for (unsigned i = 0; i < a[tmp].size(); i++)
if (--d[a[tmp][i]] == 1) q[++r] = a[tmp][i];
}
}
for (int i = 1; i <= n; i++)
if (!vis[i]) {
work(i, 0);
ans = INF;
getans(i, 0);
writeln(ans);
return 0;
}
printf("0\n");
return 0;
}
Atcoder Regular Contest 98
Problem F. Donation
若我们经过了一个点多次,那么我们不必在最后一次经过该点之前对其进行捐献,而可以等到最后一次经过它时再进行捐献。
因此,令 $ C_i=max{A_i-B_i,0} $ ,如果我们默认只有最后一次经过一个点时才会对其进行捐献,对 $ A_i $ 的限制可以等价地表示为在任何时刻,一个处于 $ i $ 点的人必须持有 $ C_i $ 单位的货币。
考虑 $ C_x $ 最大的点 $ x $ ,对 $ x $ 进行捐献后,我们将不再经过 $ x $ ,也就是说,我们会进入删去 $ x $ 所分割出的一个连通分量中,不再出来,不妨设该连通分量为 $ G $ 。
我们发现,在对 $ x $ 进行捐献之前对 $ G $ 内部的点进行的捐献可以推迟到对 $ x $ 进行捐献之后,因此我们不失一般性地认为我们只会先对删去 $ x $ 所分割出的其他连通分量捐献,然后对 $ x $ 捐献,最后对 $ G $ 捐献。
我们建出如下树形结构:将当前连通分量内 $ C_i $ 最大的点作为根,并将其在原图中删去,对于新产生的各个连通分量分别建树,将它们的根部连向当前联通分量的根部。
这棵树可以通过按 $ C_i $ 从小到大考虑所有的点在 $ O(NLogN) $ 的时间内构建出来。
然后记 $ dp_i $ 表示完成对当前连通分量的捐献需要具有的最少资金数, $ sum_i $ 表示完成对当前连通分量的捐献将会花费的最少资金数,简单树形 DP 即可。
时间复杂度 $ O(NLogN) $ 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 100005;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int n, m, root, a[MAXN], b[MAXN], f[MAXN], pos[MAXN], fa[MAXN];
vector <int> e[MAXN], t[MAXN];
long long dp[MAXN], sum[MAXN];
void work(int pos) {
if (t[pos].size() == 0) {
dp[pos] = a[pos] + b[pos];
sum[pos] = b[pos];
return;
}
sum[pos] = b[pos];
for (unsigned i = 0; i < t[pos].size(); i++) {
work(t[pos][i]);
sum[pos] += sum[t[pos][i]];
}
dp[pos] = 1e18;
for (unsigned i = 0; i < t[pos].size(); i++)
chkmin(dp[pos], max(dp[t[pos][i]], 1ll * a[pos]) + sum[pos] - sum[t[pos][i]]);
}
bool cmp(int x, int y) {
return a[x] < a[y];
}
int F(int x) {
if (f[x] == x) return x;
else return f[x] = F(f[x]);
}
int main() {
read(n), read(m);
for (int i = 1; i <= n; i++) {
read(a[i]), read(b[i]);
a[i] = max(a[i] - b[i], 0);
pos[i] = i;
}
sort(pos + 1, pos + n + 1, cmp);
for (int i = 1; i <= m; i++) {
int x, y;
read(x), read(y);
e[x].push_back(y);
e[y].push_back(x);
}
memset(fa, -1, sizeof(fa));
for (int i = 1; i <= n; i++) {
int tmp = pos[i];
f[tmp] = tmp;
for (unsigned j = 0; j < e[tmp].size(); j++)
if (fa[F(e[tmp][j])] == 0) fa[F(e[tmp][j])] = tmp, f[F(e[tmp][j])] = tmp;
fa[tmp] = 0;
}
for (int i = 1; i <= n; i++)
if (fa[i]) t[fa[i]].push_back(i);
else root = i;
work(root);
writeln(dp[root]);
return 0;
}
Atcoder Regular Contest 99
Problem F. Eating Symbols Hard
考虑对一个方案进行哈希:令一个方案的哈希值为 $ Hash(S)=\sum_{i=-\infty}{+\infty}A_iXi\ Mod\ M $ ,其中 $ X $ 为一个随机数, $ M $ 为一个大质数。该哈希函数有以下性质:
1 、 $ Hash(+S)=Hash(S)+1 $
2 、 $ Hash(-S)=Hash(S)-1 $
3 、 $ Hash(>S)=Hash(S)*X $
4 、 $ Hash(<S)=Hash(S)*X^{-1} $
定义函数 $ pls(Hash(S)) $ 为在 $ S $ 前添加一个 $ + $ 后得到的字符串的哈希值。
显然有 $ pls(Hash(S))=Hash(S)+1 $ ,也即 $ Hash(S)=pls(Hash(S))-1 $ 。
定义 $ pls^{-1}(Hash(S)) $ 为在 $ S $ 前删除一个 $ + $ 后得到的字符串的哈希值,称为上述函数的逆函数,有 $ pls^{-1}(Hash(S))=Hash(S)-1 $ 。
类似的,我们可以对剩余的三个操作也定义以上函数。
令 $ f_{r,l}(x) $ 表示用 $ S_r $ 到 $ S_l $ 的字符对应的函数依次操作 $ x $ 得到的结果。
令 $ f^{-1}_{r,l}(x) $ 表示用 $ S_r $ 到 $ S_l $ 的字符对应的逆函数依次操作 $ x $ 得到的结果。
令 $ c=f_{n,1}(0) $ ,我们希望统计 $ f_{j,i}(0)=c $ 的 $ (i,j)(i≤j) $ 的数量。
$ f_{j,i}(0)=c\Leftrightarrow f{-1}_{i,N}(f_{j,i}(0))=f{-1}{i,N}©\Leftrightarrow f{-1}_{j+1,N}(0)=f{-1}{i,N}© $
对于所有 $ i $ ,分别计算 $ a_i=f{-1}_{i,N}©,b_i=f{-1}_{i+1,N}(0) $ ,然后统计 $ a_i=b_j(i≤j) $ 的 $ (i,j) $ 数量即可。
注意到所有函数和逆函数均为一次函数,因此它们嵌套起来同样是一次函数,维护一次函数即可计算 $ a_i $ 和 $ b_i $ 。
取 $ Cnt $ 个不同的 $ X $ ,减小哈希冲突的概率。
时间复杂度 $ O(NLogN\times Cnt) $ ,取 $ Cnt=6 $ 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 250005;
const int P = 1e9 + 7;
const int val[6] = {18, 40, 23, 2333333, 58, 720};
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
struct Hash {int val[6]; };
struct info {Hash k, b; };
bool operator < (Hash a, Hash b) {
for (int i = 0; i <= 5; i++)
if (a.val[i] != b.val[i]) return a.val[i] < b.val[i];
return false;
}
map <Hash, int> mp;
char s[MAXN];
int inv[6];
Hash unit() {
Hash ans;
memset(ans.val, 0, sizeof(ans.val));
return ans;
}
info unitinfo() {
info ans;
for (int i = 0; i <= 5; i++) {
ans.b.val[i] = 0;
ans.k.val[i] = 1;
}
return ans;
}
Hash operator * (Hash x, info a) {
Hash ans;
for (int i = 0; i <= 5; i++)
ans.val[i] = (1ll * a.k.val[i] * x.val[i] + a.b.val[i]) % P;
return ans;
}
int power(int x, int y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % P;
else return 1ll * tmp * tmp % P * x % P;
}
void pls(Hash &tmp) {
for (int i = 0; i <= 5; i++)
tmp.val[i] = (tmp.val[i] + 1) % P;
}
void mns(Hash &tmp) {
for (int i = 0; i <= 5; i++)
tmp.val[i] = (tmp.val[i] + P - 1) % P;
}
void rit(Hash &tmp) {
for (int i = 0; i <= 5; i++)
tmp.val[i] = (1ll * tmp.val[i] * val[i]) % P;
}
void lft(Hash &tmp) {
for (int i = 0; i <= 5; i++)
tmp.val[i] = (1ll * tmp.val[i] * inv[i]) % P;
}
void pls(info &tmp) {
for (int i = 0; i <= 5; i++)
tmp.b.val[i] = (tmp.b.val[i] + tmp.k.val[i]) % P;
}
void mns(info &tmp) {
for (int i = 0; i <= 5; i++)
tmp.b.val[i] = (tmp.b.val[i] + P - tmp.k.val[i]) % P;
}
void rit(info &tmp) {
for (int i = 0; i <= 5; i++)
tmp.k.val[i] = (1ll * tmp.k.val[i] * val[i]) % P;
}
void lft(info &tmp) {
for (int i = 0; i <= 5; i++)
tmp.k.val[i] = (1ll * tmp.k.val[i] * inv[i]) % P;
}
int main() {
for (int i = 0; i <= 5; i++)
inv[i] = power(val[i], P - 2);
int n; read(n);
scanf("\n%s", s + 1);
Hash now = unit();
for (int i = n; i >= 1; i--) {
if (s[i] == '+') pls(now);
if (s[i] == '-') mns(now);
if (s[i] == '>') rit(now);
if (s[i] == '<') lft(now);
}
Hash tmp = unit(); mp[tmp]++;
info Now = unitinfo(), Tmp = unitinfo();
long long ans = 0;
for (int i = n; i >= 1; i--) {
if (s[i] == '-') pls(Now), pls(Tmp);
if (s[i] == '+') mns(Now), mns(Tmp);
if (s[i] == '<') rit(Now), rit(Tmp);
if (s[i] == '>') lft(Now), lft(Tmp);
ans += mp[now * Now];
mp[tmp * Tmp]++;
}
writeln(ans);
return 0;
}
Atcoder Regular Contest 100
Problem F. Colorful Sequences
考虑一个子问题,如何求解 Colorful 的序列个数。
可以通过 DP 完成,记 表示长度为 的, Colorful 程度为 的序列个数,其中 Colorful 程度 被定义为最大的 ,使得序列最后 个元素互不相同,特别地,序列若已经是 Colorful 的,其 Colorful 程度为 。显然有 转移,并且可以用前缀和优化至均摊 ,因此,解决该子问题的时间复杂度为 。
考虑原问题,枚举子串出现的位置,计算在剩余部分填入数字,使得得到的序列 Colorful 的方案数,求和即为答案。
分以下三种情况考虑:
、给定序列是 Colorful 的,显然,答案为 。
、给定序列包含相同的元素,那么,判断序列 Colorful 的区间不可能横跨出现位置的两侧,可以分为左右两个独立的子问题,用与上文相同的方式解决。
、给定序列中的元素两两不同,此时,序列本身是什么已经不重要了。可以先用 中的计算方式计算,剩余没有被计算的部分即为判断序列 Colorful 的区间必须横跨出现位置的两侧的情况,枚举判断序列 Colorful 的区间最早出现的位置,则两侧同样可以分为左右两个独立的子问题,可以通过将上文 DP 中的边反向完成。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 25005;
const int MAXK = 405;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
template <typename T> void write(T x) {
if (x < 0) x = -x, putchar('-');
if (x > 9) write(x / 10);
putchar(x % 10 + '0');
}
template <typename T> void writeln(T x) {
write(x);
puts("");
}
int power(int x, int y) {
if (y == 0) return 1;
int tmp = power(x, y / 2);
if (y % 2 == 0) return 1ll * tmp * tmp % P;
else return 1ll * tmp * tmp % P * x % P;
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
int n, k, m, a[MAXN];
int solveb() {
static int f[MAXN][MAXK], g[MAXN][MAXK], cnt[MAXK];
int l = m, r = m;
memset(cnt, 0, sizeof(cnt));
for (int i = 1; i <= m; i++)
if (++cnt[a[i]] == 2) {
l = i - 1;
break;
}
memset(cnt, 0, sizeof(cnt));
for (int i = m; i >= 1; i--)
if (++cnt[a[i]] == 2) {
r = m - i;
break;
}
f[0][l] = g[0][r] = 1;
for (int i = 1; i <= n; i++) {
int sf = 0, sg = 0;
for (int j = k; j >= 1; j--) {
if (j != k) update(sf, f[i - 1][j]);
if (j != k) update(sg, g[i - 1][j]);
f[i][j] = sf, update(f[i][j], 1ll * f[i - 1][j - 1] * (k - j + 1) % P);
g[i][j] = sg, update(g[i][j], 1ll * g[i - 1][j - 1] * (k - j + 1) % P);
}
update(f[i][k], 1ll * f[i - 1][k]* k % P);
update(g[i][k], 1ll * g[i - 1][k]* k % P);
}
int ans = 0;
for (int l = 0, r = n - m; r >= 0; l++, r--) {
int sf = 0, sg = 0;
for (int i = 1; i <= k - 1; i++) {
update(sf, f[l][i]);
update(sg, g[r][i]);
}
update(ans, 1ll * sf * g[r][k] % P);
update(ans, 1ll * sg * f[l][k] % P);
update(ans, 1ll * f[l][k] * g[r][k] % P);
}
return ans;
}
int solveu() {
static int f[MAXN][MAXK];
for (int i = 1; i <= k - 1; i++)
f[0][i] = 1;
for (int i = 1; i <= n; i++) {
int sf = 0;
for (int j = 1; j <= k - 1; j++) {
update(sf, f[i - 1][j]), f[i][j] = sf;
update(f[i][j], 1ll * f[i - 1][j + 1] * (k - j) % P);
}
f[i][k] = 1ll * f[i - 1][k] * k % P;
}
int ans = solveb(), fac = 1;
for (int i = 1; i <= k - m; i++)
fac = 1ll * fac * i % P;
for (int l = 0, r = n - m; r >= 0; l++, r--)
for (int i = 1, j = k - m - i; j >= 1; i++, j--)
if (i <= l && j <= r) update(ans, 1ll * fac * f[l - i][k - 1] % P * f[r - j][j + m] % P);
return ans;
}
bool Unique() {
static int cnt[MAXK];
for (int i = 1; i <= m; i++)
cnt[a[i]]++;
for (int i = 1; i <= k; i++)
if (cnt[i] >= 2) return false;
return true;
}
bool contain() {
static int cnt[MAXK], l = 1;
for (int i = 1; i <= m; i++) {
if (++cnt[a[i]] == 2) {
while (cnt[a[i]] == 2) cnt[a[l++]]--;
}
if (i - l + 1 == k) return true;
}
return false;
}
int main() {
read(n), read(k), read(m);
for (int i = 1; i <= m; i++)
read(a[i]);
if (contain()) writeln(1ll * (n - m + 1) * power(k, n - m) % P);
else if (Unique()) writeln(solveu());
else writeln(solveb());
return 0;
}
Atcoder Regular Contest 101
Problem E. Ribbons on Tree
不考虑所有边都被覆盖的限制,记
表示
的点的树的匹配方案数,显然有
考虑对覆盖的条件进行容斥,枚举一个边集 ,强制其不被覆盖,计算方案数,并乘上系数 计入答案。
则显然可以用树上背包优化。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 5005;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int dp[MAXN][MAXN];
int size[MAXN], f[MAXN];
vector <int> a[MAXN];
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
void work(int pos, int fa) {
dp[pos][1] = 1, size[pos] = 1;
for (auto x : a[pos])
if (x != fa) {
work(x, pos);
static int tmp[MAXN];
memset(tmp, 0, sizeof(tmp));
for (int i = 0; i <= size[pos]; i++)
for (int j = 0; j <= size[x]; j++)
update(tmp[i + j], 1ll * dp[pos][i] * dp[x][j] % P);
memcpy(dp[pos], tmp, sizeof(tmp));
size[pos] += size[x];
}
for (int i = 1; i <= size[pos]; i++)
update(dp[pos][0], P - 1ll * dp[pos][i] * f[i] % P);
}
int main() {
int n; read(n), f[0] = 1;
for (int i = 2; i <= n; i += 2)
f[i] = (i - 1ll) * f[i - 2] % P;
for (int i = 1; i <= n - 1; i++) {
int x, y; read(x), read(y);
a[x].push_back(y);
a[y].push_back(x);
}
work(1, 0);
int ans = 0;
for (int i = 1; i <= n; i++)
update(ans, 1ll * dp[1][i] * f[i] % P);
cout << ans << endl;
return 0;
}
Problem F. Robots and Exits
显然,各个机器人只能走到两侧的出口,输入可以等价地描述为数组 ,分别表示第 个机器人左右两侧第一个出口的距离。
考虑对二元组 排序,并去重,选择一个子集 ,判断是否可以使得 中所有机器人走到左侧出口,其余机器人走到右侧出口。
则
需要满足如下两点:
、若
,且
,则
。
、若
,且
,则
。
由此可以设计 DP ,记 表示考虑所有 的位置 ,不在 中的元素最大为 的方案数,显然有平方转移,且可以用树状数组简单优化。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
const int P = 1e9 + 7;
typedef long long ll;
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
void update(int &x, int y) {
x += y;
if (x >= P) x -= P;
}
struct BinaryIndexTree {
int n, a[MAXN];
void init(int x) {
n = x;
memset(a, 0, sizeof(a));
}
void modify(int x, int d) {
for (int i = x; i <= n; i += i & -i)
update(a[i], d);
}
int query(int x) {
int ans = 0;
for (int i = x; i >= 1; i -= i & -i)
update(ans, a[i]);
return ans;
}
} BIT;
int n, m, t, x[MAXN], y[MAXN];
pair <int, int> a[MAXN];
int main() {
read(n), read(m);
for (int i = 1; i <= n; i++)
read(x[i]);
for (int i = 1; i <= m; i++)
read(y[i]);
for (int i = 1; i <= n; i++) {
int pos = upper_bound(y + 1, y + m + 1, x[i]) - y;
if (pos == m + 1 || pos == 1) continue;
a[++t] = make_pair(x[i] - y[pos - 1], y[pos] - x[i]);
}
if (t == 0) {
cout << 1 << endl;
return 0;
}
n = t; int cnt = 0;
set <int> st; map <int, int> home;
for (int i = 1; i <= n; i++)
st.insert(a[i].second);
for (auto x : st) home[x] = ++cnt;
for (int i = 1; i <= n; i++)
a[i].second = home[a[i].second];
BIT.init(cnt), BIT.modify(1, 1);
sort(a + 1, a + n + 1);
n = unique(a + 1, a + n + 1) - a - 1;
int ans = 1; static int dp[MAXN];
for (int i = 1, nxt; i <= n; i = nxt + 1) {
nxt = i; while (nxt + 1 <= n && a[nxt + 1].first == a[i].first) nxt++;
for (int j = i; j <= nxt; j++) {
dp[j] = BIT.query(a[j].second);
update(ans, dp[j]);
}
for (int j = i; j <= nxt; j++)
BIT.modify(a[j].second + 1, dp[j]);
}
cout << ans << endl;
return 0;
}
Atcoder Regular Contest 102
Problem F. Revenge of BBuBBBlesort!
定义一次交换 的操作为操作 。
可以发现,进行操作 后, 始终成立,从而操作 均不会出现,因此,位置 上的元素保持不变。
那么,我们可以将数组分为若干个极小的值域与定义域相同的区间,分别判断。
在每一段区间内,条件 “奇数位的值为下标”、“偶数位的值为下标” ,至少要有一者满足。
以 “偶数位的值为下标” 为例,则奇数位不能存在值为下标的位置,否则该位置将不能移动,又因为该区间是极小的值域与定义域相同的区间,从而其一定会阻挡两侧元素的移动。
可以发现,元素一旦经过交换,不能回到原来的位置,因此元素只能向一个方向移动。
因此,答案为 Yes 的必要条件为奇数位要向右的元素和要向左的元素分别单调。
同时,该条件也是充分的,我们可以每次交换一对分别要向右、要向左的元素进行排序。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 3e5 + 5;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
int a[MAXN];
int main() {
int n; read(n);
for (int i = 1; i <= n; i++) {
read(a[i]);
if (a[i] % 2 != i % 2) {
puts("No");
return 0;
}
}
int last = 0, Max = 0;
for (int i = 1; i <= n; i++) {
chkmax(Max, a[i]);
if (Max == i) {
bool flg = true, glg = true;
for (int j = last + 1; j <= i; j++)
if (a[j] != j) {
if (j & 1) flg = false;
else glg = false;
}
if (!flg && !glg) {
puts("No");
return 0;
}
if (flg && !glg) {
int Max = 0, Nax = 0;
for (int j = last + 1; j <= i; j++)
if (j % 2 == 0) {
if (a[j] == j) {
puts("No");
return 0;
}
if (a[j] > j) {
if (a[j] < Max) {
puts("No");
return 0;
}
Max = a[j];
} else {
if (a[j] < Nax) {
puts("No");
return 0;
}
Nax = a[j];
}
}
}
if (glg && !flg) {
int Max = 0, Nax = 0;
for (int j = last + 1; j <= i; j++)
if (j % 2 == 1) {
if (a[j] == j) {
puts("No");
return 0;
}
if (a[j] > j) {
if (a[j] < Max) {
puts("No");
return 0;
}
Max = a[j];
} else {
if (a[j] < Nax) {
puts("No");
return 0;
}
Nax = a[j];
}
}
}
last = i;
}
}
puts("Yes");
return 0;
}
Atcoder Regular Contest 103
Problem D. Robot Arms
首先,我们可以初步考虑无解的情况。
引理 1: 若将各个点 按照 的奇偶性黑白染色,则对于确定的 数组,无论选择何种 ,至多只能到达一种颜色的点。
证明: 由染色的方式,若 为偶数,则无论走向 中的哪一个方向,到达的点均与起始点同色;而若 为奇数,则无论走向 中的哪一个方向,到达的点均与起始点异色。
引理 1 给出了一个无解的充分条件,即存在颜色不同的点。
通过下一节的构造算法,我们可以看到,若引理 1 不能判断输入无解,我们都可以通过该算法构造出一组解,因此这个条件同样是必要的。
由于题目对 的限制很紧,大约只有 ,可能的构造方式实际上不多。为了在限制内完成构造,可以想到采用 的构造方式。
记 表示 , 表示在 中多加入一个 得到的集合。
记 表示对于所有的 的设置方案, 可以达到的点集。
记 表示对于所有的 的设置方案, 可以达到的点集。
定理 1:
,当且仅当
且
;
,当且仅当 且 。
证明: 由引理 1 的证明,定理 1 的必要性是显然的,考虑证明其充分性。
考虑归纳法,验证得 时,定理 1 成立,因此,我们只需要在定理 1 对 成立的条件下证明定理 1 对 成立。
由于问题在四个象限是对称的,不失一般性地,考虑 的情况。
因为 , 且 , 与 中至少有一者成立,不失一般性地,令 。
那么, ,并且 。
因此 ,而 ,从而 。
同样地,因为 , 且 , 与 中至少有一者成立,不失一般性地,令 。
那么, ,并且 。
因此 ,而 ,从而 。
由定理 1 的证明,对于不能判断无解的输入,我们也可以得到一个可行的构造。
时间复杂度 ,其中 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
bool check(ll x, ll y, ll s) {
if (x < 0) x = -x;
if (y < 0) y = -y;
return x + y <= s;
}
ll x[MAXN], y[MAXN];
int main() {
int n; read(n);
int cnt[2] = {0, 0};
for (int i = 1; i <= n; i++) {
read(x[i]), read(y[i]);
cnt[(x[i] + y[i]) % 2 == 0]++;
}
if (cnt[0] != 0 && cnt[1] != 0) {
puts("-1");
return 0;
}
vector <ll> ans, sum;
ans.push_back(1);
if (cnt[1]) ans.push_back(1);
for (int i = 2; i <= 38; i++)
ans.push_back(1ll << (i - 1));
sum.push_back(0);
for (unsigned i = 1; i < ans.size(); i++)
sum.push_back(sum.back() + ans[i - 1]);
cout << ans.size() << endl;
for (auto x : ans)
printf("%lld ", x);
printf("\n");
for (int i = 1; i <= n; i++) {
string res; res.resize(ans.size());
ll nowx = x[i], nowy = y[i];
for (int j = ans.size() - 1; j >= 0; j--) {
if (check(nowx + ans[j], nowy, sum[j])) {
res[j] = 'L';
nowx += ans[j];
} else if (check(nowx - ans[j], nowy, sum[j])) {
res[j] = 'R';
nowx -= ans[j];
} else if (check(nowx, nowy + ans[j], sum[j])) {
res[j] = 'D';
nowy += ans[j];
} else if (check(nowx, nowy - ans[j], sum[j])) {
res[j] = 'U';
nowy -= ans[j];
}
}
cout << res << endl;
}
return 0;
}
Problem F. Distance Sums
考虑一条连接 两点的边 ,记 侧的点数为 , 侧的点数为 ,则有 。因此,可以发现,叶子节点具有最大的 。
找到最大的 的点 ,我们可以计算出与其直接相连的点的 ,由于 互不相同,我们可以直接确定这个点,从而删去 。重复这个考虑过程,我们可以将树构造出来。
由于构造的过程是基于 的相对大小关系的,还需要检查一下 是否正确。
时间复杂度 。
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 5;
typedef long long ll;
template <typename T> void chkmax(T &x, T y) {x = max(x, y); }
template <typename T> void chkmin(T &x, T y) {x = min(x, y); }
template <typename T> void read(T &x) {
x = 0; int f = 1;
char c = getchar();
for (; !isdigit(c); c = getchar()) if (c == '-') f = -f;
for (; isdigit(c); c = getchar()) x = x * 10 + c - '0';
x *= f;
}
ll d[MAXN]; bool vis[MAXN]; map <ll, int> home;
vector <int> a[MAXN]; pair <ll, int> b[MAXN];
int f[MAXN], s[MAXN], size[MAXN];
void dfs(int pos, int fa) {
size[pos] = 1;
for (auto x : a[pos])
if (x != fa) {
dfs(x, pos);
size[pos] += size[x];
}
}
int main() {
int n; read(n);
for (int i = 1; i <= n; i++) {
read(d[i]);
f[i] = i, s[i] = 1;
b[i] = make_pair(d[i], i);
home[d[i]] = i;
}
sort(b + 1, b + n + 1);
reverse(b + 1, b + n + 1);
for (int i = 1; i <= n - 1; i++) {
int pos = b[i].second; vis[pos] = true;
ll another = b[i].first + s[pos] - (n - s[pos]);
if (home.count(another) == 0 || vis[home[another]]) {
puts("-1");
return 0;
}
int res = home[another];
f[pos] = f[res], s[res] += s[pos];
}
for (int i = 1; i <= n; i++)
if (vis[i]) {
a[i].push_back(f[i]);
a[f[i]].push_back(i);
}
dfs(1, 0);
ll res = 0;
for (int i = 2; i <= n; i++)
res += size[i];
if (res == d[1]) {
for (int i = 1; i <= n; i++)
if (vis[i]) printf("%d %d\n", i, f[i]);
} else puts("-1");
return 0;
}