2024.06 别急记录

1. Ynoi2009 - rprsvq

首先有方差 \(=\dfrac{n-1}{n^2}\sum a_i^2-\dfrac2{n^2}\sum a_ia_j\)

还有结论:对于大小为 \(n\) 的集合 \(S\),所有 \(\dbinom nt\) 个大小为 \(t\) 的子集中,含有给定大小为 \(k\) 的子集的集合个数为 \(\dbinom {n-k}{t-k}\)

那么一个序列 \(a_1,...,a_n\) 的子序列的方差和就是:

\(\sum_{t=1}^n\dfrac{t-1}{t^2}\dbinom{n-1}{t-1}\sum a_i^2-2\sum_{t=1}^n\dfrac 1{t^2}\dbinom{n-2}{t-2}\sum a_ia_j\)

\(\sum_{t=1}^n\dfrac{\dbinom{n-2}{t-2}}{t^2}[n\sum a_i^2-(\sum a_i)^2]\)

右半边使用线段树维护,左半边 \(=\dfrac{2^n-1-\sum_{t=1}^n\dfrac 1t\dbinom nt}{n(n-1)}\)。那个和式是 A103213 除掉阶乘,可以递推计算。

点击查看代码
//P6108
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

const int N = 5e6 + 10;
const ll P = 998244353;
int n, m;
ll pw2[N], fg[N], fac[N], inv[N], vg[N];
struct node{
	int sum, len, pw, tag;
} t[N*4];

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;
}

void build(int p, int l, int r){
	t[p].len = r - l + 1;
	if(l != r){
		int mid = l + r >> 1;
		build(p<<1, l, mid);
		build(p<<1|1, mid+1, r);
	}
}
void psd(int p, ll v){
	t[p].tag = ((ll)t[p].tag + v) % P;
	t[p].pw = ((ll)t[p].pw + 2ll * t[p].sum * v) % P;
	t[p].pw = ((ll)t[p].pw + v * v % P * t[p].len) % P;
	t[p].sum = ((ll)t[p].sum + t[p].len * v) % P;
}
void add(int p, int l, int r, int ql, int qr, ll v){
	if(qr < l || r < ql){
		return;
	} else if(ql <= l && r <= qr){
		psd(p, v);
	} else {
		int mid = l + r >> 1;
		psd(p<<1, t[p].tag);
		psd(p<<1|1, t[p].tag);
		t[p].tag = 0;
		add(p<<1, l, mid, ql, qr, v);
		add(p<<1|1, mid+1, r, ql, qr, v);
		t[p].sum = ((ll)t[p<<1].sum + t[p<<1|1].sum) % P;
		t[p].pw = ((ll)t[p<<1].pw + t[p<<1|1].pw) % P;
	}
}
pair<ll, ll> qry(int p, int l, int r, int ql, int qr){
	if(qr < l || r < ql){
		return make_pair(0, 0);
	} else if(ql <= l && r <= qr){
		return make_pair(t[p].sum, t[p].pw);
	} else {
		int mid = l + r >> 1;
		psd(p<<1, t[p].tag);
		psd(p<<1|1, t[p].tag);
		t[p].tag = 0;
		pair<ll, ll> x = qry(p<<1, l, mid, ql, qr);
		pair<ll, ll> y = qry(p<<1|1, mid+1, r, ql, qr);
		x.first = (x.first + y.first) % P;
		x.second = (x.second + y.second) % P;
		return x;
	}
}

void solve(){
	scanf("%d%d", &n, &m);
	build(1, 1, n);
	pw2[0] = fac[0] = 1;
	fg[1] = 1, fg[2] = 5, fg[3] = 29, fg[4] = 206;
	for(int i = 1; i <= n; ++ i){
		pw2[i] = pw2[i-1] * 2 % P;
		fac[i] = fac[i-1] * i % P;
		fg[i+4] = 2ll * (i+3) * (i+2) % P * (i+2) % P * fg[i+1] % P;
		fg[i+4] += (P-1) * (i+3) % P * (13+i*5) % P * fg[i+2] % P;
		fg[i+4] += (13+i*4) * fg[i+3] % P;
		fg[i+4] %= P;
		vg[i] = qp((ll)i * (i-1) % P, P-2);
	}
	inv[n] = qp(fac[n], P-2);
	for(int i = n-1; i >= 0; -- i){
		inv[i] = inv[i+1] * (i+1) % P;
	}
	for(int i = 1; i <= m; ++ i){
		int op, l, r;
		scanf("%d%d%d", &op, &l, &r);
		if(op == 1){
			ll a;
			scanf("%lld", &a);
			add(1, 1, n, l, r, a);
		} else {
			auto tmp = qry(1, 1, n, l, r);
			ll len = r - l + 1;
			ll vx = len * tmp.second % P;
			vx = (vx - tmp.first * tmp.first % P + P) % P;
			ll vy = (pw2[len] - 1 - inv[len] * fg[len] % P + P + P) % P * vg[len] % P;
			printf("%lld\n", vx * vy % P);
		}
	}
}

2. CEOI2004 - Sweets

使用生成函数。

单个罐子 \(f_i(x)=\sum_{j=0}^{m_i}x^j=\dfrac{1-x^{m_i+1}}{1-x}\)

合起来 \(F(x)=\prod f_i(x)=(1-x)^{-n}\prod(1-x^{m_i+1})\)

左边的部分由牛顿二项式定理 \(=\sum_{i=0}^{\inf}\dbinom{-n}i(-x)^i\)

\(=\sum_{i=0}^{\inf}\dfrac{(-n)^{\underline i}}{i!}(-x)^i\)

\(=\sum_{i=0}^{\inf}\dfrac{(n+i-1)^{\underline i}}{i!}x^i\)

\(=\sum_{i=0}^{\inf}\dbinom{n+i-1}ix^i\)

右边部分 dfs 枚举次数 \(k\),正负符号 \(w\),答案为 \(w\sum_{i=a-k}^{b-k}\dbinom{n+i-1}i\)

\(w[\dbinom{n+b-k}{n}-\dbinom{n+a-k-1}{n}]\)

由于 \(2004\) 不是质数,组合数可以 \(O(n^2\log n)\) 计算。

总复杂度 \(O(2^nn^2\log n)\)

点击查看代码
//P6078
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

#define int long long
const int P = 2004, N = 1e7 + 10;
int n, a, b, p[13], ans;

int cl(int x){
	if(x < 0){
		return 0;
	}
	int tmp[13];
	for(int i = 1; i <= n; ++ i){
		tmp[i] = x + i;
	}
	for(int i = 1; i <= n; ++ i){
		int nw = i;
		for(int j = 1; j <= n; ++ j){
			int p = __gcd(nw, tmp[j]);
			tmp[j] /= p;
			nw /= p;
		}
	}
	int ans = 1;
	for(int i = 1; i <= n; ++ i){
		ans = ans * tmp[i] % P;
	}
	return ans;
}

void dfs(int x, int sum, int pl){
	if(x == n + 1){
		ans += pl * (cl(b-sum) - cl(a-sum-1) + P) % P;
		ans %= P;
	} else {
		dfs(x+1, sum, pl);
		dfs(x+1, sum+p[x]+1, P-pl);
	}
}

void solve(){
	scanf("%d%d%d", &n, &a, &b);
	for(int i = 1; i <= n; ++ i){
		scanf("%d", &p[i]);
	}
	dfs(1, 0, 1);
	printf("%d\n", ans);
}

3. APC001F - XOR Tree

考虑每个节点赋权值为相邻边异或和。那么每次改链操作相当于选择两个点点权异或某个数。目标变为所有点点权均为 \(0\)

若两个点权相同肯定是这两个点操作最优。于是最后只剩下 \(2^15\) 种点权集合情况,状压即可。

点击查看代码
//AT_apc001_f
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

const int N = 1e5 + 10;
int n, v[N], f[1<<15], c[20];

int dfs(int x){
	if(f[x] != -1){
		return f[x];
	}
	f[x] = 0x3f3f3f3f;
	for(int i = 1; i <= 15; ++ i){
		for(int j = 1; j <= 15; ++ j){
			if(!(x & (1<<i-1)) || !(x & (1<<j-1)) || i == j){
				continue;
			}
			for(int k = 1; k <= 15; ++ k){
				int p = i ^ k, q = j ^ k, ad = 1;
				if((x<<1) & (1<<p)){
					++ ad;
				}
				if((x<<1) & (1<<q)){
					++ ad;
				}
				f[x] = min(f[x], ad + dfs(((x<<1)^(1<<i)^(1<<j)^(1<<p)^(1<<q))>>1));
			}
		}
	}
	return f[x];
}

void solve(){
	scanf("%d", &n);
	for(int i = 1; i < n; ++ i){
		int x, y, a;
		scanf("%d%d%d", &x, &y, &a);
		v[x] ^= a;
		v[y] ^= a;
	}
	for(int i = 0; i < n; ++ i){
		++ c[v[i]];
	}
	int st = 0, ans = 0;
	for(int i = 1; i <= 15; ++ i){
		if(c[i] & 1){
			st |= (1 << i - 1);
		}
		ans += c[i] >> 1;
	}
	memset(f, -1, sizeof(f));
	f[0] = 0;
	printf("%d\n", ans + dfs(st));
}

