2024.08 别急记录

1. ARC072F - Dam

发现当 t 递增时优先倒掉前面的,再选后面的;否则优先选后面的,再一起倒掉。所以维护单调队列中 t 递增,每次弹出队首 >L 的部分,然后插入队尾。若队尾 ti1>ti 则将两个合并。

点击查看代码
//AT_arc072_d
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6 + 10;
int n, L, v[N];
double t[N];
typedef long long ll;
pair<double, int> q[N];
int l = 1, r;
int main(){
scanf("%d%d", &n, &L);
for(int i = 1; i <= n; ++ i){
scanf("%lf%d", &t[i], &v[i]);
}
q[++r] = make_pair(t[1], v[1]);
double sum = t[1] * 1.0 * v[1];
printf("%.9lf\n", sum / L);
for(int i = 2; i <= n; ++ i){
int nw = v[i];
while(nw){
int mn = min(q[l].second, nw);
sum -= q[l].first * mn;
q[l].second -= mn;
nw -= mn;
if(!q[l].second){
++ l;
}
}
while(l <= r && q[r].first >= t[i]){
t[i] = (q[r].first * q[r].second + t[i] * v[i]) * 1.0 / (q[r].second + v[i]);
v[i] += q[r].second;
sum -= q[r].first * q[r].second;
-- r;
}
q[++r] = make_pair(t[i], v[i]);
sum += t[i] * 1.0 * v[i];
printf("%.9lf\n", sum / L);
}
return 0;
}

2. ARC070E - NarrowRectangles

考虑设 fi(x) 表示前 i 个区间,第 i 个区间左端点移动到 x 的最小答案。设 leni=rili,有递推式:

fi(x)=|xli|+mink[xleni1,x+leni]{fj(k)}

写成 slope 的形式即为将斜率为 0 的区间 [L,R] 拉长至 [Lleni,R+leni1] 再加一个 v。可以使用两个堆维护 L 左侧,R 右侧的折点,维护两个 tag 表示两个堆偏移量。每次加 v 三种情况:v[L,R],直接加;否则 [L,R] 会变化,发现相当于一个堆顶移到了另一个堆中,是好维护的。

发现此时不方便求出 fn(min) 再求解,可以直接在转移过程中更新 ans

点击查看代码
//AT_arc070_c
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 10;
int n, l[N], r[N], len[N];
typedef long long ll;
priority_queue<ll> ql;
priority_queue<ll, vector<ll>, greater<ll> > qr;
ll tagl, tagr, ans;
int main(){
scanf("%d", &n);
for(int i = 1; i <= n; ++ i){
scanf("%d%d", &l[i], &r[i]);
len[i] = r[i] - l[i];
if(i == 1){
ql.push(l[i]);
qr.push(l[i]);
ql.push(-1e18);
qr.push(1e18);
} else {
tagl += len[i];
tagr += len[i-1];
if(l[i] < ql.top() - tagl){
ll x = ql.top() - tagl;
ql.pop();
ans += x - l[i];
ql.push(l[i] + tagl);
ql.push(l[i] + tagl);
qr.push(x - tagr);
} else if(l[i] <= qr.top() + tagr){
ql.push(l[i] + tagl);
qr.push(l[i] - tagr);
} else {
ll x = qr.top() + tagr;
qr.pop();
ans += l[i] - x;
qr.push(l[i] - tagr);
qr.push(l[i] - tagr);
ql.push(x + tagl);
}
}
}
printf("%lld\n", ans);
return 0;
}

3. PA2021 - Poborcy podatkowi

考虑简单的 O(n2) 做法:设 fi,0/1/2/3 表示子树 ii 向下延伸 0/1/2/3 的答案。每次转移到节点 x 时维护辅助数组 gi,j,0/1 表示考虑 x 的前 i 个子树,13 的个数为 j2 的个数奇偶性为 0/1 的方案数,即可转移。

一个结论是随机 1/1 序列最大前缀和期望是根号的,于是直接将 g 数组第二维大小开成根号,然后对于每个点子树以随机顺序更新即可。

