AtCoder Grand Contest 039 题解
本文目录(15 节)
Connection and Disconnection
分类讨论贪心。
const int N = 107;
int n, c, k, t, a, b;
char s[N];
int main() {
rds(s, n), rd(k);
for (int l = 1, r = 1; l <= n; l = ++r) {
while (s[r+1] == s[l]) ++r;
t += (r - l + 1) >> 1, ++c;
if (!a) a = r - l + 1;
b = r - l + 1;
}
if (s[1] != s[n]) print(1ll * k * t);
else if (c == 1) print((1ll * n * k) >> 1);
else {
t -= (a >> 1) + (b >> 1);
t += (a + b) >> 1;
print(1ll * k * t - ((a & 1) && (b & 1)));
}
return 0;
}
Graph Partition
首先图要是一张二分图。
是二分图的情况下,从某一个点开始 bfs,如果合法,用最大深度更新答案即可。
时间复杂度 。
const int N = 207;
int n, c[N], d[N], ans;
vi e[N];
void dfs(int x, int o) {
c[x] = o;
for (int y : e[x])
if (!c[y]) dfs(y, 3 - o);
else if (c[x] == c[y]) print(-1), exit(0);
}
inline int calc(int s) {
for (int i = 1; i <= n; i++) d[i] = 0;
d[s] = 1;
queue<int> q;
q.push(s);
while (q.size()) {
int x = q.front();
q.pop();
for (int y : e[x])
if (!d[y]) d[y] = d[x] + 1, q.push(y);
else if (d[y] != d[x] - 1 && d[y] != d[x] + 1) return 0;
}
return *max_element(d + 1, d + n + 1);
}
int main() {
rd(n);
for (int x = 1; x <= n; x++)
for (int y = 1; y <= n; y++) {
char c;
rdc(c);
if (c == '1') e[x].pb(y);
}
dfs(1, 1);
for (int i = 1; i <= n; i++) ans = max(ans, calc(i));
print(ans);
return 0;
}
Division by Two with Something
有趣的计数题。
不当成一个 位二进制数而看作一个 串的话,每次操作相当于把最右边的字符取反放到最左边。
考虑一个 串如何计算需要多少次操作能回到本身。
首先注意到串 一定会至多经过 次操作完全取反,然后再经过同等次数的操作回到本身。
因此任何一个串一定能够回到本身,且次数不超过 次,且次数为偶数次。
假设次数为 次,则意味着 次之后能取反。
于是有 和 互反, 和 互反,, 与 互反, 与 互反。
因此 为满足:
- 的约数;
- 为奇数;
- 与 互反;
的最小值。
于是就简单了,枚举 ,求出恰好经过 次操作回到本身且 的个数,即:
- 所构成的数;
- 减去 的约数对应的个数(这意味着更小的循环节);
- 如果 根据 与 互反,扩展成长度为 的数之后 ,则还要加上 。
然后将个数乘上 就是 的贡献。
时间复杂度 。
const int N = 2e5 + 7;
int n;
char s[N];
bool a[N], b[N];
modint c[N], ans;
inline bool pd(int i) {
for (int j = 1; j <= i; j++) b[j] = a[j];
for (int j = i + 1; j <= n; j++) {
b[j] = b[j-i] ^ 1;
if (b[j] < a[j]) return 1;
if (b[j] > a[j]) return 0;
}
return 1;
}
int main() {
rd(n), rds(s, n);
for (int i = 1; i <= n; i++) a[i] = s[i] & 15;
for (int i = 1; i <= n; i++)
if (!(n % i) && ((n / i) & 1)) {
for (int j = 1; j <= i; j++)
c[i] = c[i] * 2 + a[j];
for (int j = 1; j < i; j++)
if (!(i % j)) c[i] -= c[j];
c[i] += pd(i);
ans += c[i] * i * 2;
}
print(ans);
return 0;
}
Incenters
题意
- 平面直角坐标系上有 个点,第 个点为 。
- 求出在 个点中随机选择三个点,以这三个点为顶点的三角形的内心的期望坐标。
- ,。
题解
对于 ,取 弧的中点 ,可以发现 的垂心就是 的内心。
考虑如何求 的垂心,根据欧拉线,由于 的外心为 ,重心为 ,所以垂心为 。
于是显然可以 枚举求答案。
代码
const int N = 3e3 + 7;
const ld PI = acos(-1);
int n, L, a[N];
ld x, y;
int main() {
rd(n, L), rda(a, n);
for (int i = 1; i < n; i++)
for (int j = i + 1; j <= n; j++)
x += (n - 2 * (j - i)) * cos(PI * (a[i] + a[j]) / L),
y += (n - 2 * (j - i)) * sin(PI * (a[i] + a[j]) / L);
ll k = 1ll * n * (n - 1) * (n - 2) / 6;
printf("%.20Lf %.20Lf\n", x / k, y / k);
return 0;
}
Pairing Points
题意
- 圆上有 个点,对于任意六个点 满足 三线不共点。
- 给定一个 的 矩阵 ,你需要计算将 个点分成 个点对的方案数,满足:
- 对于所有点对 ,。
- 将所有点对用线段连接起来,恰好形成一棵树。
- 。
题解
考虑先将 设为原本的 。
考虑用 表示在 中连一些点对,使得至少有一组点对跨过 ,且所有跨过 的点对两两不交的方案数。
枚举 号点的对应点 ,于是问题求 的答案。
考虑如何求 ,枚举跨过 的所有点对中最靠近 的点对 。
可以发现,此时 中,有靠近 的一部分会和 连通,剩下的部分与 连通,分界点为 。
同理,此时 中,有靠近 的一部分会和 连通,剩下的部分与 连通,分界点为 。
而 一定和 连通, 一定和 连通。
因此问题被分为了 三个子问题。
记忆化搜索即可,状态数为 ,转移为 ,总时间复杂度为 ,常数非常小,可以通过本题。
这个 DP 可以优化到 ,不过没必要。
代码
const int N = 47;
int n, a[N][N];
char s[N][N];
ll f[N][N][N], ans;
bool v[N][N][N];
ll DP(int l, int m, int r) {
if ((l ^ r) & 1) return 0;
if (l == m && m == r) return 1;
if (l == m || m == r) return 0;
if (v[l][m][r]) return f[l][m][r];
v[l][m][r] = 1;
for (int p = l; p < m; p++)
for (int q = r; q > m; q--)
if (a[p][q])
for (int x = p; x < m; x++)
for (int y = q; y > m; y--)
f[l][m][r] += DP(l, p, x) * DP(y, q, r) * DP(x + 1, m, y - 1);
return f[l][m][r];
}
int main() {
rd(n), n <<= 1;
for (int i = 1; i <= n; i++) rds(s[i], n);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++)
a[i][j] = s[i][j] & 1;
for (int i = 2; i <= n; i++)
if (a[1][i]) ans += DP(2, i, n);
print(ans);
return 0;
}
Min Product Sum
题意
- 有一个 的矩阵 ,每个位置上有一个 的整数。
- 对于一种填数方案,设 ,则这种方案的权值为 。
- 求一共 种方案的权值和。
- ,答案对 取模。
题解
首先转化题意:
- 有两个 的矩阵 ,每个位置上有一个 的整数。
- 另外要求 。
- 求方案数。
显然这两者是等价的。
设 ,,假设我们已经设定了所有 ,考虑如何求方案数。
对于 , 可以填 ;对于 , 可以填 。
然而这样填是有问题的,并不能保证最小值恰好 ,于是需要容斥一下,即枚举有多少行多少列的最小值大于设定值,乘上 的那么多次方。
考虑 DP,从小到大填 ,设 表示已经填了 ,用了 行 列, 已经填了这 行 列的交的位置, 已经填了这 行 列的并的位置,的方案数。
转移时枚举填 的不被容斥的行列数 ,被容斥的行列数 ,则有: 然而设 同阶,这个 DP 复杂度是 ,显然要优化。
依然从小到大枚举 ,在这一层转移时,可以发现我们不必同时枚举 ,而只需要依次枚举,每次把贡献计算清楚即可。
具体而言,先让 继承 。
再枚举 ,转移为: 再枚举 ,转移为: 再枚举 ,转移为: 再枚举 ,转移为: 时间复杂度 ,很卡常,要进行一定程度的预处理,尽可能减少取模次数。
代码
const int N = 107;
int n, m, k;
int f[N<<2][N][N], g[N][N*N], c[N][N];
inline void upd(int &x, int y, int z) {
x = (x + 1ll * y * z) % P;
}
int main() {
rd(n, m, k), rd(P);
init(max(n, m));
for (int i = 0; i <= k; i++) {
g[i][0] = 1;
for (int j = 1; j <= n * m; j++) g[i][j] = 1ll * g[i][j-1] * i % P;
}
for (int i = 0; i <= max(n, m); i++)
for (int j = 0; j <= i; j++) c[i][j] = binom(i, j).x;
f[0][0][0] = 1;
for (int t = 1, o = 0; t <= k; t++) {
for (int i = 0; i <= n; i++)
for (int j = 0; j <= m; j++)
if (!!f[o][i][j]) {
ll x = f[o][i][j], y = 1ll * g[k-t+1][j] * g[t][m-j] % P;
for (int p = 0; i + p <= n; p++, (x *= y) %= P) upd(f[o+1][i+p][j], c[n-i][p], x);
}
++o;
for (int i = 0; i <= n; i++)
for (int j = 0; j <= m; j++)
if (!!f[o][i][j]) {
ll x = f[o][i][j], y = 1ll * g[k-t+1][i] * g[t][n-i] % P;
for (int q = 0; j + q <= m; q++, (x *= y) %= P) upd(f[o+1][i][j+q], c[m-j][q], x);
}
++o;
for (int i = 0; i <= n; i++)
for (int j = 0; j <= m; j++)
if (!!f[o][i][j]) {
ll x = f[o][i][j], y = P - 1ll * g[k-t][j] * g[t][m-j] % P;
for (int u = 0; i + u <= n; u++, (x *= y) %= P) upd(f[o+1][i+u][j], c[n-i][u], x);
}
++o;
for (int i = 0; i <= n; i++)
for (int j = 0; j <= m; j++)
if (!!f[o][i][j]) {
ll x = f[o][i][j], y = P - 1ll * g[k-t][i] * g[t][n-i] % P;
for (int v = 0; j + v <= m; v++, (x *= y) %= P) upd(f[o+1][i][j+v], c[m-j][v], x);
}
++o;
}
print(f[k<<2][n][m]);
return 0;
}
评论
暂无已公开评论。