4. APC001E - Antennas on Tree

显然一个合法解的充要条件是对于任意一个节点,它的子树中至多有一棵中没有标记点,那么标记叶子显然是更优的。

考虑一个不存在度数 \(=2\) 的节点的树,把若干个相同父亲的叶子中任意取消标记一个依旧合法。对于存在度数 \(=2\) 的节点,将这些点删掉,两侧重新连一条边即可。

点击查看代码
//AT_apc001_e
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

const int N = 1e5 + 10;
int n, f[N][3], mx;
vector<int> g[N], h[N];

void dfs(int x, int fa, int tp){
	if(g[x].size() != 2){
		h[tp].push_back(x);
	} else {
		for(int i : g[x]){
			if(i != fa){
				dfs(i, x, tp);
			}
		}
	}
}

void solve(){
	scanf("%d", &n);
	for(int i = 1; i < n; ++ i){
		int a, b;
		scanf("%d%d", &a, &b);
		++ a;
		++ b;
		g[a].push_back(b);
		g[b].push_back(a);
		mx = max(mx, (int)g[a].size());
		mx = max(mx, (int)g[b].size());
	}
	if(mx <= 2){
		puts("1");
	} else {
		for(int i = 1; i <= n; ++ i){
			if(g[i].size() != 2){
				for(int j : g[i]){
					dfs(j, i, i);
				}
			}
		}
		int ans = 0;
		for(int i = 1; i <= n; ++ i){
			int nw = -1;
			for(int j : h[i]){
				if(h[j].size() == 1){
					++ nw;
				}
			}
			ans += max(0, nw);
		}
		printf("%d\n", ans);
	}
}

5. AGC028D - Chords

首先断环为链,统计每个连通块出现次数,那么一对 \([i,j]\) 有贡献当且仅当 \([i,j]\) 内部全部连完且不连到外面,且 \(i,j\) 处在一个连通块中。

定义 \(f_{i,j}\)\([i,j]\) 有贡献的方案数,\(c_{i,j}\) 表示 \([i,j]\) 中未连的点数,\(g_i\) 表示 \(i\) 个点任意连方案数。

显然有当 \(i\) 为偶数时 \(g_i=(i-1)!!\)\(c_{i,j}\) 可以预处理求出。

\(f_{i,j}\) 可以使用总方案减去 \(i,j\) 不连通的方案。枚举 \(i\) 连通块最右端得:\(f_{i,j}=g_{c_{i,j}}-\sum f_{i,k}g_{c_{k+1,j}}\)

最后总方案数是 \(\sum f_{i,j}g_{c_{1,i-1}+c_{j+1,n+n}}\)

点击查看代码
//AT_agc028_d
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

const int N = 610;
const ll P = 1e9 + 7;
ll f[N][N], g[N], ans;
int n, k, to[N], c[N][N], ok[N][N];

void solve(){
	scanf("%d%d", &n, &k);
	for(int i = 1; i <= k; ++ i){
		int a, b;
		scanf("%d%d", &a, &b);
		to[a] = b;
		to[b] = a;
	}
	for(int i = 1; i <= n+n; ++ i){
		int mn = n+n+1, mx = 0, cnt = 0;
		for(int j = i; j <= n+n; ++ j){
			if(to[j]){
				mn = min(mn, to[j]);
				mx = max(mx, to[j]);
				++ cnt;
			}
			c[i][j] = j - i + 1 - cnt;
			if(i <= mn && mx <= j){
				ok[i][j] = 1;
			}
		}
	}
	g[0] = 1;
	for(int i = 2; i <= n+n; ++ i){
		g[i] = g[i-2] * (i-1) % P;
	}
	for(int len = 2; len <= n+n; len += 2){
		for(int i = 1; i + len - 1 <= n+n; ++ i){
			int j = i + len - 1;
			if(!ok[i][j]){
				continue;
			}
			f[i][j] = g[c[i][j]];
			for(int k = i+1; k < j; k += 2){
				if(!ok[i][k]){
					continue;
				}
				f[i][j] -= f[i][k] * g[c[k+1][j]] % P;
				f[i][j] = (f[i][j] + P) % P;
			}
			ans += f[i][j] * g[c[1][i-1]+c[j+1][n+n]] % P;
			ans %= P;
		}
	}
	printf("%lld\n", ans);
}

6. kupc2013 - タイル置き

如何在患有不会计数症的情况下通过 AT_kupc2013_j タイル置き

\(h\leq w\)

观察到 \(n=1\) 的答案是 \(2hw-h-w\)

观察到 \(n=2\) 的答案是 \((2hw-h-w)^2\) 减去一堆有重合的部分。重合包括完全重叠、拐角和三连三种。

观察到 \(n=2\) 的答案形如 \(ah^2w^2+bh^2w+ch^2+dhw^2+...\)

于是猜测 \(n=k\) 时答案形如 \(\sum a_{i,j}h^iw^j\)

但是观察到 \(n=2\) 时处理三连的重合时需满足 \(h,w>1\) 时才正确。(因为 \(h=1\) 时放不下,会出现 \(-(-1)\) 的情况)

所以当 \(n=k\)\(h,w\geq n\) 的情况下答案为多项式。其它情况可以使用矩阵快速幂 \(O(2^{3h}n^3\log w)\) 求解。

为多项式时,首先预处理出 \(h,w\in[n,2n]\) 时的答案,这时可以直接状压 \(O(4^nn^4)\) 求完。然后使用高斯消元或者横着拉插一遍,系数求出来再竖着拉插一遍得到最终系数。

然后就做完了。

点击查看代码
//AT_kupc2013_j
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const ll P = 1e9 + 7;
const ll i2 = 500000004;
const ll i3 = 333333336;
const ll i4 = 250000002;
const ll i5 = 400000003;
const ll i6 = 166666668;
const ll i8 = 125000001;
const ll i10 = i2 * i5 % P;
const ll i12 = 83333334;
const ll i15 = i3 * i5 % P;
const ll i24 = 41666667;
const ll i120 = i10 * i12 % P;
ll h, w;
int n;

namespace solve1{
    ll f[13][1<<10][13];
    ll main(){
        memset(f, 0, sizeof(f));
        f[0][0][0] = 1;
        for(int i = 1; i <= w; ++ i){
        for(int pr = 0; pr <= n; ++ pr){
        for(int j = 0; j < (1 << h); ++ j){
        int fj = (1<<h)-1-j;
        for(int k = fj; ; k = (k-1) & fj){
        int fk = j|k;
        fk = (1<<h)-1-fk;
        for(int l = fk; ; l = (l-1) & fk){
        if((fk | (l<<1) | l) == fk && (l&(l<<1)) == 0){
        int nw = __builtin_popcount(k|l);
        // printf("%d %d %d %d %d\n", (j|k|l|(l<<1))-(j^k^l^(l<<1)), j, k, l, l<<1);
        if(pr+nw <= n)
        f[i][k][pr+nw] = (f[i][k][pr+nw] + f[i-1][j][pr]) % P;
        }if(!l) break;}
        if(!k) break;}
        }
        }
        }
        // printf("%lld,", f[w][0][n]);
        return f[w][0][n];
    }
}

namespace solve2{
    struct mat{
        ll a[256][256];
    } a;
    int ys[32][8], cnt;
    mat mul(mat &x, mat &y){
        mat z;
        for(int i = 0; i < cnt; ++ i) for(int j = 0; j < cnt; ++ j) z.a[i][j] = 0;
        for(int k = 0; k < cnt; ++ k){
            for(int i = 0; i < cnt; ++ i){
                for(int j = 0; j < cnt; ++ j){
                    z.a[i][j] = (z.a[i][j] + x.a[i][k] * y.a[k][j]) % P;
                }
            }
        }
        return z;
    }
    mat qp(mat x, int v){
        mat z = x;
        -- v;
        while(v){
            if(v & 1) z = mul(z, x);
            x = mul(x, x);
            v >>= 1;
        }
        return z;
    }
    int main(){
        cnt = 0;
        for(int i = 0; i < (1 << h); ++ i){
            for(int j = 0; j <= n; ++ j){
                ys[i][j] = cnt ++;
            }
        }
        for(int pr = 0; pr <= n; ++ pr){
        for(int j = 0; j < (1 << h); ++ j){
        int fj = (1<<h)-1-j;
        for(int k = fj; ; k = (k-1) & fj){
        int fk = j|k;
        fk = (1<<h)-1-fk;
        for(int l = fk; ; l = (l-1) & fk){
        if((fk | (l<<1) | l) == fk && (l&(l<<1)) == 0){
        int nw = __builtin_popcount(k|l);
        // printf("%d %d %d %d %d\n", (j|k|l|(l<<1))-(j^k^l^(l<<1)), j, k, l, l<<1);
        if(pr+nw <= n)
        ++ a.a[ys[j][pr]][ys[k][pr+nw]];
        }if(!l) break;}
        if(!k) break;}
        }
        }
        a = qp(a, w);
        printf("%lld\n", a.a[ys[0][0]][ys[0][n]]);
        return 0;
    }
}