点击查看代码
//monkey
#include <bits/stdc++.h>
using namespace std;
const int N = 2e5 + 10, K = 1310;
int n;
vector<pair<int, int> > gr[N];
typedef long long ll;
ll f[N][4], g[2][K+K+5][2];
void dfs(int x, int fa){
for(auto nd : gr[x]){
int y = nd.first, z = nd.second;
if(y == fa) continue;
dfs(y, x);
}
memset(g, 0xcf, sizeof(g));
g[0][K][0] = g[1][K][0] = 0;
for(auto nd : gr[x]){
int y = nd.first, z = nd.second;
if(y == fa) continue;
for(int i = 0; i < K+K; ++ i){
for(int j = 0; j <= 1; ++ j){
if(g[0][i][j] < -1e13) continue;
g[1][i][j^1] = max(g[1][i][j^1], g[0][i][j] + f[y][1] + z);
g[1][i][j] = max(g[1][i][j], g[0][i][j] + f[y][3] + z);
g[1][i][j] = max(g[1][i][j], g[0][i][j] + f[y][0]);
if(i+1 < K+K){
g[1][i+1][j] = max(g[1][i+1][j], g[0][i][j] + f[y][0] + z);
}
if(i > 0){
g[1][i-1][j] = max(g[1][i-1][j], g[0][i][j] + f[y][2] + z);
}
}
}
memcpy(g[0], g[1], sizeof(g[1]));
}
f[x][0] = g[0][K][0];
f[x][1] = g[0][K+1][0];
f[x][2] = g[0][K][1];
f[x][3] = g[0][K-1][0];
}
int main(){
srand(unsigned(time(NULL)));
scanf("%d", &n);
for(int i = 1; i < n; ++ i){
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
gr[u].push_back(make_pair(v, w));
gr[v].push_back(make_pair(u, w));
}
for(int i = 1; i <= n; ++ i){
int t = gr[i].size();
for(int j = 1; j <= t+t+t+t; ++ j){
int x = rand() % t, y = rand() % t;
swap(gr[i][x], gr[i][y]);
}
}
dfs(1, 0);
printf("%lld\n", f[1][0]);
return 0;
}

4. YsOI2023 - 连通图计数

引理:一个 n 个节点,每个节点度数分别为 d1,d2,...,dn 的有标号无根树的个数为 (n2)!(di1)!

根据 prufer 数列,数列中 i 的出现个数即为 di1,根据可重集计数易证引理成立。

A=(ai1)!

Sub 1. m=n1

此时构成一棵树,di=ai。根据引理得 ans=(n2)!A

Sub 2. m=n

此时构成一棵基环树,对于环上点 di=ai+1,对于非环上点 di=ai。将环边全部断开,环上点与一个新的点 n+1 连边,设这个点为方点(类似于圆方树),则这棵树的 a 和原树一样。方点度数即为环上点数 sum=2nai。注意到新树中与方点连边的点无序,而原树中环上点有序,所以答案需乘上 (sum1)!2

ans=(n1)!A(sum1)!(sum1)!2=(n1)!2A

Sub 3. m=n+1

此时分两种情况:两个环有共边、两个环无共边。设 sum=2nai

Sub 3A. 有共边

设这两个环交集为路径 xy。则 dx=ax+2,dy=ay+2,环上其他点 idi=ai+1;非环上的点 jdj=aj

所以环上点数仍然为 sum=2nai

考虑先枚举 x,y,再枚举连接它们的三条链(合法当且仅当至多一条链点数为 2)。所以方案数为 (sum2)(sum2)![(sum2)3]3!=sum!(sum+2)(sum3)24

将两个环缩成一个方点,其它部分答案依旧为 (n1)!A(sum1)!。二者乘起来得:

ans=(n1)!sum(sum+2)(sum3)24A

Sub 3B. 无共边

此时两个环上点 idi=ai+1,否则有 di=ai。两个环上共 sum+2 个点。

枚举一个环上点数 j(j3,sum+2j3)。将两个环分别设为两个方点,有方案数为 n!A(j1)!(sumj+1)!

但是此时两个方点之间可能有连边,需要把这种情况减掉。

有连边,就相当于构成了一个大小为 sum 的环,方案数为 (n1)!A(sum1)!。而每个点可以选择连哪个方点,答案要乘上 (sumj1)

二者相减,得 (n1)!(nsum)A(j1)!(sumj+1)!

同样要乘上环排列,得 (n1)!(nsum)4A

对于每个 j 有这么多情况,而总共有 j[3,sum+23]sum3j,乘上 sum3 即可。又 j,sum+2j 属于同一种情况,答案需除以 2

