11.4训练赛
A.P1066 2^k进制数
dp[i][j]表示最高位在i取j时的方案数,需要高精度,这里用py并滚动掉第一维逆序迭代
k, w = map(int, input().split())
l = pow(2, k) - 1
n = min((w + k - 1) // k, l) # 最长位数
m = min(pow(2, w - (n - 1) * k) - 1, l - n + 1) # 最高位上限
dp = [1] * l
dp[0], ans = 0, 0
for i in range(n - 1): # 直接从倒数第二位开始
for j in range(1, l - i): # 逆序
dp[j] += dp[j - 1]
if i < n - 2:
ans += dp[j]
for i in range(m):
ans += dp[l - n + 1 - i]
print(ans)
用组合数学考虑\(k \mid w\)与\(k \nmid w\)的情况得到答案为 \(\sum_{i = 2}^j C_l^i + \sum_{i = 1}^{2^{n \mod k} - 1} C_{l - i}^j\) 其中 \(j = \lfloor w / k \rfloor,\ l = 2^k - 1\)
从dp的转移与组合数递推的相似之处也可推出答案。
B.P1099 树网的核
这题可以直接暴力,如果要降低复杂度需要对树的直径的性质有一定了解。
首先注意到答案的下界为直径的偏心距(长度足够取整条直径时),记为D,
取不到时考虑其它点对路径偏心距的贡献,分成两部分,一部分是从路径某点出发不经过直径到达的点,
不会超过D,可以忽略,另一部分的点贡献显然是路径端点到直径端点的最大距离(如果还有更大的就与直径定义矛盾了)
所以我们在直径上尺取长度合法的路径,记路径端点为p1,p2,直径端点为d1,d2,
则路径相对于直径的偏心距为 \(max(dis(p1, d1), dis(p2, d2))\),最小化这个值,记为d,则答案为\(max(d,D)\)
如果有多条直径,考虑其中任意两条,必然相交于树上若干个点(如不相交则可以通过
连接这两条直径的边构造出更长的路径,矛盾),这若干个点可以缩成一个点,剩下的
部分关于此点对称,所以d和D都不会变,任取一条操作即可。
理解了这种做法可以过掉这题加强版P2491
#include <bits/stdc++.h>
#define fu(a, b, c) for (ll a = b; a <= c; a++)
#define fd(a, b, c) for (ll a = b; a >= c; a--)
#define mx(a, b) a = max(a, b)
#define mn(a, b) a = min(a, b)
using namespace std;
using ll = long long;
using pll = pair<ll, ll>;
const ll N = 310;
ll n, m, u, v, w, k, e, d1, d2 = 1e9;
ll fa[N], d[N], l[N];
vector<pll> g[N];
void dfs(ll u, ll f) {
fa[u] = f, k = d[u] > d[k] ? u : k;
for (auto [v, w] : g[u])
if (v ^ f && !l[v])
d[v] = d[u] + w, dfs(v, u);
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> m;
fu(i, 2, n) {
cin >> u >> v >> w;
g[u].emplace_back(v, w);
g[v].emplace_back(u, w);
}
dfs(1, 0), d[k] = 0, dfs(k, 0), e = k;
for (ll i = k, j = k; i; j = fa[j])
while (i && d[j] - d[i] <= m) {
mn(d2, max(d[e] - d[j], d[i]));
l[i] = 1, k = i, dfs(i, fa[i]);
mx(d1, d[k] - d[i]), i = fa[i];
}
cout << max(d1, d2);
}
C.P1081 开车旅行
用set预处理然后倍增一下即可
#include <bits/stdc++.h>
#define fu(a, b, c) for (ll a = b; a <= c; a++)
#define fd(a, b, c) for (ll a = b; a >= c; a--)
#define mx(a, b) a = max(a, b)
#define mn(a, b) a = min(a, b)
#define f first
#define s second
using namespace std;
typedef long long ll;
typedef long double ld;
using pll = pair<ll, ll>;
const ll N = 1e5 + 5, M = 1e6 + 7;
ll t, n, m, a, b, c, x0;
ll h[N], nx[N][16], nb[N];
ll dis[N][16], da[N][16], db[N];
multiset<pll> s;
inline void cal() {
s.emplace(5e18, 0), s.emplace(-5e18, 0);
s.emplace(5e18, 0), s.emplace(-5e18, 0);
fd(i, n, 1) {
auto j = s.lower_bound({h[i], 0}), j1 = j--;
pair<ll, pll> p[4];
p[0].s = *j, p[1].s = *--j;
p[2].s = *j1, p[3].s = *++j1;
fu(j, 0, 3) p[j].f = abs(p[j].s.f - h[i]);
sort(p, p + 4);
if (p[0].f < 1e18)
nb[i] = p[0].s.s, db[i] = p[0].f;
if (p[1].f < 1e18)
nx[i][0] = p[1].s.s, dis[i][0] = da[i][0] = da[i][1] = p[1].f;
s.emplace(h[i], i);
}
fu(i, 1, n) nx[i][1] = nb[nx[i][0]], dis[i][1] = dis[i][0] + db[nx[i][0]];
fu(i, 2, 15) fu(j, 1, n) {
t = nx[j][i - 1];
nx[j][i] = nx[t][i - 1];
dis[j][i] = dis[j][i - 1] + dis[t][i - 1];
da[j][i] = da[j][i - 1] + da[t][i - 1];
}
}
inline void ck(ll s, ll d) {
a = c = 0;
fd(i, 15, 0) if (nx[s][i] && c + dis[s][i] <= d) {
c += dis[s][i];
a += da[s][i];
s = nx[s][i];
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
fu(i, 1, n) cin >> h[i];
cal();
cin >> x0;
pair<ld, ll> ans = {1, n};
fu(i, 1, n - 1) {
ck(i, x0);
ld x = a ? c * 1.0 / a : 1;
if (x > ans.f || x == ans.f && h[i] > h[ans.s])
ans = {x, i};
}
cout << ans.s << '\n';
cin >> m;
fu(i, 1, m) {
cin >> a >> b;
ck(a, b);
cout << a << ' ' << c - a << '\n';
}
}
D.P7919 ABC
每次操作最多使相邻相同个数-2。
#include <bits/stdc++.h>
#define fu(a, b, c) for (int a = b; a <= c; a++)
#define fd(a, b, c) for (int a = b; a >= c; a--)
using namespace std;
const int N = 5e3 + 1;
int n, x, y, t;
char s[N];
int l[N], r[N];
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n >> s + 1;
x = 1, y = n;
while (x < y) {
while (x < y && s[x] ^ s[x + 1])
++x;
while (y > x && s[y] ^ s[y - 1])
--y;
if (x == y)
break;
l[++t] = ++x, r[t] = --y;
}
if (l[t] > r[t])
r[t] = n;
cout << t << '\n';
fu(i, 1, t) cout << l[i] << ' ' << r[i] << ' ' << "BCA" << '\n';
}
E.CF1607F Robot on the Board 2
一条路径如果如果成环那么整条路径的最大移动次数都是环的长度,
否则move[now] = move[next] + 1,朴素的dfs实现极限要压4e6次栈容易爆空间,
可以优化或者用非递归的写法。
#include <bits/stdc++.h>
#define fu(a, b, c) for (int a = b; a <= c; ++a)
#define fd(a, b, c) for (int a = b; a >= c; --a)
#define mx(a, b) a = max(a, b)
#define mn(a, b) a = min(a, b)
using namespace std;
const int N = 2e3 + 5;
short int t, n, m, a, b;
int c, k, dfn;
int dp[N][N], vis[N][N];
char mp[N][N];
void dfs(short int x, short int y) {
if (x < 1 || x > n || y < 1 || y > m)
return dp[x][y] = 0, void();
if (dp[x][y])
return;
if (vis[x][y])
return dp[x][y] = k = dfn - vis[x][y] + 1, void();
vis[x][y] = ++dfn;
if (mp[x][y] == 'R') {
dfs(x, y + 1);
dp[x][y] = k ? --k, dp[x][y + 1] : dp[x][y + 1] + 1;
} else if (mp[x][y] == 'L') {
dfs(x, y - 1);
dp[x][y] = k ? --k, dp[x][y - 1] : dp[x][y - 1] + 1;
} else if (mp[x][y] == 'U') {
dfs(x - 1, y);
dp[x][y] = k ? --k, dp[x - 1][y] : dp[x - 1][y] + 1;
} else {
dfs(x + 1, y);
dp[x][y] = k ? --k, dp[x + 1][y] : dp[x + 1][y] + 1;
}
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> t;
while (t--) {
cin >> n >> m;
fu(i, 1, n) {
cin >> mp[i] + 1;
memset(dp[i] + 1, 0, m * 8);
memset(vis[i] + 1, 0, m * 8);
}
c = dfn = 0;
fu(i, 1, n) {
fu(j, 1, m) {
if (dfs(i, j), dp[i][j] > c)
c = dp[i][j], a = i, b = j;
}
}
cout << a << ' ' << b << ' ' << c << '\n';
}
}
F.CF1601B Frog Traveler
bfs,已经入过队的点不用重复入队。
#include <bits/stdc++.h>
#define fu(a, b, c) for (ll a = b; a <= c; a++)
#define fd(a, b, c) for (ll a = b; a >= c; a--)
#define mx(a, b) a = max(a, b)
#define mn(a, b) a = min(a, b)
using namespace std;
typedef long long ll;
typedef pair<ll, ll> pll;
typedef long double ld;
const ll N = 3e5 + 10, M = 1e9;
ll t, n, m, k, ans;
ll a, b, c, d, e, f;
ll x[N], y[N], dp[N], lst[N], from[N];
pll p[N];
vector<ll> vx[N], vy[N];
string s;
void out(ll x) {
if (x ^ n)
out(lst[x]);
else
return;
cout << x << ' ';
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
fu(i, 1, n) cin >> x[i];
fu(i, 1, n) cin >> y[i];
queue<ll> q;
q.emplace(n);
ll r = n - 1;
while (q.size()) {
a = q.front(), q.pop();
if (!a) {
cout << dp[a] << '\n';
out(0);
return 0;
}
b = a, a += y[a];
fu(i, a - x[a], r) dp[i] = dp[b] + 1, lst[i] = b, q.emplace(i);
mn(r, a - x[a] - 1);
}
cout << -1;
}
G.P4551 最长异或路径
这题以前的周赛出现过,好像还是加强版,注意到任意两点间异或路径等于两点到根的
异或路径的异或值,建一颗trie就可以贪心出每位的最大值。
#include <bits/stdc++.h>
#define fu(a, b, c) for (int a = b; a <= c; a++)
#define fd(a, b, c) for (int a = b; a >= c; a--)
using namespace std;
const int N = 2e5;
int n, u, v, ans, no, res;
int to[N], he[N], ne[N], d[N], w[N];
int t[N * 15][2];
void dfs(int u, int fa) {
for (int i = he[u]; i; i = ne[i]) {
v = to[i];
if (v == fa)
continue;
d[v] = d[u] ^ w[i];
dfs(v, u);
}
}
void insert(int x) {
u = 0;
fd(i, 30, 0) {
v = x >> i & 1;
if (!t[u][v])
t[u][v] = ++no;
u = t[u][v];
}
}
int q(int x) {
res = u = 0;
fd(i, 30, 0) {
v = x >> i & 1;
if (t[u][1 - v])
res += 1 << i, u = t[u][1 - v];
else
u = t[u][v];
}
return res;
}
int main() {
scanf("%d", &n);
fu(i, 1, n - 1) {
int j = i * 2 - 1;
scanf("%d%d%d", &u, &v, &w[j]);
to[j] = v, ne[j] = he[u], he[u] = j;
to[i * 2] = u, ne[i * 2] = he[v], he[v] = i * 2;
w[i * 2] = w[j];
}
dfs(1, 0);
fu(i, 1, n) insert(d[i]);
fu(i, 1, n) ans = max(ans, q(d[i]));
printf("%d\n", ans);
}
H.P4145 花神游历各国
实现方法很多,关键是注意到每个点修改次数有上限。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 1e5 + 1;
ll T[N * 4], a[N], fa[N];
ll n, m, q, l, r, t;
ll find(ll x) {
return fa[x] ? fa[x] = find(fa[x]) : x;
}
void add(ll x, ll y) {
while (x <= n)
T[x] += y, x += x & -x;
}
ll qry(ll x) {
ll r = 0;
while (x)
r += T[x], x -= x & -x;
return r;
}
int main() {
ios::sync_with_stdio(0), cin.tie(0);
cin >> n;
for (ll i = 1; i <= n; ++i)
cin >> a[i], add(i, a[i]);
cin >> m;
while (m--) {
cin >> q >> l >> r;
if (l > r)
swap(l, r);
if (q ^ 1)
for (ll i = l; i <= r; i = find(i) ^ i ? fa[i] : i + 1) {
if (a[i] == 1) {
fa[i] = i + 1;
continue;
}
t = sqrt(a[i]);
add(i, t - a[i]), a[i] = t;
}
else
cout << qry(r) - qry(l - 1) << '\n';
}
}