namespace solven3{
    int main(){
        ll x = h - 2, y = w - 2;
        ll f3 = (4*i3%P*y%P*y%P*y%P + 6     *y%P*y%P + 9      *y%P + 9*i2%P        ) % P;
        ll f2 = (6     *y%P*y%P*y%P + 12    *y%P*y%P + 3*i2%P *y%P - 9*i2%P + P    ) % P;
        ll f1 = (9     *y%P*y%P*y%P + 3*i2%P*y%P*y%P + 17*i3%P*y%P + 4             ) % P;
        ll f0 = (9*i2%P*y%P*y%P*y%P - 9*i2%P*y%P*y%P + 4      *y%P - 4      + P + P) % P;
        ll ans = f3 * x % P * x % P * x % P;
        ans += f2 * x % P * x % P;
        ans += f1 * x % P;
        ans += f0;
        printf("%lld\n", ans % P);
        return 0;
    }
}
namespace solven4{
    int main(){
        ll x = h - 3, y = w - 3;
        ll f4 = (2*i3%P   *y%P*y%P*y%P*y%P + 20*i3%P *y%P*y%P*y%P + 25        *y%P*y%P + 125*i3%P  *y%P + 625*i24%P ) % P;
        ll f3 = (20*i3%P  *y%P*y%P*y%P*y%P + 52      *y%P*y%P*y%P + 146       *y%P*y%P + 515*i3%P  *y%P + 275*i4%P  ) % P;
        ll f2 = (25       *y%P*y%P*y%P*y%P + 146     *y%P*y%P*y%P + 3757*i12%P*y%P*y%P + 3677*i12%P*y%P + 2843*i24%P) % P;
        ll f1 = (125*i3%P *y%P*y%P*y%P*y%P + 515*i3%P*y%P*y%P*y%P + 3677*i12%P*y%P*y%P + 2933*i12%P*y%P + 275*i4%P  ) % P;
        ll f0 = (625*i24%P*y%P*y%P*y%P*y%P + 275*i4%P*y%P*y%P*y%P + 2843*i24%P*y%P*y%P + 275*i4%P  *y%P + 24        ) % P;
        ll ans = f4 * x % P * x % P * x % P * x % P;
        ans += f3 * x % P * x % P * x % P;
        ans += f2 * x % P * x % P;
        ans += f1 * x % P;
        ans += f0;
        printf("%lld\n", ans % P);
        return 0;
    }
}
namespace solven5{
    int main(){
        ll x = h - 4, y = w - 4;
        ll f5 = (4*i15%P     *y%P*y%P*y%P*y%P*y%P + 14*i3%P    *y%P*y%P*y%P*y%P + 98*i3%P    *y%P*y%P*y%P + 343*i3%P    *y%P*y%P + 2401*i12%P  *y%P + 16807*i120%P) % P;
        ll f4 = (14*i3%P     *y%P*y%P*y%P*y%P*y%P + 72         *y%P*y%P*y%P*y%P + 1321*i3%P  *y%P*y%P*y%P + 3997*i3%P   *y%P*y%P + 15925*i8%P  *y%P + 14063*i12%P ) % P;
        ll f3 = (98*i3%P     *y%P*y%P*y%P*y%P*y%P + 1321*i3%P  *y%P*y%P*y%P*y%P + 4707*i2%P  *y%P*y%P*y%P + 74977*i12%P *y%P*y%P + 99137*i12%P *y%P + 34895*i8%P  ) % P;
        ll f2 = (343*i3%P    *y%P*y%P*y%P*y%P*y%P + 3997*i3%P  *y%P*y%P*y%P*y%P + 74977*i12%P*y%P*y%P*y%P + 44350*i3%P  *y%P*y%P + 422351*i24%P*y%P + 100897*i12%P) % P;
        ll f1 = (2401*i12%P  *y%P*y%P*y%P*y%P*y%P + 15925*i8%P *y%P*y%P*y%P*y%P + 99137*i12%P*y%P*y%P*y%P + 422351*i24%P*y%P*y%P + 190659*i10%P*y%P + 125521*i15%P) % P;
        ll f0 = (16807*i120%P*y%P*y%P*y%P*y%P*y%P + 14063*i12%P*y%P*y%P*y%P*y%P + 34895*i8%P *y%P*y%P*y%P + 100897*i12%P*y%P*y%P + 125521*i15%P*y%P + 3380        ) % P;
        ll ans = f5 * x % P * x % P * x % P * x % P * x % P;
        ans += f4 * x % P * x % P * x % P * x % P;
        ans += f3 * x % P * x % P * x % P;
        ans += f2 * x % P * x % P;
        ans += f1 * x % P;
        ans += f0;
        printf("%lld\n", ans % P);
        return 0;
    }
}

int main(){
    freopen("domino.in", "r", stdin);
    freopen("domino.out", "w", stdout);
    scanf("%lld%lld%d", &h, &w, &n);
    if(h > w) swap(h, w);
    if(n == 1){
        printf("%lld\n", (2*h*w-h-w)%P);
    } else if(n == 2){
        ll x = (2*h*w-h-w) % P;
        ll y = (h*w-h-w+1) % P;
        ll ans = x*x%P-x-8*y%P;
        if(h > 1){
            ans -= 2*w*(h-2)%P;
        }
        if(w > 1){
            ans -= 2*h*(w-2)%P;
        }
        ans = ans * i2 % P;
        ans = (ans % P + P) % P;
        printf("%lld\n", ans);
    } else if(w <= 10){
        printf("%lld\n", solve1::main());
    } else if(h <= 5){
        solve2::main();
    } else if(n == 3){
        solven3::main();
    } else if(n == 4){
        solven4::main();
    } else if(n == 5){
        solven5::main();
    }
    return 0;
}

7. NOI2020 - 命运

\(f_{x,i}\) 表示 \(x\) 子树内限制最低点深度为 \(i\) 方案数(\(f_{x,0}\) 表示没限制)。

\(f_{u,i} = f_{u,i}(f_{v,[0,dep_u]}+f_{v,[0,i]})+f_{u,[0,i-1]}f_{v,i}\)

进行线段树合并,传参过程维护 \(X=f_{v,[0,dep_u]}+f_{v,[0,i]},Y=f_{u,[0,i-1]}\),在到达叶子时更新。

点击查看代码
//P6773
#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10;
typedef long long ll;
const ll P = 998244353;
int n, m;
vector<int> g[N];
int dep[N], top[N];

void dfs(int x, int fa){
    dep[x] = dep[fa] + 1;
    for(int i : g[x]){
        if(i != fa){
            dfs(i, x);
        }
    }
}

struct node{
    ll sum, mul;
    int ls, rs;
} t[N*40];
int cnt, rt[N];
void psd(int p){
    if(t[p].ls){
        t[t[p].ls].sum = (t[t[p].ls].sum * t[p].mul) % P;
        t[t[p].ls].mul = (t[t[p].ls].mul * t[p].mul) % P;
    }
    if(t[p].rs){
        t[t[p].rs].sum = (t[t[p].rs].sum * t[p].mul) % P;
        t[t[p].rs].mul = (t[t[p].rs].mul * t[p].mul) % P;
    }
    t[p].mul = 1;
}
void add(int &p, int l, int r, int x, int v){
    if(!p){
        p = ++ cnt;
        t[p].mul = 1;
    }
    if(l == r){
        t[p].sum = v;
    } else {
        int mid = l + r >> 1;
        if(x <= mid){
            add(t[p].ls, l, mid, x, v);
        } else {
            add(t[p].rs, mid+1, r, x, v);
        }
        t[p].sum = (t[t[p].ls].sum + t[t[p].rs].sum) % P;
    }
}
ll qry(int p, int l, int r, int ql, int qr){
    if(!p || qr < l || r < ql){
        return 0;
    } else if(ql <= l && r <= qr){
        return t[p].sum;
    } else {
        int mid = l + r >> 1;
        psd(p);
        return (qry(t[p].ls, l, mid, ql, qr) + 
                qry(t[p].rs, mid+1, r, ql, qr)) % P;
    }
}
void mg(int &p, int q, int l, int r, ll &x, ll &y){
    if(!p && !q){
        return;
    } else if(!p){
        x = (x + t[q].sum) % P;
        t[q].sum = (t[q].sum * y) % P;
        t[q].mul = (t[q].mul * y) % P;
        p += q;
    } else if(!q){
        y = (y + t[p].sum) % P;
        t[p].sum = (t[p].sum * x) % P;
        t[p].mul = (t[p].mul * x) % P;
    } else if(l == r){
        ll tp = t[p].sum, tq = t[q].sum;
        x = (x + tq) % P;
        t[p].sum = (t[p].sum * x + t[q].sum * y) % P;
        y = (y + tp) % P;
    } else {
        psd(p);
        psd(q);
        int mid = l + r >> 1;
        mg(t[p].ls, t[q].ls, l, mid, x, y);
        mg(t[p].rs, t[q].rs, mid+1, r, x, y);
        t[p].sum = (t[t[p].ls].sum + t[t[p].rs].sum) % P;
    }
}

void dfss(int x, int fa){
    add(rt[x], 0, n, top[x], 1);
    for(int i : g[x]){
        if(i != fa){
            dfss(i, x);
            ll val = qry(rt[i], 0, n, 0, dep[x]), vv = 0;
            mg(rt[x], rt[i], 0, n, val, vv);
        }
    }
}

