AtCoder Grand Contest 001 CDEF
C - Shorten Diameter
枚举直径的中点。把距离过大的删掉。奇数个点枚举中间的边
#include <bits/stdc++.h>
using namespace std;
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 = 2010;
int n, k, lim, res = 1e9, a[N], b[N]; vector<int> g[N];
#define pb push_back
int dfs (int u, int la, int d) {
int s = (d > lim);
for (int v : g[u]) if (v != la)
s += dfs (v, u, d + 1); return s;
}
signed main() {
read (n), read (k); lim = k / 2;
for (int i = 1, u, v; i < n; ++i)
read (a[i]), read (b[i]), g[a[i]].pb (b[i]), g[b[i]].pb (a[i]);
if (k & 1) {
for (int i = 1; i < n; ++i)
res = min (res, dfs (a[i], b[i], 0) + dfs (b[i], a[i], 0));
} else {
for (int i = 1; i <= n; ++i)
res = min (res, dfs (i, i, 0));
}
return printf ("%d\n", res), 0;
}
D - Arrays and Palindrome
在两个位置之间连边表示字符相同。一个回文串相当于从中间向两边对称的连边。所有字符相等就是所有点连通。
\(n\) 个节点连通至少要 \(n - 1\) 条边。奇回文连 \(\frac{len}{2} - 0.5\) ,偶回文连 \(\frac{len}{2}\)。所有都是偶回文只有 \(\frac{n}{2}\times 2 = n\) 条边。如果 \(A\) 中有多于两个奇数,最多只有 \(n - 1.5\) 条边,不可能连通。接下来只要对奇数个数 \(\leq 2\) 的构造出合法方案。设构造序列为 \(b\)
如果全部都是偶数,\(b_1 = 1\),后面的 \(b_i= a_{i-1}\),注意最后一个要 \(-1\),其实就是和 \(a\) 错位把所有的串在一起
如果有一个奇数,把这个放到末尾,其他不变。
两个奇数,一头一尾,此时 \(b_i = a_i\),第一个加一,最后一个减一。
#include <bits/stdc++.h>
using namespace std;
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 = 1e5 + 5;
int n, m, a[N], tot, res[N];
signed main() {
read (n), read (m); int s = 0;
for (int i = 1; i <= m; ++i) read (a[i]);
for (int i = 1; i <= m; ++i) s += (a[i] & 1);
if (s > 2) return puts ("Impossible"), 0;
if (s == 0) {
res[++tot] = 1;
for (int i = 1; i < m; ++i) res[++tot] = a[i];
if (a[m] > 1) res[++tot] = a[m] - 1;
} else if (s == 1) {
for (int i = 1; i <= m; ++i)
if (a[i] & 1) { swap (a[i], a[m]); break; }
res[++tot] = 1;
for (int i = 1; i < m; ++i) res[++tot] = a[i];
if (a[m] > 1) res[++tot] = a[m] - 1;
} else {
for (int i = 1, t = 0; i <= m; ++i) {
if (a[i] & 1) {
if ((++t) == 1) swap (a[i], a[1]);
else { swap (a[i], a[m]); break; }
}
}
res[++tot] = a[1] + 1;
for (int i = 2; i < m; ++i) res[++tot] = a[i];
if (a[m] > 1) res[++tot] = a[m] - 1;
}
for (int i = 1; i <= m; ++i) printf ("%d ", a[i]); puts ("");
printf ("%d\n", tot);
for (int i = 1; i <= tot; ++i) printf ("%d ", res[i]);
return 0;
}
E - BBQ Hard
\(C_{a_i+b_i+a_j+b_j}^{a_i+a_j}\) 的集合意义就是从 \((0,0)\) 走到 \((a_i+a_j,b_i+b_j)\) 的方案。但这个和 \(i,j\) 均相关,不好计算。不妨把坐标轴移动,看成从 \((-a_i,-b_i)\) 走到 \((a_j,b_j)\) 的方案数。
可以先把所有起点的的数量统计,然后按照普通的 \(dp\) 做一遍。\(f_{i,j} = f_{i-1,j} + f_{i,j-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 = 2010, mod = 1e9 + 7;
int n, res, pw[N], in[N], f[M << 1][M << 1], p[M << 1][M << 1];
int qpow (int x, int y) {
int t = 1;
while (y) {
if (y & 1) t = t * x % mod;
x = x * x % mod, y >>= 1;
} return t;
}
#define up(x, y) ((x += y) >= mod && (x -= mod))
int C (int x, int y) {
return pw[x] * in[y] % mod * in[x - y] % mod;
}
signed main() {
read (n); pw[0] = 1;
for (int i = 1; i <= 8000; ++i) pw[i] = pw[i - 1] * i % mod;
in[8000] = qpow (pw[8000], mod - 2);
for (int i = 8000; i; --i) in[i - 1] = in[i] * i % mod;
for (int i = 1, x, y; i <= n; ++i) {
read (x), read (y), ++f[2000 - x][2000 - y], ++p[2000 + x][2000 + y];
up (res, mod - C (x + x + y + y, x + x));
}
for (int i = 0; i <= 4000; ++i)
for (int j = 0; j <= 4000; ++j) {
if (i) up (f[i][j], f[i - 1][j]);
if (j) up (f[i][j], f[i][j - 1]);
(res += p[i][j] * f[i][j]) %= mod;
}
return printf ("%lld\n", res * qpow (2, mod - 2) % mod), 0;
}
F - Wide Swap
题目的条件特别别扭,可以弄到逆序列上化简。算出 \(a\),满足 \(a_{p_i}=i\)。题目变成如果相邻的两个数 \(|a_i-a_{i+1}|>=k\),可以交换,使 \(a\) 的字典序最小。
如果两个数的绝对值之差 \(<k\),相对位置一定不变。暴力做法可以在所有这样的数对之间连有向边,表示相对位置关系,按照拓扑序贪心。但很多边是不需要的,只需要连差值在范围内且位置最靠近的数(一大一小两条)。可以手算理解一下正确性。需要连的边可以用线段树求出
#include <bits/stdc++.h>
using namespace std;
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 = 5e5 + 5;
int n, k, a[N], w[N], cnt[N]; vector<int> g[N];
struct tree {
int c[N << 2];
#define ls (p << 1)
#define rs (p << 1 | 1)
void modify (int p, int l, int r, int i) {
c[p] = max (c[p], i); if (l == r) return;
int mid (l + r >> 1);
if (w[i] <= mid) modify (ls, l, mid, i);
else modify (rs, mid + 1, r, i);
}
int query (int p, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr) return c[p];
int mid (l + r >> 1), res = 0;
if (ql <= mid) res = query (ls, l, mid, ql, qr);
if (qr > mid) res = max (res, query (rs, mid + 1, r, ql, qr));
return res;
}
} t;
priority_queue<int> q; int res[N];
signed main() {
read (n), read (k);
for (int i = 1; i <= n; ++i) read (a[i]), w[a[i]] = i;
for (int i = 1, tmp; i <= n; ++i) {
// [p[i] - k + 1, p[i]] 、[p[i], p[i] + k - 1] 内最近的值,
tmp = t.query (1, 1, n, w[i] - k + 1, w[i]);
if (tmp) g[w[i]].push_back (w[tmp]), ++cnt[w[tmp]];
tmp = t.query (1, 1, n, w[i], w[i] + k - 1);
if (tmp) g[w[i]].push_back (w[tmp]), ++cnt[w[tmp]];
t.modify (1, 1, n, i);
}
for (int i = 1; i <= n; ++i) if (!cnt[i]) q.push (i);
int now = n;
while (now) {
int u = q.top(); q.pop(); res[u] = now, --now;
for (int v : g[u]) if ((--cnt[v]) == 0) q.push (v);
}
for (int i = 1; i <= n; ++i) printf ("%d\n", res[i]);
return 0;
}