ans=(n1)!(nsum)(sum3)8A

点击查看代码
// Problem: P9535 [YsOI2023] 连通图计数
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P9535
// Memory Limit: 512 MB
// Time Limit: 1000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10;
int n, m, a[N];
typedef long long ll;
const ll P = 998244353;
ll fac[N], inv[N], ans, sum;
ll qp(ll x, ll y){
ll ans = 1;
while(y){
if(y & 1){
ans = ans * x % P;
}
x = x * x % P;
y >>= 1;
}
return ans;
}
int main(){
scanf("%d%d", &n, &m);
fac[0] = 1;
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[i]);
sum -= a[i];
fac[i] = fac[i-1] * i % P;
}
inv[n] = qp(fac[n], P-2);
for(int i = n-1; i >= 0; -- i){
inv[i] = inv[i+1] * (i+1) % P;
}
if(m == n - 1){
ans = fac[n-2];
} else if(m == n){
ans = fac[n-1] * inv[2] % P;
} else {
sum += n+n;
ll x = (n-sum) % P * qp(8, P-2) % P;
ll y = (sum+2) % P * sum % P * qp(24, P-2) % P;
ans = (x + y) * fac[n-1] % P * (sum-3) % P;
}
for(int i = 1; i <= n; ++ i){
ans = ans * inv[a[i]-1] % P;
}
printf("%lld\n", ans);
return 0;
}

5. AT_xmascon19_d - Sum of (-1)^f(n)

h(n)=(1)f(n),则 h 为完全积性函数。观察到 h1=[n=q2]。杜教筛即可。

点击查看代码
//AT_xmascon19_d
#include <bits/stdc++.h>
using namespace std;
const int N = 5e6 + 10;
int p[N], v[N], c, f[N], B;
typedef long long ll;
ll n;
map<ll, ll> vs, mp;
ll dj(ll x){
if(x <= B){
return f[x];
} else if(vs[x] != 0){
return mp[x];
} else {
vs[x] = 1;
ll tmp = sqrt(x);
for(ll l = 2, r; l <= x; l = r + 1){
r = x / (x / l);
tmp -= dj(x / l) * (r - l + 1);
}
return mp[x] = tmp;
}
}
int main(){
B = N - 10;
f[1] = 1;
for(int i = 2; i <= B; ++ i){
if(!v[i]){
p[++c] = i;
f[i] = -1;
}
for(int j = 1; j <= c && p[j] * i <= B; ++ j){
v[i*p[j]] = 1;
f[i*p[j]] = f[i] * f[p[j]];
if(i % p[j] == 0){
break;
}
}
}
for(int i = 2; i <= B; ++ i){
f[i] += f[i-1];
}
scanf("%lld", &n);
printf("%lld\n", dj(n));
return 0;
}

6. LGP6329 - 【模板】点分树 | 震波

考虑点分治的过程中,将每次的重心连成一个树形结构,就形成了点分树。点分树有两个性质:

  1. 树高 O(logn)
  2. 对于 x,y 以及他们在点分树上的 lca l,有 l 在原树 xy 路径上。

于是,对于与 x 的距离 k 的点,就可以枚举 x 在点分树上的祖先 p,计算 p 除掉 x 所在那部分子树中距离 kdis(p,x) 的点(dis 表示原树距离)。

所以对于这道题,我们维护两个数据结构 c0/1,i,j 表示 dis(i,x)=jax 和;dis(fi,x)=jax 和(fii 在点分树上的父亲)。于是每次修改只用修改这个节点到点分树根的那部分点即可,查询时枚举 lca,使用总答案减去同一子树内答案即可。可以使用动态开点线段树维护 c

一些优化:

  • dfs 序求 lca:https://www.luogu.com.cn/article/pu52m9ue
  • 使用树状数组代替线段树。观察到 c0,ij 范围为 [0,sizi)c1,ij 范围为 [0,sizi+1)。(siz 为点分树内子树大小)。于是可以直接使用 vector 开树状数组。注意查询时处理查询下标 [0,sizi] 的情况。

证明:

  • 点分树中 x 的子树是原树中一个包含 x 的连通块,自然 maxdep<siz
  • 点分树中 x 的子树与 fx 是原树中一个包含 x 的连通块与一个直接与此连通块相邻的点,自然 maxdep<siz+1

于是这么开树状数组是正确的。