int main(){
    scanf("%d", &n);
    for(int i = 1; i < n; ++ i){
        int x, y;
        scanf("%d%d", &x, &y);
        g[x].push_back(y);
        g[y].push_back(x);
    }
    scanf("%d", &m);
    dfs(1, 0);
    for(int i = 1; i <= m; ++ i){
        int x, y;
        scanf("%d%d", &x, &y);
        if(dep[x] < dep[y]){
            swap(x, y);
            top[x] = max(top[x], dep[y]);
        }
    }
    dfss(1, 0);
    printf("%lld\n", qry(rt[1], 0, n, 0, 0));
    return 0;
}

8. JOI Open 2017 - 推土机

考虑答案斜率只有可能为给定点中两点连线斜率,以及这个斜率稍微偏一点点(如样例 3),所以预处理所有斜率并排序,维护一个数组表示以这个斜率从下往上扫到每一个点的顺序。那么答案就为这个数组每个时刻的最大子段和。

对于斜率需稍微偏一点点的情况,可以发现仍然对应的是数组中的某个子段;每个子段也都能找到一个给定斜率,所以做法是正确的。

对于每个点对,它们之间在数组中的先后关系一定至多两种且一定只会变化至多一次,故可以预处理出这一次变化时间,使用单点修改、查询最大子段和的线段树维护数组即可。

但是会有一个问题:若存在多点共线,不妨为 \(A,B,C\) 共线,斜率扫过这一条线时数组中三点顺序会由 \(A,B,C\) 变为 \(C,B,A\)。预处理的结果是在这一刻有三个先后关系变化:\(AB,AC,BC\)。问题就是这三个变化需要一定的顺序。

若以 \(AB,AC,BC\) 的顺序变化,数组 \(ABC\) 会变为 \(CBA\);但若以 \(AC,AB,BC\) 的顺序变化,数组 \(ABC\) 会变为 \(BAC\)

经过打表得知可以使得排列 rev 的变化顺序数量为 A092238,但不是本题重点。

可以证明若原排列为 \(ABCD...\),以 \(AB,AC,AD,...,BC,BD,...,CD,...\) 的顺序变化一定是正确的。所以只需在算法开始之前对点进行排序即可。

时间复杂度 \(O(n^2\log n)\)

点击查看代码
//P10630
#include <bits/stdc++.h>
using namespace std;

const int N = 2010;
int n, x[N], y[N], w[N];
int cnt, pos[N];
map<int, int> mp;
vector<pair<int, int> > cg[N*N];
int tmp[N], tmpp[N];

typedef long long ll;
double p[N*N];
struct tree{
    ll am, lm, rm, sum;
} t[N*4];
void psu(tree &p, tree ls, tree rs){
    p.sum = ls.sum + rs.sum;
    p.lm = max(ls.lm, ls.sum + rs.lm);
    p.rm = max(rs.rm, rs.sum + ls.rm);
    p.am = max(ls.am, rs.am);
    p.am = max(p.am, ls.rm + rs.lm);
}
void add(int p, int l, int r, int x, int v){
    if(l == r){
        tmp[x] = p;
        t[p].sum = v;
        t[p].am = t[p].lm = t[p].rm = max(0, v);
    } else {
        int mid = l + r >> 1;
        if(x <= mid){
            add(p<<1, l, mid, x, v);
        } else {
            add(p<<1|1, mid+1, r, x, v);
        }
        psu(t[p], t[p<<1], t[p<<1|1]);
    }
}

ll ans = 0;

struct node{
    int x, y, w, id;
} a[N];

int st = 0;
bool cmp(node c, node d){
    double cc = c.y - c.x * p[st];
    double dd = d.y - d.x * p[st];
    return fabs(cc-dd) <= 1e-8 ? c.x < d.x : cc<dd;
}
bool cmq(node c, node d, int op){
    double cc = c.y - c.x * p[op];
    double dd = d.y - d.x * p[op];
    return fabs(cc-dd) <= 1e-8 ? c.x < d.x : cc<dd;
}

int main(){
    scanf("%d", &n);
    for(int i = 1; i <= n; ++ i){
        scanf("%d%d%d", &x[i], &y[i], &w[i]);
        a[i] = { x[i], y[i], w[i], i };
    }
    for(int i = 1; i <= n; ++ i){
        for(int j = i + 1; j <= n; ++ j){
            if(x[i] != x[j]){
                double sl = (y[j] - y[i]) * 1.0 / (x[j] - x[i]);
                p[++cnt] = sl;
            }
        }
    }
    sort(p + 1, p + cnt + 1);
    p[0] = p[1] - 1;
    int m = unique(p, p + cnt + 1) - p;
    sort(a + 1, a + n + 1, cmp);
    for(int i = 1; i <= n; ++ i){
        pos[a[i].id] = i;
    }
    for(int i = 1; i <= n; ++ i){
        for(int j = i + 1; j <= n; ++ j){
            if(a[i].x != a[j].x){
                double sl = (a[j].y - a[i].y) * 1.0 / (a[j].x - a[i].x);
                int ps = lower_bound(p, p + m + 1, sl) - p;
                cg[ps].push_back(make_pair(a[i].id, a[j].id));
            }   
        }
    }
    for(int i = 1; i <= n; ++ i){
        add(1, 1, n, pos[i], w[i]);
    }
    for(int i = 0; i <= m; ++ i){
        ans = max(ans, t[1].am);
        for(auto j : cg[i]){
            int x = j.first, y = j.second;
            add(1, 1, n, pos[x], w[y]);
            add(1, 1, n, pos[y], w[x]);
            swap(pos[x], pos[y]);
        }
    }
    ans = max(ans, t[1].am);
    printf("%lld\n", ans);
}

9. NOI Online #2 提高组 - 游戏

\(h_i\) 表示钦定 \(i\) 对的答案;\(g_i\) 表示恰好 \(i\) 对的答案,则有 \(g_i=\sum_{j=i}^{n}(-1)^{i-n}\dbinom in h_i\)

考虑 dp 求出 \(h_i\),设 \(f_{i,j}\) 表示 \(i\) 的子树中钦定 \(j\) 对的方案数。转移直接树形背包即可。最后 \(h_i=(m-i)!f_{1,i}\)

点击查看代码
//P6478
#include <bits/stdc++.h>
using namespace std;

const int N = 5010;
typedef long long ll;
const ll P = 998244353;
int n;
vector<int> g[N];
int sc[N][2], sz[N];
char s[N];
ll f[N][N], tmp[N];
ll C[N][N];
ll fac[N];

void dfs(int x, int fa){
    sc[x][0] += s[x] == '0';
    sc[x][1] += s[x] == '1';
    f[x][0] = 1;
    sz[x] = 1;
    for(int i : g[x]){
        if(i != fa){
            dfs(i, x);
            for(int j = (sz[x] - 1) / 2; j >= 0; -- j){
                for(int k = sz[i] / 2; k >= 0; -- k){
                    tmp[j+k] = (tmp[j+k] + f[x][j] * f[i][k]) % P;
                }
            }
            for(int j = (sz[x] - 1) / 2 + sz[i] / 2; j >= 0; -- j){
                f[x][j] = tmp[j];
                tmp[j] = 0;
            }
            sz[x] += sz[i];
            sc[x][0] += sc[i][0];
            sc[x][1] += sc[i][1];
        }
    }
    for(int i = sz[x] / 2; i > 0; -- i){
        f[x][i] = (f[x][i] + f[x][i-1] * (sc[x]['1'-s[x]] - i + 1)) % P;
    }
}

int main(){
    scanf("%d", &n);
    scanf("%s", s+1);
    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);
    }
    dfs(1, 0);
    C[0][0] = fac[0] = 1;
    for(int i = 1; i <= n; ++ i){
        C[i][0] = 1;
        fac[i] = fac[i-1] * i % P;
        for(int j = 1; j <= i; ++ j){
            C[i][j] = (C[i-1][j-1] + C[i-1][j]) % P;
        }
    }
    for(int i = 0; i <= n/2; ++ i){
        ll ans = 0;
        for(int j = i; j <= n/2; ++ j){
            ll val = C[j][i] * f[1][j] % P * fac[n/2-j] % P;
            if((j-i) & 1){
                val = val * (P-1) % P;
            }
            ans = (ans + val) % P;
        }
        printf("%lld\n", ans);
    }
    return 0;
}

10. NEERC2016 - Mole Tunnels

显然有费用流模型,考虑模拟费用流。

维护 \(f_i,g_i\) 表示点 \(i\) 到子树中每个点再流出的最小费用即取到最小费用的流出点。添加一个流入边时枚举路径 lca,计算出答案后修改路径和 lca 祖先点的 \(f_i,g_i\) 即可。

点击查看代码
//P6122
#include <bits/stdc++.h>
using namespace std;

const int N = 2e5 + 10;
int n, m, c[N], p;
int f[N], g[N];
int eg[N];

#define vdn(i) (eg[i]>0 ? -1 : 1)
#define vup(i) (eg[i]<0 ? -1 : 1)

