2024.08 别急记录

1. ARC072F - Dam

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

点击查看代码
//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

考虑设 \(f_i(x)\) 表示前 \(i\) 个区间,第 \(i\) 个区间左端点移动到 \(x\) 的最小答案。设 \(len_i=r_i-l_i\),有递推式:

\[f_i(x)=|x-l_i|+\min_{k\in[x-len_{i-1},x+len_i]}\{f_j(k)\} \]

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

发现此时不方便求出 \(f_n(\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(n^2)\) 做法:设 \(f_{i,0/1/2/3}\) 表示子树 \(i\)\(i\) 向下延伸 \(0/1/2/3\) 的答案。每次转移到节点 \(x\) 时维护辅助数组 \(g_{i,j,0/1}\) 表示考虑 \(x\) 的前 \(i\) 个子树,\(1-3\) 的个数为 \(j\)\(2\) 的个数奇偶性为 \(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\) 个节点,每个节点度数分别为 \(d_1,d_2,...,d_n\) 的有标号无根树的个数为 \(\dfrac{(n-2)!}{\prod(d_i-1)!}\)

根据 prufer 数列,数列中 \(i\) 的出现个数即为 \(d_i-1\),根据可重集计数易证引理成立。

\(A=\prod(a_i-1)!\)

Sub 1. \(m=n-1\)

此时构成一棵树,\(d_i=a_i\)。根据引理得 \(ans=\dfrac{(n-2)!}{A}\)

Sub 2. \(m=n\)

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

\[ans=\dfrac{(n-1)!}{A(sum-1)!}*\dfrac{(sum-1)!}2=\dfrac{(n-1)!}{2A} \]

Sub 3. \(m=n+1\)

此时分两种情况:两个环有共边、两个环无共边。设 \(sum=2n-\sum a_i\)

Sub 3A. 有共边

设这两个环交集为路径 \(x\to y\)。则 \(d_x=a_x+2,d_y=a_y+2\),环上其他点 \(i\)\(d_i=a_i+1\);非环上的点 \(j\)\(d_j=a_j\)

所以环上点数仍然为 \(sum=2n-\sum a_i\)

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

将两个环缩成一个方点,其它部分答案依旧为 \(\dfrac{(n-1)!}{A(sum-1)!}\)。二者乘起来得:

\[ans=\dfrac{(n-1)!sum(sum+2)(sum-3)}{24A} \]

Sub 3B. 无共边

此时两个环上点 \(i\)\(d_i=a_i+1\),否则有 \(d_i=a_i\)。两个环上共 \(sum+2\) 个点。

枚举一个环上点数 \(j(j\geq 3, sum+2-j\geq 3)\)。将两个环分别设为两个方点,有方案数为 \(\dfrac{n!}{A(j-1)!(sum-j+1)!}\)

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

有连边,就相当于构成了一个大小为 \(sum\) 的环,方案数为 \(\dfrac{(n-1)!}{A(sum-1)!}\)。而每个点可以选择连哪个方点,答案要乘上 \(\dbinom{sum}{j-1}\)

二者相减,得 \(\dfrac{(n-1)!(n-sum)}{A(j-1)!(sum-j+1)!}\)

同样要乘上环排列,得 \(\dfrac{(n-1)!(n-sum)}{4A}\)

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

\[ans=\dfrac{(n-1)!(n-sum)(sum-3)}{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\) 为完全积性函数。观察到 \(h*1=[n=q^2]\)。杜教筛即可。

点击查看代码
//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(\log n)\)
  2. 对于 \(x,y\) 以及他们在点分树上的 lca \(l\),有 \(l\) 在原树 \(x\to y\) 路径上。

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

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

一些优化:

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

证明:

  • 点分树中 \(x\) 的子树是原树中一个包含 \(x\) 的连通块,自然 \(\max dep< siz\)
  • 点分树中 \(x\) 的子树与 \(f_x\) 是原树中一个包含 \(x\) 的连通块与一个直接与此连通块相邻的点,自然 \(\max dep <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(nV^2)\) 的做法。考虑如何优化,设现在考虑到 \(x\)\(f_x\) 为不考虑点 \(x\) 的背包数组:

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

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

点击查看代码
// 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」惊蛰

考虑设 \(f_{i}(x)\) 表示考虑到第 \(i\) 个,目前 \(b_i\geq x\) 的最优解。那么这个函数肯定是单调递增的。考虑一次 \(i-1\to i\) 相当于:

  • \(<a_i\) 的部分全体 \(+C\)
  • \(\geq a_i\) 的部分全体 \(-a_i\) 再加上下标;
  • 取后缀 min,即二分出 \([0,a_i-1]\)\(>f_i(a_i)\) 的部分修改为 \(f_i(a_i)\)

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

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

点击查看代码
// 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 @ 2024-08-07 14:14  KiharaTouma  阅读(8)  评论(0编辑  收藏  举报