点击查看代码
// Problem: P6329 【模板】点分树 | 震波
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P6329
// Memory Limit: 250 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
namespace FastIO {
#if __cplusplus > 201700
#define INLINE_V inline
#else
#define INLINE_V
#endif
#if (defined(LOCAL) || defined(_WIN32)) && !defined(DISABLE_MMAP)
#define DISABLE_MMAP
#endif
#ifndef DISABLE_MMAP
#include <sys/mman.h>
#endif
INLINE_V constexpr int _READ_SIZE = 1 << 18; INLINE_V static char _read_buffer[_READ_SIZE], *_read_ptr = nullptr, *_read_ptr_end = nullptr;
inline char gc() { if (__builtin_expect(_read_ptr == _read_ptr_end, false)) { _read_ptr = _read_buffer; _read_ptr_end = _read_buffer + fread(_read_buffer, 1, _READ_SIZE, stdin); if (__builtin_expect(_read_ptr == _read_ptr_end, false)) return EOF;} return *_read_ptr++; }
INLINE_V constexpr int _WRITE_SIZE = 1 << 18; INLINE_V static char _write_buffer[_WRITE_SIZE], *_write_ptr = _write_buffer; inline void pc(char c) { *_write_ptr++ = c; if (__builtin_expect(_write_buffer + _WRITE_SIZE == _write_ptr, false)) { fwrite(_write_buffer, 1, _write_ptr - _write_buffer, stdout); _write_ptr = _write_buffer; } }
INLINE_V struct _auto_flush { ~_auto_flush() { fwrite(_write_buffer, 1, _write_ptr - _write_buffer, stdout); } } _auto_flush; inline bool _isdigit(char c) { return (c & 16) && c != EOF; } inline bool _isgraph(char c) { return c > 32 && c != EOF; }
template <class T> INLINE_V constexpr bool _is_integer = numeric_limits<T>::is_integer; template <class T> INLINE_V constexpr bool _is_signed = numeric_limits<T>::is_signed; template <class T> INLINE_V constexpr bool _is_unsigned = _is_integer<T> && !_is_signed<T>;
template <> INLINE_V constexpr bool _is_integer<__int128> = true; template <> INLINE_V constexpr bool _is_integer<__uint128_t> = true; template <> INLINE_V constexpr bool _is_signed<__int128> = true; template <> INLINE_V constexpr bool _is_unsigned<__uint128_t> = true;
inline void read(char &c) { do c = gc(); while (!_isgraph(c)); } inline void read_cstr(char *s) { char c = gc(); while (!_isgraph(c)) c = gc(); while (_isgraph(c)) *s++ = c, c = gc(); *s = 0; } inline void read(string &s) { char c = gc(); s.clear(); while (!_isgraph(c)) c = gc(); while (_isgraph(c)) s.push_back(c), c = gc(); }
template <class T, enable_if_t<_is_signed<T>, int> = 0> inline void read(T &x) { char c = gc(); bool f = true; x = 0; while (!_isdigit(c)) { if (c == 45) f = false; c = gc(); }
if (f) while (_isdigit(c)) x = x * 10 + (c & 15), c = gc(); else while (_isdigit(c)) x = x * 10 - (c & 15), c = gc(); } template <class T, enable_if_t<_is_unsigned<T>, int> = 0> inline void read(T &x) { char c = gc(); while (!_isdigit(c)) c = gc(); x = 0; while (_isdigit(c)) x = x * 10 + (c & 15), c = gc(); }
inline void write(char c) { pc(c); } inline void write_cstr(const char *s) { while (*s) pc(*s++); } inline void write(const string &s) { for (char c : s) pc(c); } template <class T, enable_if_t<_is_signed<T>, int> = 0> inline void write(T x) { char buffer[numeric_limits<T>::digits10 + 1]; int digits = 0; if (x >= 0) do buffer[digits++] = (x % 10) | 48, x /= 10; while (x);
else { pc(45); do buffer[digits++] = -(x % 10) | 48, x /= 10; while (x); } while (digits) pc(buffer[--digits]); } template <class T, enable_if_t<_is_unsigned<T>, int> = 0> inline void write(T x) { char buffer[numeric_limits<T>::digits10 + 1]; int digits = 0; do buffer[digits++] = (x % 10) | 48, x /= 10; while (x); while (digits) pc(buffer[--digits]); }
template <int N> struct _tuple_io_helper { template <class... T> static inline void _read(tuple<T...> &x) { _tuple_io_helper<N - 1>::_read(x), read(get<N - 1>(x)); } template <class... T> static inline void _write(const tuple<T...> &x) { _tuple_io_helper<N - 1>::_write(x), pc(32), write(get<N - 1>(x)); } };
template <> struct _tuple_io_helper<1> { template <class... T> static inline void _read(tuple<T...> &x) { read(get<0>(x)); } template <class... T> static inline void _write(const tuple<T...> &x) { write(get<0>(x)); } };
template <class... T> inline void read(tuple<T...> &x) { _tuple_io_helper<sizeof...(T)>::_read(x); } template <class... T> inline void write(const tuple<T...> &x) { _tuple_io_helper<sizeof...(T)>::_write(x); }
template <class T1, class T2> inline void read(pair<T1, T2> &x) { read(x.first), read(x.second); } template <class T1, class T2> inline void write(const pair<T1, T2> &x) { write(x.first), pc(32), write(x.second); }
template <class T1, class... T2> inline void read(T1 &x, T2 &...y) { read(x), read(y...); } template <class... T> inline void read_cstr(char *x, T *...y) { read_cstr(x), read_cstr(y...); }
template <class T1, class... T2> inline void write(const T1 &x, const T2 &...y) { write(x), write(y...); } template <class... T> inline void write_cstr(const char *x, const T *...y) { write_cstr(x), write_cstr(y...); }
template <class T> inline void print(const T &x) { write(x); } inline void print_cstr(const char *x) { write_cstr(x); } template <class T1, class... T2> inline void print(const T1 &x, const T2 &...y) { print(x), pc(32), print(y...); }
template <class... T> inline void print_cstr(const char *x, const T *...y) { print_cstr(x), pc(32), print_cstr(y...); } inline void println() { pc(10); } inline void println_cstr() { pc(10); }
template <class... T> inline void println(const T &...x) { print(x...), pc(10); } template <class... T> inline void printk(const T &...x) { print(x...), pc(32); } template <class... T> inline void println_cstr(const T *...x) { print_cstr(x...), pc(10); } } using namespace FastIO;
const int N = 1e5 + 10;
int n, m, a[N], la, fat[N], ban[N], rt, siz[N];
int dep[N], anc[N][20];
vector<int> g[N];
int tmp, mn = 1e9;
void csiz(int x, int fa){
siz[x] = 1;
for(int i : g[x]){
if(!ban[i] && i != fa){
csiz(i, x);
siz[x] += siz[i];
}
}
}
void fndc(int x, int fa, int sum){
for(int i : g[x]){
if(!ban[i] && i != fa){
fndc(i, x, sum);
}
}
int mx = sum - siz[x];
for(int i : g[x]){
if(!ban[i] && i != fa){
mx = max(mx, siz[i]);
}
}
if(mx < mn){
tmp = x;
mn = mx;
}
}
int build(int x){
csiz(x, 0);
mn = 1e9;
fndc(x, 0, siz[x]);
int c = tmp;
siz[c] = siz[x];
ban[c] = 1;
for(int i : g[c]){
if(!ban[i]){
int xx;
fat[xx = build(i)] = c;
}
}
return c;
}
int dn, dfn[N], mi[19][N];
int get(int x, int y) {return dfn[x] < dfn[y] ? x : y;}
void dfs(int id, int f) {
mi[0][dfn[id] = ++dn] = f;
dep[id] = dep[f] + 1;
for(int it : g[id]) if(it != f) dfs(it, id);
}
int lca(int u, int v) {
if(u == v) return u;
if((u = dfn[u]) > (v = dfn[v])) swap(u, v);
int d = __lg(v - u++);
return get(mi[d][u], mi[d][v - (1 << d) + 1]);
}
int dis(int x, int y){
int l = lca(x, y);
return dep[x] + dep[y] - dep[l] * 2;
}
struct bit{
int siz;
vector<int> bit;
void init(int l){
siz = l;
bit.resize(siz+3);
for(int i = 0; i < siz+3; ++ i){
bit[i] = 0;
}
}
void add(int x, int v){
++ x;
while(x <= siz){
bit[x] += v;
x += x & -x;
}
}
int ask(int x){
int rs = 0;
if(x < 0){
return 0;
}
x = min(x + 1, siz);
while(x){
rs += bit[x];
x -= x & -x;
}
return rs;
}
} c[2][N];
void add(int x, int val){
int nw = x;
while(true){
c[0][nw].add(dis(x, nw), val);
if(nw != rt){
c[1][nw].add(dis(x, fat[nw]), val);
} else {
break;
}
nw = fat[nw];
}
}
int main(){
read(n, m);
for(int i = 1; i <= n; ++ i){
read(a[i]);
}
for(int i = 1; i < n; ++ i){
int u, v;
read(u, v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
for(int i = 1; i <= __lg(n); i++)
for(int j = 1; j + (1 << i) - 1 <= n; j++)
mi[i][j] = get(mi[i - 1][j], mi[i - 1][j + (1 << i - 1)]);
rt = build(1);
for(int i = 1; i <= n; ++ i){
c[0][i].init(siz[i]);
c[1][i].init(siz[i] + 1);
}
for(int i = 1; i <= n; ++ i){
add(i, a[i]);
}
while(m --){
int op, x, y;
read(op, x, y);
x ^= la;
y ^= la;
if(op == 0){
int nw = x, ls, ans = 0;
while(true){
ans += c[0][nw].ask(y - dis(x, nw));
if(nw != x){
ans -= c[1][ls].ask(y - dis(x, nw));
}
if(nw == rt){
break;
}
ls = nw;
nw = fat[nw];
}
la = ans;
println(ans);
} else {
add(x, y - a[x]);
a[x] = y;
}
}
return 0;
}

7. WC2021 - 括号路径

观察到若 (x,y),(y,z) 可行则 (x,z) 也可行。所以用并查集维护每一个可行集合。若存在 (x,y,k)(z,y,k) 则将 (x,z) 放入一个队列中,每次取出队首合并。使用 map 维护通过 k 进入每个集合的点集。那么合并 u,v 时,若二者同一个 k 的 map 有交集则再将这些插入队列。复杂度为启发式合并加 map 的两 log。

点击查看代码
//qoj1301
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 3e5 + 10;
int n, m, k, fa[N], siz[N];
vector<pair<int, int> > g[N];
map<int, int> mp[N];
queue<pair<int, int> > q;
long long ans;
int gf(int x){
return x == fa[x] ? x : fa[x] = gf(fa[x]);
}
void merge(int x, int y){
x = gf(x);
y = gf(y);
if(x == y){
return;
}
if(siz[x] < siz[y]){
swap(x, y);
}
fa[y] = x;
siz[x] += siz[y];
for(auto [i, j] : mp[y]){
if(mp[x][i]){
q.push(make_pair(mp[x][i], j));
} else {
mp[x][i] = j;
}
}
}
int main(){
scanf("%d%d%d", &n, &m, &k);
for(int i = 1; i <= m; ++ i){
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
if(!mp[v][w]){
mp[v][w] = u;
} else {
q.push(make_pair(u, mp[v][w]));
}
}
for(int i = 1; i <= n; ++ i){
fa[i] = i;
siz[i] = 1;
}
while(!q.empty()){
int x, y;
tie(x, y) = q.front();
q.pop();
merge(x, y);
}
for(int i = 1; i <= n; ++ i){
if(fa[i] == i){
ans += 1ll * siz[i] * (siz[i]-1) / 2;
}
}
printf("%lld\n", ans);
return 0;
}

8. LuoguP6326 - Shopping

首先不妨连通块包含根。那么显然有一个 O(nV2) 的做法。考虑如何优化,设现在考虑到 xfx 为不考虑点 x 的背包数组:

  • 若不选 x,则 x 的子树全部不能选,更新到 x+sizx
  • 若选 x,强制选一个 x 后更新到 x+1

使用单调队列优化多重背包即可做到 O(nV)。那么套上个点分治即可 O(nlognV) 解决。

点击查看代码
// Problem: P6326 Shopping
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P6326
// Memory Limit: 125 MB
// Time Limit: 1500 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 510, M = 4010;
int n, m, w[N], c[N], d[N];
vector<int> g[N];
int siz[N], ban[N], tmp, mn, cnt, dfn[N];
int f[N][M], h[2][M], ans, q[M];
void pr(int x, int fa){
dfn[++cnt] = x;
siz[x] = 1;
for(int i : g[x]){
if(i != fa && !ban[i]){
pr(i, x);
siz[x] += siz[i];
}
}
}
void ks(int p){
for(int y = 0; y < c[p]; ++ y){
int l = 1, r = 0;
for(int x = 0; x * c[p] + y <= m; ++ x){
while(l <= r && q[l] < x - d[p] + 1){
++ l;
}
while(l <= r && h[0][q[r]*c[p]+y] - q[r]*w[p] < h[0][x*c[p]+y] - x*w[p]){
-- r;
}
q[++r] = x;
h[1][x*c[p]+y] = h[0][q[l]*c[p]+y] - q[l]*w[p]+x*w[p];
}
}
}
void dp(int rt){
for(int i = 0; i <= siz[rt] + 1; ++ i){
memset(f[i], 0xcf, sizeof(f[i]));
}
f[1][0] = 0;
for(int p = 1; p <= siz[rt]; ++ p){
int x = dfn[p];
for(int i = 0; i <= m; ++ i){
f[p+siz[x]][i] = max(f[p+siz[x]][i], f[p][i]);
}
for(int j = m; j >= 0; -- j){
if(j < c[x]){
h[0][j] = -1e9;
} else {
h[0][j] = f[p][j-c[x]] + w[x];
}
}
ks(x);
for(int j = 0; j <= m; ++ j){
f[p+1][j] = max(f[p+1][j], h[1][j]);
ans = max(ans, h[1][j]);
}
}
}
void cs(int x, int fa){
siz[x] = 1;
for(int i : g[x]){
if(!ban[i] && i != fa){
cs(i, x);
siz[x] += siz[i];
}
}
}
void fc(int x, int fa, int sum){
for(int i : g[x]){
if(!ban[i] && i != fa){
fc(i, x, sum);
}
}
int mx = sum - siz[x];
for(int i : g[x]){
if(!ban[i] && i != fa){
mx = max(mx, siz[i]);
}
}
if(mx < mn){
tmp = x;
mn = mx;
}
}
void cl(int x){
cs(x, 0);
mn = 1e9;
fc(x, 0, siz[x]);
int c = tmp;
cnt = 0;
pr(c, 0);
dp(c);
ban[c] = 1;
for(int i : g[c]){
if(!ban[i]){
cl(i);
}
}
}
int main(){
int T = 1;
scanf("%d", &T);
while(T--){
scanf("%d%d", &n, &m);
for(int i = 1; i <= n; ++ i) scanf("%d", &w[i]);
for(int i = 1; i <= n; ++ i) scanf("%d", &c[i]);
for(int i = 1; i <= n; ++ i) scanf("%d", &d[i]);
for(int i = 1; i < n; ++ i){
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
cl(1);
printf("%d\n", ans);
for(int i = 1; i <= n; ++ i){
ban[i] = siz[i] = 0;
vector<int> ().swap(g[i]);
}
ans = 0;
}
return 0;
}

9. LuoguP8476 -「GLR-R3」惊蛰

考虑设 fi(x) 表示考虑到第 i 个,目前 bix 的最优解。那么这个函数肯定是单调递增的。考虑一次 i1i 相当于:

  • <ai 的部分全体 +C
  • ai 的部分全体 ai 再加上下标;
  • 取后缀 min,即二分出 [0,ai1]>fi(ai) 的部分修改为 fi(ai)

发现都可以线段树维护。每个节点维护推平、加、加下标三个 tag 再加一个区间 max。修改时先推平再加。

但是这题如果直接动态开点空间应该过不去,所以可以先离散化(这样加下标的部分变为加 bi,由于都是单增的所以区别不大)。

点击查看代码
// Problem: GLR-R3 - 惊蛰
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/P8476
// Memory Limit: 512 MB
// Time Limit: 3000 ms
//
// Powered by CP Editor (https://cpeditor.org)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 10;
int n, C, a[N], b[N], tot = 1;
struct node{
ll ad, mx, sp;
int ls, rs, l, r, tg;
} t[N*12];
void psd(int p){
int mid = t[p].l + t[p].r >> 1;
if(!t[p].ls && t[p].l != t[p].r){
t[p].ls = ++ tot;
t[t[p].ls].l = t[p].l;
t[t[p].ls].r = mid;
}
if(!t[p].rs && t[p].l != t[p].r){
t[p].rs = ++ tot;
t[t[p].rs].l = mid + 1;
t[t[p].rs].r = t[p].r;
}
if(t[p].sp != -1){
t[t[p].ls].mx = t[t[p].rs].mx = t[p].sp;
t[t[p].ls].tg = t[t[p].ls].ad = 0;
t[t[p].ls].sp = t[p].sp;
t[t[p].rs].tg = t[t[p].rs].ad = 0;
t[t[p].rs].sp = t[p].sp;
t[p].sp = -1;
}
t[t[p].ls].tg += t[p].tg;
t[t[p].ls].mx += (ll)t[p].tg * b[t[t[p].ls].r];
t[t[p].rs].tg += t[p].tg;
t[t[p].rs].mx += (ll)t[p].tg * b[t[t[p].rs].r];
t[p].tg = 0;
t[t[p].ls].ad += t[p].ad;
t[t[p].ls].mx += t[p].ad;
t[t[p].rs].ad += t[p].ad;
t[t[p].rs].mx += t[p].ad;
t[p].ad = 0;
}
void psu(int p){
t[p].mx = max(t[t[p].ls].mx, t[t[p].rs].mx);
}
void add(int p, int l, int r, int ql, int qr, ll v){
if(qr < l || r < ql) return;
if(ql <= l && r <= qr){
t[p].mx += v;
t[p].ad += v;
} else {
int mid = l + r >> 1;
psd(p);
add(t[p].ls, l, mid, ql, qr, v);
add(t[p].rs, mid+1, r, ql, qr, v);
psu(p);
}
}
void tag(int p, int l, int r, int ql, int qr){
if(qr < l || r < ql) return;
if(ql <= l && r <= qr){
t[p].mx += b[t[p].r];
++ t[p].tg;
} else {
int mid = l + r >> 1;
psd(p);
tag(t[p].ls, l, mid, ql, qr);
tag(t[p].rs, mid+1, r, ql, qr);
psu(p);
}
}
void mdf(int p, int l, int r, int ql, int qr, ll v){
if(qr < l || r < ql) return;
if(ql <= l && r <= qr){
t[p].mx = t[p].sp = v;
t[p].tg = t[p].ad = 0;
} else {
int mid = l + r >> 1;
psd(p);
mdf(t[p].ls, l, mid, ql, qr, v);
mdf(t[p].rs, mid+1, r, ql, qr, v);
psu(p);
}
}
ll qry(int p, int l, int r, int x){
if(l == r){
return t[p].mx;
} else {
int mid = l + r >> 1;
psd(p);
if(x <= mid) return qry(t[p].ls, l, mid, x);
else return qry(t[p].rs, mid+1, r, x);
}
}
int fnd(int p, int l, int r, int ql, int qr, ll v){
if(l == r){
return l;
} else {
int mid = l + r >> 1;
psd(p);
if(t[t[p].ls].mx >= v){
return fnd(t[p].ls, l, mid, ql, qr, v);
} else {
return fnd(t[p].rs, mid+1, r, ql, qr, v);
}
}
}
int main(){
scanf("%d%d", &n, &C);
if(!C){
puts("0");
return 0;
}
bool flg = 1;
for(int i = 1; i <= n; ++ i){
scanf("%d", &a[i]);
if(a[i] < a[i-1]){
flg = 0;
}
b[i] = a[i];
}
if(flg){
ll sum = 0, ans = 8e18;
for(int i = 1; i <= n; ++ i){
sum += a[i];
ans = min(ans, a[i] * (ll)i - sum + ((ll)n-i) * C);
}
printf("%lld\n", ans);
return 0;
}
sort(b + 1, b + n + 1);
int mx = unique(b + 1, b + n + 1) - b - 1;
t[1].l = 1, t[1].r = mx;
for(int i = 1; i <= n; ++ i){
a[i] = lower_bound(b + 1, b + mx + 1, a[i]) - b;
add(1, 1, mx, 1, mx, C);
add(1, 1, mx, a[i], mx, -b[a[i]]-C);
tag(1, 1, mx, a[i], mx);
ll val = qry(1, 1, mx, a[i]);
int p = fnd(1, 1, mx, 1, a[i]-1, val);
mdf(1, 1, mx, p, a[i]-1, val);
}
printf("%lld\n", qry(1, 1, mx, 1));
return 0;
}
posted @   KiharaTouma  阅读(13)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起