void upd(int x){
    f[x] = 0x3f3f3f3f;
    if(f[x<<1] + vdn(x<<1) < f[x]){
        f[x] = f[x<<1] + vdn(x<<1);
        g[x] = g[x<<1];
    }
    if(f[x<<1|1] + vdn(x<<1|1) < f[x]){
        f[x] = f[x<<1|1] + vdn(x<<1|1);
        g[x] = g[x<<1|1];
    }
    if(c[x] && f[x] > 0){
        f[x] = 0;
        g[x] = x;
    }
}

signed main(){
    memset(f, 0x3f, sizeof(f));
    scanf("%d%d", &n, &m);
    for(int i = 1; i <= n; ++ i){
        scanf("%d", &c[i]);
    }
    for(int i = n; i >= 1; -- i){
        upd(i);
    }
    long long la = 0;
    for(int i = 1; i <= m; ++ i){
        scanf("%d", &p);
        int x = p, mn = 2e9, ps = 0, nw = 0;
        while(x){
            if(f[x] + nw < mn){
                mn = f[x] + nw;
                ps = x;
            }
            nw += vup(x);
            x >>= 1;
        }
        la += mn;
        printf("%lld ", la);
        x = p;
        while(x != ps){
            ++ eg[x];
            upd(x);
            x >>= 1;
        }
        x = g[ps];
        -- c[x];
        while(x != ps){
            -- eg[x];
            upd(x);
            x >>= 1;
        }
        while(ps){
            upd(ps);
            ps >>= 1;
        }
    }
    return 0;
}

11. AHOI2022 - 排列

连边 \(i\to p_i\),则 \(f(i,j)=0\) 当且仅当 \(i,j\) 在一个环中;否则 \(f(i,j)\) 为将这两个环合并为一个后所有环长的 \(\operatorname{lcm}\)

设共有 \(n\) 个环,大小为 \(a_1\cdots a_n\)。则答案为:

\(\sum\limits_{i=1}^n\sum\limits_{j\neq i}a_ia_j\operatorname{lcm}\{\operatorname{lcm}_{k\neq i,j}a_k,a_i+a_j\}\)

考虑将相同的 \(a\) 一起处理。将序列 \(a\) 变为二元组 \((b_i,c_i)\),表示 \(a_i\) 中有 \(c_i\)\(b_i\)。由于 \(\sum a=n\),则 \((b,c)\) 的个数至多 \(O(\sqrt n)\)

答案转化为:

\(\sum\limits_{i=1}^n\sum\limits_{j=i+1}^n2c_ic_jb_ib_j\operatorname{lcm}\{\operatorname{lcm}_{k\neq i,j}b_k,b_i+b_j\}+\sum\limits_{i=1}^nc_i(c_i-1)b_i^2\operatorname{lcm}\{\operatorname{lcm}_{k\neq i}a_k,2a_i\}\)

需要支持 \(O(\sqrt n)\) 维护序列任意删两个数、加一个数的 \(\operatorname{lcm}\),分每个质因数维护最大、次大、次次大幂次即可。

点击查看代码
//P8338
#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10;
int T, n, m, per[N], col[N], a[N];
typedef long long ll;
const ll P = 1e9 + 7;
ll ans;
int b[N], c[N], er[N];
pair<int, int> pr[N][3];
vector<int> cg[N];

void upd(pair<int, int> p, int i){
    if(p >= pr[i][0]){
        pr[i][2] = pr[i][1];
        pr[i][1] = pr[i][0];
        pr[i][0] = p;
    } else if(p >= pr[i][1]){
        pr[i][2] = pr[i][1];
        pr[i][1] = p;
    } else {
        pr[i][2] = max(pr[i][2], p);
    }
}

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;
}

void dvv(ll &val, int tim, int vl){
    ll q = qp(vl, P-2);
    while(tim--){
        val = val * q % P;
    }
}

int main(){
    scanf("%d", &T);
    while(T--){
        scanf("%d", &m);
        for(int i = 1; i <= m; ++ i){
            scanf("%d", &per[i]);
        }
        for(int i = 1; i <= m; ++ i){
            if(!col[i]){
                col[i] = ++ n;
                ++ a[n];
                int st = per[i];
                while(st != i){
                    col[st] = n;
                    st = per[st];
                    ++ a[n];
                }
                b[n] = a[n];
            }
        }
        sort(a + 1, a + n + 1);
        sort(b + 1, b + n + 1);
        int tot = unique(b + 1, b + n + 1) - b - 1;
        for(int i = 1, j = 1; i <= tot; ++ i){
            while(j <= n && a[j] == b[i]){
                ++ j;
                ++ c[i];
            }
            int x = b[i];
            for(int y = 2; y * y <= x; ++ y){
                if(x % y == 0){
                    int cy = 0;
                    while(x % y == 0){
                        x /= y;
                        ++ cy;
                    }
                    pair<int, int> p = make_pair(cy, i);
                    upd(p, y);
                    if(y == 2){
                        er[i] = cy;
                    }
                }
            }
            if(x){
                upd(make_pair(1, i), x);
            }
        }
        ll ml = 1;
        for(int i = 2; i <= m; ++ i){
            cg[pr[i][0].second].push_back(i);
            for(int j = 1; j <= pr[i][0].first; ++ j){
                ml = ml * i % P;
            }
        }
        n = tot;
        for(int i = 1; i <= n; ++ i){
            for(int j = i + 1; j <= n; ++ j){
                ll tmp = 2 * c[i] * c[j] % P * b[i] % P * b[j] % P;
                ll mll = ml;
                if(c[i] == 1){
                    for(int p : cg[i]){
                        if(pr[p][1].second == j && c[j] == 1){
                            dvv(mll, pr[p][0].first - pr[p][2].first, p);
                        } else {
                            dvv(mll, pr[p][0].first - pr[p][1].first, p);
                        }
                    }
                }
                if(c[j] == 1){
                    for(int p : cg[j]){
                        if(pr[p][1].second == i && c[i] == 1){
                            dvv(mll, pr[p][0].first - pr[p][2].first, p);
                        } else {
                            dvv(mll, pr[p][0].first - pr[p][1].first, p);
                        }
                    }
                }
                int vv = b[i] + b[j];
                for(int x = 2; x * x <= vv; ++ x){
                    if(vv % x == 0){
                        int y = 0;
                        while(vv % x == 0){
                            vv /= x;
                            ++ y;
                        }
                        int st;
                        if((pr[x][0].second!=i||c[i]-1) && (pr[x][0].second!=j||c[j]-1)){
                            st = pr[x][0].first;
                        } else if((pr[x][1].second!=i||c[i]-1) && (pr[x][1].second!=j||c[j]-1)){
                            st = pr[x][1].first;
                        } else {
                            st = pr[x][2].first;
                        }
                        for(int z = st; z < y; ++ z){
                            mll = mll * x % P;
                        }
                    }
                }
                if(vv != 1){
                    int x = vv;
                    int st;
                    if((pr[x][0].second!=i||c[i]-1) && (pr[x][0].second!=j||c[j]-1)){
                        st = pr[x][0].first;
                    } else if((pr[x][1].second!=i||c[i]-1) && (pr[x][1].second!=j||c[j]-1)){
                        st = pr[x][1].first;
                    } else {
                        st = pr[x][2].first;
                    }
                    for(int z = st; z < 1; ++ z){
                        mll = mll * x % P;
                    }
                }
                ans = (ans + tmp * mll) % P;
            }
        }
        for(int i = 1; i <= n; ++ i){
            ll tmp = c[i] * (c[i]-1) % P * b[i] % P * b[i] % P;
            ll mll = ml;
            if(er[i] == pr[2][0].first){
                mll = mll * 2 % P;
            }
            ans = (ans + tmp * mll % P) % P;
        }
        
        printf("%lld\n", ans);
        for(int i = 0; i <= m; ++ i){
            per[i] = col[i] = a[i] = b[i] = c[i] = er[i] = 0;
            memset(pr[i], 0, sizeof(pr[i]));
            vector<int> ().swap(cg[i]);
        }
        n = m = ans = 0;
    }
    return 0;
}

12. bzoj - 最假女选手

seg beats 区间最值。套路是维护区间最值、区间次最值、区间最值数量。可以证明复杂度为 \(O(q\log^2 n)\)\(O(q\log n)\)

本题线段树每一个节点维护:最大值、次大值、最大值数量、最小值、次小值、最小值数量、区间 max tag、区间 min tag、区间 add tag、区间和。

pushdown 时先 add 再 max/min;pushup add 时更新 max/min tag。

注意 pushup max/min 的时候,对于区间内只有 \(\leq 2\) 种数时,另一个方向的最值、次最值也要相应更新;如果取 min 时 \(v\) 小于区间 \(\max\) 的标记,则所有的数最后都会变成 \(v\),那么把区间 \(\max\) 的标记也变成 \(v\),max 同理。

点击查看代码
//P10639
#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10, inf = 2e9;
typedef long long ll;
int n, m, a[N];

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;

struct node{
	ll sum;  //区间和
	int mx;  //最大值
	int smx; //次大值
	int mxc; //最大值数量
	int mn;  //最小值
	int smn; //次小值
	int mnc; //最小值数量
	int tmx; //取max tag
	int tmn; //取min tag
	int tpl; //加法 tag
} t[N*4];

inline void psu(int p){
	#define ls p<<1
	#define rs p<<1|1
	t[p].sum = t[ls].sum + t[rs].sum;
	if(t[ls].mx == t[rs].mx){
		t[p].mx = t[ls].mx;
		t[p].smx = max(t[ls].smx, t[rs].smx);
		t[p].mxc = t[ls].mxc + t[rs].mxc;
	} else if(t[ls].mx > t[rs].mx){
		t[p].mx = t[ls].mx;
		t[p].smx = max(t[ls].smx, t[rs].mx);
		t[p].mxc = t[ls].mxc;
	} else {
		t[p].mx = t[rs].mx;
		t[p].smx = max(t[rs].smx, t[ls].mx);
		t[p].mxc = t[rs].mxc;
	}
	if(t[ls].mn == t[rs].mn){
		t[p].mn = t[ls].mn;
		t[p].smn = min(t[ls].smn, t[rs].smn);
		t[p].mnc = t[ls].mnc + t[rs].mnc;
	} else if(t[ls].mn < t[rs].mn){
		t[p].mn = t[ls].mn;
		t[p].smn = min(t[ls].smn, t[rs].mn);
		t[p].mnc = t[ls].mnc;
	} else {
		t[p].mn = t[rs].mn;
		t[p].smn = min(t[rs].smn, t[ls].mn);
		t[p].mnc = t[rs].mnc;
	}
}

inline void spp(int p, int l, int r, int v){
	if(v){
		t[p].sum += (ll)v * (r - l + 1);
		t[p].mx += v;
		if(t[p].smx != -inf){
			t[p].smx += v;
		}
		t[p].mn += v;
		if(t[p].smn != inf){
			t[p].smn += v;
		}
		if(t[p].tmx != -inf){
			t[p].tmx += v;
		}
		if(t[p].tmn != inf){
			t[p].tmn += v;
		}
		t[p].tpl += v;
	}
}
inline void spx(int p, int v){
	if(t[p].mn < v){
		t[p].sum += (ll)(v - t[p].mn) * t[p].mnc;
		if(t[p].smx == t[p].mn){
			t[p].smx = v;
		}
		if(t[p].mx == t[p].mn){
			t[p].mx = v;
		}
		t[p].tmn = max(t[p].tmn, v);
		t[p].mn = v;
		t[p].tmx = v;
	}
}
inline void spn(int p, int v){
	if(t[p].mx > v){
		t[p].sum -= (ll)(t[p].mx - v) * t[p].mxc;
		if(t[p].smn == t[p].mx){
			t[p].smn = v;
		}
		if(t[p].mn == t[p].mx){
			t[p].mn = v;
		}
		t[p].tmx = min(t[p].tmx, v);
		t[p].mx = v;
		t[p].tmn = v;
	}
}
inline void psd(int p, int l, int r){
	#define ls p<<1
	#define rs p<<1|1
	int mid = l + r >> 1;
	spp(ls, l, mid, t[p].tpl);
	spp(rs, mid+1, r, t[p].tpl);
	if(t[p].tmx != -inf){
		spx(ls, t[p].tmx);
		spx(rs, t[p].tmx);
	}
	if(t[p].tmn != inf){
		spn(ls, t[p].tmn);
		spn(rs, t[p].tmn);
	}
	t[p].tpl = 0;
	t[p].tmx = -inf;
	t[p].tmn = inf;
}

inline void build(int p, int l, int r){
	t[p].tmx = -inf;
	t[p].tmn = inf;
	if(l == r){
		t[p].mx = t[p].mn = t[p].sum = a[l];
		t[p].smx = -inf;
		t[p].smn = inf;
		t[p].mxc = t[p].mnc = 1;
	} else {
		int mid = l + r >> 1;
		build(p<<1, l, mid);
		build(p<<1|1, mid+1, r);
		psu(p);
	}
}

inline void add(int p, int l, int r, int ql, int qr, int v){
	if(qr < l || r < ql){
		return;
	} else if(ql <= l && r <= qr){
		spp(p, l, r, v);
	} else {
		int mid = l + r >> 1;
		psd(p, l, r);
		add(p<<1, l, mid, ql, qr, v);
		add(p<<1|1, mid+1, r, ql, qr, v);
		psu(p);
	}
}
inline void cmx(int p, int l, int r, int ql, int qr, int v){
	if(qr < l || r < ql || t[p].mn >= v){
		return;
	} else if(ql <= l && r <= qr && t[p].smn > v){
		spx(p, v);
	} else {
		int mid = l + r >> 1;
		psd(p, l, r);
		cmx(p<<1, l, mid, ql, qr, v);
		cmx(p<<1|1, mid+1, r, ql, qr, v);
		psu(p);
	}
}
inline void cmn(int p, int l, int r, int ql, int qr, int v){
	if(qr < l || r < ql || t[p].mx <= v){
		return;
	} else if(ql <= l && r <= qr && t[p].smx < v){
		spn(p, v);
	} else {
		int mid = l + r >> 1;
		psd(p, l, r);
		cmn(p<<1, l, mid, ql, qr, v);
		cmn(p<<1|1, mid+1, r, ql, qr, v);
		psu(p);
	}
}
inline ll qsm(int p, int l, int r, int ql, int qr){
	if(qr < l || r < ql){
		return 0;
	} else if(ql <= l && r <= qr){
		return t[p].sum;
	} else {
		int mid = l + r >> 1;
		psd(p, l, r);
		return qsm(p<<1, l, mid, ql, qr) + 
			   qsm(p<<1|1, mid+1, r, ql, qr);
	}
}
inline int qmx(int p, int l, int r, int ql, int qr){
	if(qr < l || r < ql){
		return -inf;
	} else if(ql <= l && r <= qr){
		return t[p].mx;
	} else {
		int mid = l + r >> 1;
		psd(p, l, r);
		return max(qmx(p<<1, l, mid, ql, qr), 
			       qmx(p<<1|1, mid+1, r, ql, qr));
	}
}
inline int qmn(int p, int l, int r, int ql, int qr){
	if(qr < l || r < ql){
		return inf;
	} else if(ql <= l && r <= qr){
		return t[p].mn;
	} else {
		int mid = l + r >> 1;
		psd(p, l, r);
		return min(qmn(p<<1, l, mid, ql, qr), 
			       qmn(p<<1|1, mid+1, r, ql, qr));
	}
}

int main(){
	read(n);
	for(int i = 1; i <= n; ++ i){
		read(a[i]);
	}
	build(1, 1, n);
	read(m);
	while(m--){
		int op, l, r, x;
		read(op, l, r);
		if(op == 1){
			read(x);
			add(1, 1, n, l, r, x);
		} else if(op == 2){
			read(x);
			cmx(1, 1, n, l, r, x);
		} else if(op == 3){
			read(x);
			cmn(1, 1, n, l, r, x);
		} else if(op == 4){
			println(qsm(1, 1, n, l, r));
		} else if(op == 5){
			println(qmx(1, 1, n, l, r));
		} else if(op == 6){
			println(qmn(1, 1, n, l, r));
		}
	}
	return 0;
}

13. luogu - Scarlet loves WenHuaKe

发现可行方案一定每行 \(2\) 个棋子,每列 \(\leq 2\) 个棋子。答案即为将 \(n\) 类棋子每类 \(2\) 个放到 \(m\) 个大小为 \(2\) 的无序集合中,集合可以不放满的方案数。

不放满不好处理,考虑转化模型,设 \(f(n,m)\) 表示 \(m\) 类棋子每类 \(2\) 个放到 \(n\) 个大小为 \(2\) 的无序集合中,每个集合中两个棋子类别不同的方案数(即答案)。

类别不同/无序集合不好处理,转化,设 \(g(n,m)\) 表示 \(m\) 类棋子每类 \(2\) 个,放到 \(n\) 个大小为 \(2\) 的有序集合中的方案数。

枚举 \(2\) 个棋子都放进去的类别数,易得 \(g(n,m)=(2n)!\sum\limits_{i=0}^m\dfrac{\binom mi\binom {m-i}{2(n-i)}}{2^i}\)

上下界取严格一点得 \(g(n,m)=(2n)!\sum\limits_{i=\max(0,n+n-m )}^n\dfrac{\binom mi\binom {m-i}{2(n-i)}}{2^i}\)

发现可以用 \(f\) 表示 \(g\),枚举集合内两个棋子类别相同的集合数量:\(g(n,m)=\sum\limits_{i=0}^n\dbinom ni\dbinom mii!f(n-i,m-i)2^{n-i}\)

变形得 \(\dfrac{g(n,m)}{m!}=\sum\limits_{i=0}^n\dbinom ni\dfrac{2^{n-i}f(n-i,m-i)}{(m-i)!}\)

\(G(i)=\dfrac{g(i,m-n+i)}{(m-n+i)!},F(i)=\dfrac{2^{i}f(i,m-n+i)}{(m-n+i)!}\)

则有 \(G(i)=\sum\limits_{i=0}^n\dbinom ni F(n-i)\)

二项式定理得 \(F(i)=\sum\limits_{i=0}^n\dbinom ni(-1)^iG(i)\)

\(G(i)\) 计算复杂度是 \(O(m-n)\),所以总复杂度 \(O(n(m-n))\)

点击查看代码
//P4831
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

const int N = 2e5 + 10;
const ll P = 998244353;
const ll i2 = (P+1) / 2;
int n, m;
ll fac[N], inv[N], ip2[N];

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;
}
ll C(int x, int y){
	return fac[x] * inv[y] % P * inv[x-y] % P;
}

void solve(){
	scanf("%d%d", &n, &m);
	fac[0] = ip2[0] = 1;
	for(int i = 1; i <= m+m; ++ i){
		ip2[i] = ip2[i-1] * i2 % P;
		fac[i] = fac[i-1] * i % P;
	}
	inv[m+m] = qp(fac[m+m], P-2);
	for(int i = m+m-1; i >= 0; -- i){
		inv[i] = inv[i+1] * (i+1) % P;
	}
	ll f = 0;
	for(int i = 0; i <= n; ++ i){
		ll g = 0;
		for(int j = max(n+n-m-i, 0); j <= n-i; ++ j){
			g = (g + C(m-i, j) * 
			         C(m-i-j, 2*(n-i-j)) % P * 
					 ip2[j]) % P;
		}
		g = g * fac[2*(n-i)] % P;
		f = (f + ((i&1) ? P-1 : 1) * 
		         C(n, i) % P * 
				 g % P * 
				 inv[m-i]) % P;
	}
	f = f * fac[m] % P * ip2[n] % P;
	printf("%lld\n", f);
}

14. CF1970A2/3 - Balanced Unshuffle

考虑求出排序后三元组的 \(x\)

设排序后 \([l_i,r_i]\) 区间内三元组的 \(x=i\),则有结论:

  1. \([l_i,r_i]\) 中左括号数量 \(=\) \([l_{i+1},r_{i+1}]\) 中右括号数量;
  2. \(i\neq 0\) 时有 \(c_{l_i}\) 为右括号。

结论 1 成立,因为对于原串中一对匹配的括号 \(c_p,c_q\),有 \(x_p+1=x_q\);结论 2 显然成立。

于是从左往右扫一遍 \(c\) 串,扫描时记录当前 \(x\),若扫描到右括号数大于 \(x-1\) 的左括号数,则将现在的这个右括号归到 \(x+1\) 层。有了 \(x\) 数组后则直接模拟即可求出原串。

点击查看代码
//CF1970A3
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

const int N = 5e5 + 10;
int n, dep[N], st[N];
char c[N], s[N];

void solve(){
	scanf("%s", c+1);
	n = strlen(c+1);
	for(int i = 1, nw = 0, lc = 0, nc = 0; i <= n; ++ i){
		if(c[i] == '\('){
			++ nc;
		} else if(lc == 0){
			++ nw;
			lc = nc - 1;
			nc = 0;
		} else {
			-- lc;
		}
		dep[i] = nw;
		st[nw] = i;
	}
	for(int i = 1, nw = 0; i <= n; ++ i){
		s[i] = c[st[nw]];
		if(c[st[nw]] == '\('){
			-- st[nw];
			++ nw;
		} else {
			-- st[nw];
			-- nw;
		}
	}
	printf("%s", s+1);
}

15. 十二省联考2019 - 春节十二响

考虑维护集合 \(S_x\) 表示以 \(x\) 为根的子树中每个段内的 \(\max\),考虑合并两棵子树,发现最优策略一定是最大的和最大的合并、次大的和次大的合并,于是启发式合并一下就做完了。

点击查看代码
//P5290
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

const int N = 2e5 + 10;
int n, a[N], id[N], tot;
vector<int> g[N];
priority_queue<int> st[N];

void dfs(int x){
	for(int i : g[x]){
		dfs(i);
		if(id[x] == 0){
			id[x] = id[i];
			continue;
		}
		int p = id[x], q = id[i];
		if(st[p].size() < st[q].size()){
			swap(p, q);
		}
		id[x] = p;
		vector<int> v;
		while(!st[q].empty()){
			int ip = st[p].top(), iq = st[q].top();
			st[p].pop();
			st[q].pop();
			v.push_back(max(ip, iq));
		}
		for(int i : v){
			st[p].push(i);
		}
	}
	if(!id[x]){
		id[x] = ++ tot;
	}
	st[id[x]].push(a[x]);
}

void solve(){
	scanf("%d", &n);
	for(int i = 1; i <= n; ++ i){
		scanf("%d", &a[i]);
	}
	for(int i = 2; i <= n; ++ i){
		int f;
		scanf("%d", &f);
		g[f].push_back(i);
	}
	dfs(1);
	ll ans = 0;
	while(!st[id[1]].empty()){
		ans += st[id[1]].top();
		st[id[1]].pop();
	}
	printf("%lld\n", ans);
}


16. UNR #7 - 火星式选拔

考虑 \(k=1\) 时可以预处理出 \(f_{i,0}\) 表示 \(i\) 会最先被右侧哪个拿下。\(f_{i,j}\) 表示 \(i\) 被拿下 \(2^j\) 次后是谁。查询时暴力往右跳到 \(\leq r\) 的最大值即可。

\(k\neq 1\) 时考虑 \(b_{[l,r]}\) 的前 \(k-1\) 大,它们一定都在答案中,因为它们并不会被任何人赶出去。现在目标变成了求那最后的第 \(k\) 大。

考虑 \(b_{[l,r]}\)\(k-1\) 大最右侧的下标 \(x\),那么 \(x\) 会拿下 \([l,x-1]\) 中留下的最小值,\([l,x-1]\) 中留下的次小值会和 \([x+1,r]\) 共同竞争最后一个位置。由上述结论,\([l,x-1]\) 中留下的次小值就是 \([l,x-1]\) 中第 \(k-1\) 大值。

所以我们的数据结构要支持查询 \([l,r]\) 中第 \(k\)\(b\)、前 \(k\)\(c\) 权值和、\(\geq x\)\(b\) 最右侧下标,这些都可以使用主席树维护。

还有一个问题:查询 \(x\) 经过 \([l,r]\) 后变成了什么,可以把这种再次离线下来绑在 \(l\) 上,和 \(f\) 数组可以一起求出 \(x\) 第一个被谁拿下之后再倍增跳即可。

点击查看代码
//uoj812
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

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 = 5e5 + 10;
int n, q, a[N], b[N], c[N], fb[N];
int f[N][20], tq;
ll ans[N];

struct qry{
	int x, r, id;
} qr[N], qrr[N];
vector<int> g[N];

struct seg1{
	int t[N*4];
	inline void build(int p, int l, int r){
		t[p] = n+1;
		if(l != r){
			int mid = l + r >> 1;
			build(p<<1, l, mid);
			build(p<<1|1, mid+1, r);
		}
	}
	inline int ask(int p, int l, int r, int ql, int qr){
		if(qr < l || r < ql){
			return n+1;
		} else if(ql <= l && r <= qr){
			return t[p];
		} else {
			int mid = l + r >> 1;
			return min(ask(p<<1, l, mid, ql, qr),
			           ask(p<<1|1, mid+1, r, ql, qr));
		}
	}
	inline void add(int p, int l, int r, int x, int v){
		if(l == r){
			t[p] = v;
		} else {
			int mid = l + r >> 1;
			if(x <= mid){
				add(p<<1, l, mid, x, v);
			} else {
				add(p<<1|1, mid+1, r, x, v);
			}
			t[p] = min(t[p<<1], t[p<<1|1]);
		}
	}
} sg1;

int rt[N], cnt;
struct node{
	int ls, rs, sum, pos;
	ll val;
} t[N*60];
inline int add(int p, int l, int r, int x, int v, int ps){
	++ cnt;
	t[cnt] = t[p];
	p = cnt;
	if(l == r){
		++ t[p].sum;
		t[p].val += v;
		t[p].pos = ps;
	} else {
		int mid = l + r >> 1;
		if(x <= mid){
			t[p].ls = add(t[p].ls, l, mid, x, v, ps);
		} else {
			t[p].rs = add(t[p].rs, mid+1, r, x, v, ps);
		}
		t[p].sum = t[t[p].ls].sum + t[t[p].rs].sum;
		t[p].val = t[t[p].ls].val + t[t[p].rs].val;
		t[p].pos = max(t[t[p].ls].pos, t[t[p].rs].pos);
	}
	return p;
}
inline int qry(int p, int q, int l, int r, int k){
	if(l == r){
		return l;
	} else {
		int mid = l + r >> 1;
		int v = t[t[p].rs].sum - t[t[q].rs].sum;
		if(v >= k){
			return qry(t[p].rs, t[q].rs, mid+1, r, k);
		} else {
			return qry(t[p].ls, t[q].ls, l, mid, k-v);
		}
	}
}
int tmp;
inline ll qvl(int p, int l, int r, int ql, int qr){
	if(qr < l || r < ql){
		return 0;
	} else if(ql <= l && r <= qr){
		tmp = max(tmp, t[p].pos);
		return t[p].val;
	} else {
		int mid = l + r >> 1;
		return qvl(t[p].ls, l, mid, ql, qr) + 
			   qvl(t[p].rs, mid+1, r, ql, qr);
	}
}

void solve(){
	read(n, q);
	for(int i = 1; i <= n; ++ i){
		read(a[i]);
	}
	for(int i = 1; i <= n; ++ i){
		read(b[i]);
		fb[b[i]] = i;
	}
	for(int i = 1; i <= n; ++ i){
		read(c[i]);
	}
	
	rt[0] = 1;
	++ cnt;
	for(int i = 1; i <= n; ++ i){
		rt[i] = add(rt[i-1], 1, n, b[i], c[i], i);
	}
	
	for(int i = 1; i <= q; ++ i){
		int l, r, k;
		read(l, r, k);
		if(k == 1){
			qrr[++tq] = { l, r, i };
		} else {
			int x = qry(rt[r], rt[l-1], 1, n, k-1);
			ll val = - qvl(rt[l-1], 1, n, x, n);
			tmp = 0;
			val += qvl(rt[r], 1, n, x, n);
			int ps = tmp;
			ans[i] += val;
			int y = qry(rt[ps-1], rt[l-1], 1, n, k-1);
			if(ps == r){
				ans[i] += c[fb[y]];
			} else {
				qr[i] = { fb[y], r, i };
				g[ps+1].push_back(i);
			}
		}
	}
	
	sg1.build(1, 1, n+1);
	for(int i = n; i >= 1; -- i){
		f[i][0] = sg1.ask(1, 1, n+1, b[i], n+1);
		sg1.add(1, 1, n+1, a[i], i);
		for(int j : g[i]){
			int y = qr[j].x, r = qr[j].r, id = qr[j].id;
			int p = sg1.ask(1, 1, n+1, b[y], n+1);
			if(p > r){
				ans[id] += c[y];
			} else {
				qrr[++tq] = { p, r, id };
			}
		}
	}
	f[n+1][0] = n+1;
	for(int i = 1; i < 19; ++ i){
		for(int j = 1; j <= n+1; ++ j){
			f[j][i] = f[f[j][i-1]][i-1];
		}
	}
	
	for(int i = 1; i <= tq; ++ i){
		int l = qrr[i].x, r = qrr[i].r;
		for(int i = 18; i >= 0; -- i){
			if(f[l][i] <= r){
				l = f[l][i];
			}
		}
		ans[qrr[i].id] += c[l];
	}
	
	for(int i = 1; i <= q; ++ i){
		println(ans[i]);
	}
}

17. UNR #6 - 面基之路

考虑答案其实等价于所有人走到一个点的最小时间。

分类讨论这个点在点上还是在边上:

  • 在点上,直接求出到每个人最短路最大值即可。
  • 在边上,求出二元组 \((u,v)\) 表示每个人到边两端最短路,按 \(u\) 升序 \(v\) 降序排列后一定是一个前缀走到点 \(u\),一个后缀走到点 \(v\),贡献为 \(mxu+mxv+w\)(注意能贡献当且仅当 \(mxu+w>mxv,mxv+w>mxu\))。
点击查看代码
//uoj747
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

const int N = 1e5 + 10;
int n, m, k, a[25];
vector<pair<int, int> > g[N];
ll dis[25][N], ans = 1e18;
int eu[N*2], ev[N*2], ew[N*2];
pair<ll, ll> tmp[25];
int vis[N];

void solve(){
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= m; ++ i){
		int u, v, w;
		scanf("%d%d%d", &u, &v, &w);
		g[u].push_back(make_pair(v, w));
		g[v].push_back(make_pair(u, w));
		eu[i] = u;
		ev[i] = v;
		ew[i] = w;
	}
	scanf("%d", &k);
	++ k;
	a[1] = 1;
	for(int i = 2; i <= k; ++ i){
		scanf("%d", &a[i]);
	}
	for(int i = 1; i <= k; ++ i){
		priority_queue<pair<ll, int> > q;
		memset(dis[i], 0x3f, sizeof(dis[i]));
		memset(vis, 0, sizeof(vis));
		dis[i][a[i]] = 0;
		q.push(make_pair(0, a[i]));
		while(!q.empty()){
			int x = q.top().second;
			q.pop();
			if(vis[x]){
				continue;
			}
			vis[x] = 1;
			for(auto j : g[x]){
				int y = j.first, z = j.second;
				if(dis[i][y] > dis[i][x] + z){
					dis[i][y] = dis[i][x] + z;
					q.push(make_pair(-dis[i][y], y));
				}
			}
		}
	}
	for(int i = 1; i <= n; ++ i){
		ll tmp = 0;
		for(int j = 1; j <= k; ++ j){
			tmp = max(tmp, dis[j][i]);
		}
		ans = min(ans, tmp * 2);
	}
	for(int i = 1; i <= m; ++ i){
		int u = eu[i], v = ev[i], w = ew[i];
		for(int j = 1; j <= k; ++ j){
			tmp[j] = make_pair(dis[j][u], -dis[j][v]);
		}
		sort(tmp + 1, tmp + k + 1);
		ll mx = 0, my;
		for(int p = k; p > 1; -- p){
			mx = max(mx, -tmp[p].second);
			my = tmp[p-1].first;
			if(mx + w > my && my + w > mx){
				ans = min(ans, mx + my + w);
			}
		}
	}
	printf("%lld\n", ans);
}

18. UNR #6 - 机器人表演

\(0\) 为左括号,\(1\) 为右括号,括号串高度为左括号数量减去右括号数量。

首先考虑对于给定的 \(T\),是否可以由 \(S\) 扩展得到。等价于是否存在一种方式将 \(T\) 划分为两个子序列,一个为 \(S\),一个为合法括号串。

定义 \(a_i\) 表示 \(T_{[1,i]}\) 的高度,\(b_i\) 表示 \(S_{[1,i]}\) 的高度。\(g_{i,j}\) 表示 \(T_{[1,i]},S_{[1,j]}\) 是否可以匹配(因为目标是计数 \(T\),且 \(g_i\) 不单调,所以采用值域为 \(1\) 的定义方式)。

转移有:\(\begin{cases}g_{i,j}\to g_{i+1,j+1}&if&T_{i+1}=S_{j+1}\\g_{i,j}\to g_{i+1,j}&if&a_{i+1} \geq b_j\end{cases}\)

第一个式子是匹配末尾的两个括号,第二个式子是将末尾的括号不与 \(S\) 匹配。\(a_{i+1}-b_j\) 相当于 \(T\) 除掉 \(S\) 的括号串高度,显然要时刻 \(\geq 0\)

那么得到了一个 dp 套 dp 的做法:设 \(f_{i,j,k}\) 表示 \(T_{[1,i]}\),高度为 \(j\)\(g_{i}=k\) 的方案数,复杂度 \(O(2^nn^3)\)(转移复杂度 \(O(n)\),要更新 \(g_{i}\))。

考虑贪心地匹配,设 \(f_{i,j,k}\) 表示 \(T_{[1,i]}\) 高度为 \(j\) 匹配 \(S_{[1,k]}\)

  • \(T_{i+1}=S_{k+1}\),直接匹配;
  • 否则若 \(T_{i+1}=0\)\(a_{i}>b_k\),将 \(T_{i+1}\) 放入括号串中;
  • 否则 \(T_{i+1}=1,a_i=b_k\),此时出现失配,预处理一个 \(\max kk\in [0,k)\) 使得 \(b_{kk}<b_k\),将 \(S_{(b_{kk},b_k]}\) 舍弃放入括号串中。

但是 评论回复 hehezhou:我的评价是:歪打正着。证明一下这个失配转移使得括号串依旧合法即可。

点击查看代码
//uoj748
#include <bits/stdc++.h>
using namespace std; typedef long long ll;
void solve();int main(){ solve(); return 0; }

const int N = 310;
const ll P = 998244353;
int n, t, b[N], pr[N];
char s[N];
int f[2][N*6][N];

void upd(int &x, int y){
	x += y;
	if(x >= P){
		x -= P;
	}
}

void solve(){
	scanf("%d%d%s", &n, &t, s+1);
	pr[0] = -1;
	for(int i = 1; i <= n; ++ i){
		if(s[i] == '0'){
			b[i] = b[i-1] + 1;
		} else {
			b[i] = b[i-1] - 1;
		}
		pr[i] = -1;
		for(int j = i-1; j >= 0; -- j){
			if(b[j] < b[i]){
				pr[i] = j;
				break;
			}
		}
	}
	int m = n + 2 * t + 10;
	f[0][m][0] = 1;
	for(int i = 1; i <= n + 2 * t; ++ i){
		int ii = i&1;
		for(int j = m-i; j <= m+i; ++ j){
			for(int k = 0; k <= n; ++ k){
				int val = f[ii^1][j][k];
				if(k < n && s[k+1] == '0'){
					upd(f[ii][j+1][k+1], val);
				} else {
					upd(f[ii][j+1][k], val);
				}
				if(k < n && s[k+1] == '1'){
					upd(f[ii][j-1][k+1], val);
				} else if(j - m > b[k]){
					upd(f[ii][j-1][k], val);
				} else if(pr[k] != -1){
					upd(f[ii][j-1][pr[k]], val);
				}
				f[ii^1][j][k] = 0;
			}
		}
	}
	printf("%d\n", f[n&1][m+b[n]][n]);
}

posted @ 2024-06-17 13:03  KiharaTouma  阅读(13)  评论(1编辑  收藏  举报