2024.03 别急记录

1. IOI2018 - 狼人 / werewolf [省选/NOI-]

题意简述:多次询问求是否存在一条 \(s\to t\) 的路径 \(a_1,a_2,...,a_k\) 和路径上一个点 \(a_i\) 使 \(a_1,...,a_i\in[L,n]\)\(a_i,...,a_k\in[1,R]\)

首先求出两棵 kruskal 重构树:

  • 第一棵树边权值设为 \(\min(u,v)\),由大到小插入每一条边,树上点权设为子树内点权 \(\min\),则可通过倍增跳树的方式求得每个点经过 \(\geq L\) 的点能到达的点集合(一个子树内所有叶子);
  • 第二棵树边权值设为 \(\max(u,v)\),由小到大插入每一条边,树上点权设为子树内点权 \(\max\)

因为一个子树内所有叶子是 dfn 序上一段连续区间,dfn 序又是一个排列,问题转化为了两个排列 \(a,b\),求 \(a_{[l,r]},b_{[p,q]}\) 是否有交。

\(a^{-1}_i\) 表示 \(i\)\(a\) 中的出现位置,若有交则等价于存在 \(x\in[p,q]\) 使 \(a^{-1}_{b_x}\in{[l,r]}\)。设排列 \(c_i=a^{-1}_{b_i}\),则等价于 \(c_{[p,q]}\) 中存在值域 \(\in [l,r]\) 的数。

考虑使用主席树维护 \(c\)。每次询问查询 \(c_{[1,q]},c_{[1,p-1]}\) 中值域 \(\in[l,r]\) 的数的个数。若二者不等则答案为 \(1\),否则为 \(0\)

点击查看代码
const int N = 4e5 + 10;
int n, m, Q, fa[N], anti[N], c[N];
int ancmin[N][20], lenmin[N], ancmax[N][20], lenmax[N];
int dfnmin[N], dfnmax[N], mnmin[N], mxmin[N], mnmax[N], mxmax[N];
pair<int, int> emin[N], emax[N];
vector<int> gmin[N], gmax[N];

bool cmpmin(pair<int, int> x, pair<int, int> y){
	return min(x.first, x.second) > min(y.first, y.second);
}
bool cmpmax(pair<int, int> x, pair<int, int> y){
	return max(x.first, x.second) < max(y.first, y.second);
}
int gf(int x){
	return x == fa[x] ? x : fa[x] = gf(fa[x]);
}
void dfsmin(int x){
	if(x <= n){
		++ *dfnmin;
		dfnmin[*dfnmin] = x;
		mnmin[x] = mxmin[x] = *dfnmin;
		return;
	}
	mnmin[x] = 1e9;
	for(int i : gmin[x]){
		dfsmin(i);
		mnmin[x] = min(mnmin[i], mnmin[x]);
		mxmin[x] = max(mxmin[i], mxmin[x]);
	}
}
void dfsmax(int x){
	if(x <= n){
		++ *dfnmax;
		dfnmax[*dfnmax] = x;
		mnmax[x] = mxmax[x] = *dfnmax;
		return;
	}
	mnmax[x] = 1e9;
	for(int i : gmax[x]){
		dfsmax(i);
		mnmax[x] = min(mnmax[i], mnmax[x]);
		mxmax[x] = max(mxmax[i], mxmax[x]);
	}
}
int jmpmin(int x, int val){
	for(int i = 19; i >= 0; -- i){
		if(lenmin[ancmin[x][i]] >= val){
			x = ancmin[x][i];
		}
	}
	return x;
}
int jmpmax(int x, int val){
	for(int i = 19; i >= 0; -- i){
		if(lenmax[ancmax[x][i]] <= val){
			x = ancmax[x][i];
		}
	}
	return x;
}

int rt[N], t[N*40], cnt, ls[N*40], rs[N*40];
int add(int p, int l, int r, int x, int v){
	++ cnt;
	t[cnt] = t[p];
	ls[cnt] = ls[p];
	rs[cnt] = rs[p];
	p = cnt;
	if(l == r){
		t[p] += v;
	} else {
		int mid = l + r >> 1;
		if(x <= mid){
			ls[p] = add(ls[p], l, mid, x, v);
		} else {
			rs[p] = add(rs[p], mid+1, r, x, v);
		}
		t[p] = t[ls[p]] + t[rs[p]];
	}
	return p;
}
int 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];
	} else {
		int mid = l + r >> 1;
		return qry(ls[p], l, mid, ql, qr) 
			   + qry(rs[p], mid+1, r, ql, qr);
	}
}

vector<int> check_validity(
			int N, 
			vector<int> X, 
			vector<int> Y, 
			vector<int> S, 
			vector<int> E, 
			vector<int> L,
        	vector<int> R) {
	vector<int> ans;
	n = N;
	m = X.size();
	Q = S.size();
	for(int i = 1; i <= m; ++ i){
		emin[i].first = X[i-1];
		emin[i].second = Y[i-1];
		++ emin[i].first;
		++ emin[i].second;
		emax[i] = emin[i];
	}
	sort(emin + 1, emin + m + 1, cmpmin);
	iota(fa + 1, fa + n + n + 1, 1);
	iota(lenmin + 1, lenmin + n + 1, 1);
	int cnt = n;
	for(int i = 1; i <= m; ++ i){
		int x = gf(emin[i].first), y = gf(emin[i].second);
		if(x != y){
			fa[x] = fa[y] = ++ cnt;
			gmin[cnt].push_back(x);
			gmin[cnt].push_back(y);
			ancmin[x][0] = ancmin[y][0] = cnt;
			lenmin[cnt] = min(lenmin[x], lenmin[y]);
		}
	}
	dfsmin(cnt);
	for(int i = cnt; i >= 1; -- i){
		for(int j = 1; j < 20; ++ j){
			ancmin[i][j] = ancmin[ancmin[i][j-1]][j-1];
		}
	}
	sort(emax + 1, emax + m + 1, cmpmax); 
	iota(fa + 1, fa + n + n + 1, 1);
	memset(lenmax, 0x3f, sizeof(lenmax));
	iota(lenmax + 1, lenmax + n + 1, 1);
	cnt = n;
	for(int i = 1; i <= m; ++ i){
		int x = gf(emax[i].first), y = gf(emax[i].second);
		if(x != y){
			fa[x] = fa[y] = ++ cnt;
			gmax[cnt].push_back(x);
			gmax[cnt].push_back(y);
			ancmax[x][0] = ancmax[y][0] = cnt;
			lenmax[cnt] = max(lenmax[x], lenmax[y]);
		}
	}
	dfsmax(cnt);
	for(int i = cnt; i >= 1; -- i){
		for(int j = 1; j < 20; ++ j){
			ancmax[i][j] = ancmax[ancmax[i][j-1]][j-1];
		}
	}
	for(int i = 1; i <= n; ++ i){
		anti[dfnmin[i]] = i;
	}
	for(int i = 1; i <= n; ++ i){
		c[i] = anti[dfnmax[i]];
		rt[i] = add(rt[i-1], 1, n, c[i], 1);
	}
	for(int i = 0; i < Q; ++ i){
		int st = S[i], ed = E[i], le = L[i], ri = R[i];
		++ st;
		++ ed;
		++ le;
		++ ri;
		int x = jmpmin(st, le), y = jmpmax(ed, ri);
		int l = mnmin[x], r = mxmin[x], p = mnmax[y], q = mxmax[y];
		int v = qry(rt[q], 1, n, l, r) - qry(rt[p-1], 1, n, l, r);
		if(v){
			ans.push_back(1);
		} else {
			ans.push_back(0);
		}
	}
	return ans;
}

2. **AGC020C - Median Sum [*2259]

子集和中位数 \(O(nV)\) 做法!

\(mid=\dfrac{\sum a_i}2,\operatorname{sum}(S)=\sum_{i\in S}a_i\),答案显然是 \(\min_S\{\operatorname{sum}(S)|\operatorname{sum}(S)\geq mid\}\)。显然有一个 \(O(\dfrac{n^2V}\omega)\)bitset 做法,不多讲。

由于所有的子集 \(S\) 数量过于多,考虑提取出其中的一部分:

若子集 \(S\) 使得不存在 \(i\) 使 \(\operatorname{sum}(S)\)\(\operatorname{sum}(S\setminus\{i\})\) 同时 \(> mid\) 或同时 \(\leq mid\),称这样的 \(S\)平衡解

\(b=\max_i\{i|\operatorname{sum}(\{1,2,...,i\})\leq mid\}\),则易得 \(S=\{1,2,...,b\}\) 为一个平衡解,设其为初始平衡解。设答案为 \(T\),则易得 \(T\) 也为一个平衡解。

我们发现可以使用以下两种操作使得 \(S\) 变为任意一个平衡解,可以发现每次操作后 \(S\) 仍然为平衡解:

  • \(\operatorname{sum}(S)\leq mid\),选择一个 \(i\notin S\)\(S\) 变为 \(S\cup\{i\}\)
  • \(\operatorname{sum}(S)> mid\),选择一个 \(i\in S\)\(S\) 变为 \(S\setminus\{i\}\)

\(ok(s,t,w)=1\) 当且仅当存在一个平衡解 \(S\) 使得 \(\operatorname{sum}(S)=w\)\(\forall 1\leq i< s,i\in S,\forall t< i \leq n,i\neq S\),否则 \(ok(s,t,w)=0\)

可以发现对于 \(s\leq s',t'\leq t\)\(ok(s',t',w)\leq ok(s,t,w)\)。所以我们设 \(f(t,w)\) 表示 \(max_s\{s|ok(s,t,w)\}\),若不存在这样的 \(s\)\(f(t,w)=0\)。那么存在平衡解 \(S\) 使得 \(\operatorname{sum}(S)=w\) 当且仅当 \(f(n,w)\neq 0\)。最后计算答案的时候枚举 \(w\) 查看 \(f(n,w)\) 的值即可。

发现 \(w\) 这一维实际上有用的只有 \(O(V)\) 个位置。因为对于平衡解 \(S\)\(\operatorname{sum}(S)\in (mid-V,mid+V]\)。所以 \(f(t,w)\) 状态总数是 \(O(nV)\) 的。

考虑 dp:

初始化 \(f(b,\operatorname{sum}(\{1,2,...,b\}))=b+1\)。原因显然。

从小到大枚举 \(i\in[b+1,n]\),转移:

  • \(\forall w\in(mid-V,mid+V], f(i,w)\leftarrow f(i-1,w)\),表示不变。
  • \(\forall w\in(mid-V,mid], f(i,w+a_i)\leftarrow f(i-1,w)\),表示插入一个 \(i\)
  • \(\forall w\in(mid,mid+V],j<f(i,w), f(i,w-a_j)\leftarrow j\),表示删除一个 \(j\)

考虑第三种转移的复杂度:

发现当 \(j<f(i-1,w)\) 时,\(j<f(i-1,w)\leq f(i-1,w-a_j)\),所以 \(f(i,w-a_j)\) 在第一个转移中使用 \(f(i-1,w-a_j)\) 转移过来更优。所以 \(j\in[f(i-1,w),f(i,w))\)。对于一个 \(w\),所有 \(i\) 能对应到的 \(j\) 之和为 \(O(n)\) 的,所以总共的转移次数是 \(O(nV)\)。前两个转移的总转移次数显然是 \(O(nV)\)。所以总共的复杂度是 \(O(nV)\)

一个细节:第三个转移对于 \(w\) 的枚举应该从大到小,原因显然。

点击查看代码
const int N = 2e3 + 10;
int n, x, a[N];
int f[N*2], g[N*2];

void solve(){
	read(n);
	int sum = 0, all = 0;
	for(int i = 1; i <= n; ++ i){
		read(a[i]);
		sum += a[i];
		all += a[i];
		x = max(x, a[i]);
	}
	sum /= 2;
	int b = 0, w = 0;
	while(w + a[b+1] <= sum){
		++ b;
		w += a[b];
	}
	f[w-sum+x] = b + 1;
	for(int i = b + 1; i <= n; ++ i){
		memcpy(g, f, sizeof(g));
		for(int j = x; j; -- j){
			f[j+a[i]] = max(f[j+a[i]], g[j]);
		}
		for(int j = x + x; j >= x + 1; -- j){
			for(int k = f[j]-1; k >= max(1, g[j]); -- k){
				f[j-a[k]] = max(f[j-a[k]], k);
			} 
		}
	}
	int ans = 0;
	for(int j = x; j; -- j){
		if(f[j]){
			ans = all - sum - j + x;
			break;
		}
	}
	println(ans);
}

3. 湖北省选模拟 2024 - 花神诞日 / sabzeruz [省选/NOI-]

显然有 \(a\leq b\leq c, a\operatorname{xor} c \geq \min\{a\operatorname{xor} b, b\operatorname{xor} c\}\)。所以我们只用排序后从左往右考虑每个数,记录一下每道菜最后一个数是什么即可。

考虑设 \(f_{i,j}\) 表示前 \(i\) 个数,第 \(i\) 个数被第一道菜选,第二道菜最后选的是第 \(j\) 个数的方案数;\(g_{i,j}\) 表示前 \(i\) 个数,第 \(i\) 个数被第二道菜选,第一道菜最后选的是第 \(j\) 个数的方案数。

有四种转移:

  • \([a_{i-1}\operatorname{xor}a_{i}\geq k_1]f_{i-1,j}\to f_{i,j}\)
  • \([a_{i-1}\operatorname{xor}a_{i}\geq k_2]g_{i-1,j}\to g_{i,j}\)
  • \([a_{j}\operatorname{xor}a_{i}\geq k_2]f_{i-1,j}\to g_{i,i-1}\)
  • \([a_{j}\operatorname{xor}a_{i}\geq k_1]g_{i-1,j}\to f_{i,i-1}\)

那么我们使用 trie 树维护 \((x,y)=(a_j,f_{i,j})\) 的二元组,第三种操作实际上相当于对于 \(x\operatorname{xor}a_i\geq k_2\)\(x\) 求和 \(y\);第一种操作实际上相当于什么都不干或者全部置 \(0\),可以在 trie 每个节点上维护一个 lazytag。第二、第四个操作同理。

最后,因为题目要求两个集合均非空。那么要减去所有数都归到一个集合的方案。

点击查看代码
const int N = 2e5 + 10;
const ll P = 1e9 + 7;
ll a[N], k1, k2;
int n, cnt = 2;
struct node{
	int ch[2], tag;
	ll sum;
} t[N*130];

void psd(int p){
	if(t[p].tag){
		if(t[p].ch[0]){
			t[t[p].ch[0]].sum = 0;
			t[t[p].ch[0]].tag = 1;
		}
		if(t[p].ch[1]){
			t[t[p].ch[1]].sum = 0;
			t[t[p].ch[1]].tag = 1;
		}
		t[p].tag = 0;
	}
}
void ins(int p, ll v, ll x){
	for(ll i = 62; i >= 0; -- i){
		psd(p);
		t[p].sum = (t[p].sum + x) % P;
		if(!t[p].ch[(v>>i)&1]){
			t[p].ch[(v>>i)&1] = ++ cnt;
		}
		p = t[p].ch[(v>>i)&1];
	}
	t[p].sum = (t[p].sum + x) % P;
}
ll qry(int p, ll v, ll w){
	ll ans = 0;
	for(ll i = 62; i >= 0; -- i){
		psd(p);
		if((w >> i) & 1){
			if(t[p].ch[!((v>>i)&1)]){
				p = t[p].ch[!((v>>i)&1)];
			} else {
				return ans;
			}
		} else {
			ans += t[t[p].ch[!((v>>i)&1)]].sum;
			if(t[p].ch[(v>>i)&1]){
				p = t[p].ch[(v>>i)&1];
			} else {
				return ans;
			}
		}
	}
	ans = (ans + t[p].sum) % P;
	return ans;
}
void fil(int p){
	t[p].sum = 0;
	t[p].tag = 1;
}

void solve(){
	read(n, k1, k2);
	for(int i = 1; i <= n; ++ i){
		read(a[i]);
	}
	sort(a + 1, a + n + 1);
	ins(1, (1ll<<61), 1);
	ins(2, (1ll<<61), 1);
	for(int i = 2; i <= n; ++ i){
		ll sg = qry(1, a[i], k2);
		ll sf = qry(2, a[i], k1);
		if((a[i-1] ^ a[i]) < k1){
			fil(1);
		}
		if((a[i-1] ^ a[i]) < k2){
			fil(2);
		}
		ins(1, a[i-1], sf);
		ins(2, a[i-1], sg);
	}
	int fa = 1, fb = 1;
	for(int i = 2; i <= n; ++ i){
		if((a[i-1] ^ a[i]) < k1){
			fa = 0;
		}
		if((a[i-1] ^ a[i]) < k2){
			fb = 0;
		}
	}
	println((t[1].sum + t[2].sum - fa - fb + P) % P);
}

4. APIO2014 - 回文串 [省选/NOI-]

建出回文树,对每次增量添加一个字符后到达的点 \(cur\),令 \(sum_{cur}\)\(1\)。最后每个点代表的串的出现次数即为失配子树内 \(sum\) 和,拓扑序逆序(直接编号逆序即可)往根贡献即可。

点击查看代码
const int N = 3e5 + 10;
int n, tot = 1, len[N], sum[N], ch[N][26], fl[N], cur;
char s[N];

int gfl(int x, int i){
	while(i - len[x] - 1 < 0 || s[i-len[x]-1] != s[i]){
		x = fl[x];
	}
	return x;
}

void solve(){
	scanf("%s", s + 1);
	n = strlen(s + 1);
	fl[0] = 1;
	len[1] = -1;
	for(int i = 1; i <= n; ++ i){
		int pos = gfl(cur, i);
		if(!ch[pos][s[i]-'a']){
			fl[++tot] = ch[gfl(fl[pos], i)][s[i]-'a'];
			ch[pos][s[i]-'a'] = tot;
			len[tot] = len[pos] + 2;
		}
		cur = ch[pos][s[i]-'a'];
		++ sum[cur];
	}
	for(int i = tot; i >= 0; -- i){
		sum[fl[i]] += sum[i];
	}
	ll ans = 0;
	for(int i = 0; i <= tot; ++ i){
		ans = max(ans, (ll)len[i] * sum[i]);
	}
	printf("%lld\n", ans);
	return;
}

5. APIO2023 - 赛博乐园 / cyberland [提高+/省选-]

考虑一层一层地跑。预处理出 \(1\) 不经过 \(H\) 能到达的点集,每次从这些点开始跑一遍 dij。然后称一次操作为:

  • 对于任意一条边 \((u,v)\),若 \(arr_u=2\),则使用 \(dis_u/2+w(u,v)\) 更新 \(dis_v\)
  • 从点集中点开始跑一遍 dij。

注意 \(inf\) 的取值。

点击查看代码
vector<pair<int, int>> g[N];
int a[N], ok[N], n, m, k, ed, vis[N];
double dis[N], nd[N];

void dfs(int x) {
    ok[x] = 1;

    for (auto i : g[x]) {
        if (!ok[i.first] && i.first != ed) {
            dfs(i.first);
        }
    }
}
void dij() {
    priority_queue<pair<double, int>> q;

    for (int i = 1; i <= n; ++ i) {
        vis[i] = 0;

        if (ok[i]) {
            q.push(make_pair(-dis[i], i));
        }
    }

    while (!q.empty()) {
        int x = q.top().second;
        q.pop();

        if (vis[x]) {
            continue;
        }

        vis[x] = 1;

        for (auto i : g[x]) {
            int y = i.first, z = i.second;

            if (dis[y] > dis[x] + z) {
                dis[y] = dis[x] + z;
                q.push(make_pair(-dis[y], y));
            }
        }
    }
}
void cpy() {
    for (int i = 1; i <= n; ++ i) {
        nd[i] = dis[i];
    }

    for (int i = 1; i <= n; ++ i) {
        if (a[i] != 2 || dis[i] > 1e23) {
            continue;
        }

        for (auto j : g[i]) {
            int y = j.first, z = j.second;
            dis[y] = min(dis[y], nd[i] / 2.0 + z);
        }
    }
}

double solve(
    int nn,
    int mm,
    int kk,
    int hh,
    vector<int> x,
    vector<int> y,
    vector<int> c,
    vector<int> arr) {
    n = nn;
    m = mm;
    k = kk;
    ed = hh + 1;

    for (int i = 1; i <= n; ++ i) {
        vector<pair<int, int>> ().swap(g[i]);
        a[i] = arr[i - 1];
        ok[i] = vis[i] = 0;
        dis[i] = nd[i] = 1e24;
    }

    for (int i = 1; i <= m; ++ i) {
        int X = x[i - 1] + 1, Y = y[i - 1] + 1, Z = c[i - 1];

        if (X != ed)
            g[X].emplace_back(Y, Z);

        if (Y != ed)
            g[Y].emplace_back(X, Z);
    }

    dfs(1);

    for (int i = 1; i <= n; ++ i) {
        if (ok[i] && (i == 1 || a[i] == 0)) {
            dis[i] = 0;
        }
    }

    k = min(k, 70);
    dij();

    while (k--) {
        cpy();
        dij();
    }

    double ans = dis[ed] > 1e23 ? (double) -1 : dis[ed];

    for (int i = 1; i <= n; ++ i) {
        vector<pair<int, int>> ().swap(g[i]);
        a[i] = arr[i - 1];
        ok[i] = vis[i] = 0;
        dis[i] = nd[i] = 1e18;
    }

    return ans;
}

6. 省选联考 2024 - 魔法手杖 [NOI/NOI+/CTSC]

真不难吧这个题?为什么不会?

特判一下 \(\sum b_i\leq m\) 的情况,此时易得答案为 \((\min a_i)+2^k-1\)。否则至少有一个选择异或,此时答案一定 \(<2^k\)

把所有 \((a,b)\) 二元组插入一棵 trie 中。设最优答案为 \(now\) 以及对应的 \(x\),考虑按位确定 \(now,x\)。对于遍历到的二叉树上节点 \(p\),在更浅的分叉中钦定选择加法的最小值 \(sum\),分四种情况:

  • \(now,x\) 这一位均为 \(1\)。则需要满足 \(p.ch_1\) 中的所有点都选择加法,check 魔力值是否足够,更新 \(sum\),递归到 \(p.ch_0\) 中。
  • \(now\) 这一位为 \(1\)\(x\) 这一位为 \(0\)。与上一种类似。
  • \(now\) 这一位为 \(0\)\(x\) 这一位为 \(1\)。此时 \(p.ch_0\) 中的点已经完全优于答案,不用管,递归到另一侧。
  • \(now,x\) 这一位均为 \(0\)。与上一种类似。

所以我们在递归求解的过程中需要记录 \(p,dep,lst,x,sum,now\),分别表示节点编号、深度、还可用的魔力值大小、前面若干位最优的 \(x\)、已经钦定选择加法中最小的 \(a\)、前面若干位最优的答案。记录这些后就可以一遍深搜解决了。每个点最多遍历两次,所以复杂度 \(O(Tnk)\)

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

void read(__int128 &x){
    // read a __int128 variable
    char c; bool f = 0;
    while (((c = getchar()) < '0' || c > '9') && c != '-');
    if (c == '-') { f = 1; c = getchar(); }
    x = c - '0';
    while ((c = getchar()) >= '0' && c <= '9') x = x * 10 + c - '0';
    if (f) x = -x;
}
void write(__int128 x){
    // print a __int128 variable
    if (x < 0) { putchar('-'); x = -x; }
    if (x > 9) write(x / 10);
    putchar(x % 10 + '0');
}

const int N = 1e5 + 10, K = 125;
int T, c, n, m, k, cnt;
__int128 a[N], ans;
ll b[N];

struct node{
	int ch[2];
	__int128 amin;
	ll bsum;
} t[N*K];

__int128 zy(int x){
	return (__int128) 1 << x;
}
__int128 fl(int x){
	return zy(x) - 1;
}
void solve(int p, int dep, int lst, __int128 x, __int128 sum, __int128 now){
	if(dep == -1){
		ans = max(ans, now);
	} else if(!p){
		ans = max(ans, sum + (x | fl(dep+1)));
	} else {
		int lc = t[p].ch[0], rc = t[p].ch[1];
		bool flg = 0;
		if(t[lc].bsum <= lst && (x|fl(dep)) + min(sum,t[lc].amin) >= (now|zy(dep))){
			solve(rc, dep-1, lst-t[lc].bsum, x, min(sum,t[lc].amin), now|zy(dep));
			flg = 1;
		}
		if(t[rc].bsum <= lst && (x|fl(dep+1)) + min(sum,t[rc].amin) >= (now|zy(dep))){
			solve(lc, dep-1, lst-t[rc].bsum, x|zy(dep), min(sum,t[rc].amin), now|zy(dep));
			flg = 1;
		}
		if(!flg){
			solve(lc, dep-1, lst, x, sum, now);
			solve(rc, dep-1, lst, x|zy(dep), sum, now);
		}
	}
}

void solve(){
	for(int i = 0; i < N*K; ++ i){
		t[i].amin = zy(121);
	}
	scanf("%d%d", &c, &T);
	while(T--){
		scanf("%d%d%d", &n, &m, &k);
		for(int i = 1; i <= n; ++ i){
			read(a[i]);
		}
		for(int i = 1; i <= n; ++ i){
			scanf("%lld", &b[i]);
		}
		cnt = 1;
		for(int i = 1; i <= n; ++ i){
			int p = 1;
			t[p].bsum += b[i];
			t[p].amin = min(t[p].amin, a[i]);
			for(int j = k - 1; j >= 0; -- j){
				int v = a[i] >> j & 1;
				if(!t[p].ch[v]){
					t[p].ch[v] = ++ cnt;
				}
				p = t[p].ch[v];
				t[p].amin = min(t[p].amin, a[i]);
				t[p].bsum += b[i];
			}
		}
		if(t[1].bsum <= m){
			ans = t[1].amin + zy(k) - 1;
		} else {
			solve(1, k-1, m, 0, zy(k), 0);
		}
		write(ans);
		putchar('\n');
		for(int i = 0; i <= cnt; ++ i){
			t[i].ch[0] = t[i].ch[1] = t[i].bsum = 0;
			t[i].amin = zy(121);
		}
		ans = 0;
	}
}

7. 湖北省选模拟 2024 - 沉玉谷 / jade [省选/NOI-]

\(f_{i,j,k}\) 表示 \([i,j]\)\(k\) 次删空方案数,\(g_{i,j,k,l}\) 表示 \([i,j]\)\(k\) 次仅剩颜色 \(l\) 的方案数。

转移:

\(g_{l,r,x+y,a_k}=\sum f_{l,k-1,x}(f_{k+1,r,y}+g_{k+1,r,y,a_k})\dbinom{x+y}x\),即枚举最左边一个未删除的位置 \(k\),两侧分别的操作数 \(x,y\)

\(f_{l,r,k}=\sum g_{l,r,k-1,c}\),即枚举未删除的颜色 \(c\)

最后答案为 \(\sum f_{1,n,i}\)

初始化应设 \(f_{i+1,i,0}=1 (i\in[0,n])\)

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

const int N = 55;
const ll P = 1e9 + 7;
int n, a[N];
ll C[N][N], f[N][N][N], g[N][N][N][N];
void upd(ll &x, ll y){
	x = (x + y) % P;
}

void solve(){
	scanf("%d", &n);
	C[0][0] = 1;
	for(int i = 1; i <= n; ++ i){
		scanf("%d", &a[i]);
		C[i][0] = C[i][i] = 1;
		for(int j = 1; j < i; ++ j){
			C[i][j] = (C[i-1][j] + C[i-1][j-1]) % P;
		}
		f[i][i-1][0] = 1;
	}
	f[n+1][n][0] = 1;
	for(int len = 1; len <= n; ++ len){
		for(int l = 1; l + len - 1 <= n; ++ l){
			int r = l + len - 1;
			for(int k = l; k <= r; ++ k){
				for(int x = 0; x <= k - l; ++ x){
					for(int y = 0; y <= r - k; ++ y){
						upd(g[l][r][x+y][a[k]],
								f[l][k-1][x] * 
								(f[k+1][r][y] + g[k+1][r][y][a[k]]) % P *
								C[x+y][x] % P);
					}
				}
			}
			for(int k = 1; k <= len; ++ k){
				for(int c = 1; c <= n; ++ c){
					upd(f[l][r][k], g[l][r][k-1][c]);
				}
			}
		}
	}
	ll ans = 0;
	for(int i = 1; i <= n; ++ i){
		upd(ans, f[1][n][i]);
	}
	printf("%lld\n", ans);
}

8. ABC342F - Black Jack [*2141]

考虑设 \(g_i\) 表示对方以 \(i\) 结尾的概率,\(f_i\) 表示自己目前投出总和 \(i\),按照最优方案最大获胜概率。

发现 \(g\) 很好求:\(\dfrac{g_i}d\to g_{i+j}[i<l][1\leq j\leq d]\)。稍微变形得 \(g_i=\sum g_j[j<l][j\geq i-d]\),可以前缀和优化。

\(pr_i=\sum g_j[l\leq j\leq i],ls=\sum g_j[j>n]\),那么对于 \(i\in[l,n]\),自己在 \(i\) 处停止投的获胜概率是 \(pr_{i-1}+ls\)

那么我们就有 \(f\) 的递推式:\(f_i=\max\{\dfrac{\sum f_j[i+1\leq j\leq i+d]}d,pr_{i-1}+ls\}\),表示继续投还是停止投,也可以前缀和优化。

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

const int N = 4e5 + 10;
int n, l, d;
double f[N], g[N], sum[N], ls, pr[N];

void solve(){
	scanf("%d%d%d", &n, &l, &d);
	int m = n + l;
	g[0] = sum[0] = 1;
	for(int i = 1; i <= m; ++ i){
		#define clc(x, y) (sum[y] - (x-1>=0 ? sum[x-1] : 0))
		g[i] = clc(i-d, i-1) * 1.0 / d;
		if(i < l){
			sum[i] = sum[i-1] + g[i];
			g[i] = 0;
		} else {
			sum[i] = sum[i-1];
			pr[i] = pr[i-1] + g[i];
		}
	}
	for(int i = 1; i <= m; ++ i){
		if(i > n){
			ls += g[i];
		}
		sum[i] = 0;
	}
	sum[0] = 0;
	for(int i = n; i >= 0; -- i){
		f[i] = max((sum[i+1] - sum[i+d+1]) / d, pr[i-1] + ls);
		sum[i] = sum[i+1] + f[i];
	}
	printf("%.20lf\n", f[0]);
}

9. ABC342G - Retroactive Range Chmax [*2107]

做法解析:树套树。

对于线段树每个节点使用 std::multiset 维护可重集。

  • 对于操作 1,将 \([l,r]\) 对应的若干个可重集中插入元素 \(x\)
  • 对于操作 2,将 \([q_i.l,q_i.r]\) 对应的若干个可重集中删除元素 \(q_i.x\)
  • 对于操作 3,输出 \(i\) 的祖先可重集中最大元素的最大值和 \(a_i\) 中的较大值。
点击查看代码
//AT_abc342_g
#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], q, X[N], Y[N], Z[N];
multiset<int> t[N*4];

void add(int p, int l, int r, int ql, int qr, int x){
	if(qr < l || r < ql){
		return;
	} else if(ql <= l && r <= qr){
		t[p].insert(-x);
	} else {
		int mid = l + r >> 1;
		add(p<<1, l, mid, ql, qr, x);
		add(p<<1|1, mid+1, r, ql, qr, x);
	}
}
void del(int p, int l, int r, int ql, int qr, int x){
	if(qr < l || r < ql){
		return;
	} else if(ql <= l && r <= qr){
		auto it = t[p].lower_bound(-x);
		t[p].erase(it);
	} else {
		int mid = l + r >> 1;
		del(p<<1, l, mid, ql, qr, x);
		del(p<<1|1, mid+1, r, ql, qr, x);
	}
}

int ans;
void qry(int p, int l, int r, int x){
	ans = max(ans, t[p].empty() ? 0 : -*t[p].begin());
	if(l != r){
		int mid = l + r >> 1;
		if(x <= mid){
			qry(p<<1, l, mid, x);
		} else {
			qry(p<<1|1, mid+1, r, x);
		}
	}
}

void solve(){
	scanf("%d", &n);
	for(int i = 1; i <= n; ++ i){
		scanf("%d", &a[i]);
	}
	scanf("%d", &q);
	for(int i = 1; i <= q; ++ i){
		int op, l, r, x;
		scanf("%d%d", &op, &l);
		if(op == 1){
			scanf("%d%d", &r, &x);
			tie(X[i], Y[i], Z[i]) = tie(l, r, x);
			add(1, 1, n, l, r, x);
		} else if(op == 2){
			del(1, 1, n, X[l], Y[l], Z[l]);
		} else {
			ans = a[l];
			qry(1, 1, n, l);
			printf("%d\n", ans);
		}
	}
}

10. CCO2023 - Real Mountains [省选/NOI-]

\(b_i=\min(\max_{1\leq j\leq i}\{a_j\},\max_{i\leq j\leq n}\{a_j\})\),显然有 \(b\) 是一组最小的可达的满足要求的方案。可以证明 \(b\) 是唯一的一组方案。所以我们的目标就变为求得最小代价使得 \(h\) 变为 \(b\)

容易猜测一个结论:每次选择 \(h_i\) 最小且需要增加的位置,然后进行一次操作对其 \(+1\)。显然这个结论是正确的,证明可以考虑若存在 \(h_j>h_i\)\(j\) 的操作在 \(i\) 前,两个交换一定不劣。那么如果有若干个 \(h_i\) 并列最小值怎么办?

假设这个并列最小值为 \(x\),有 \(k\) 个,我们要将这些全部变为 \(x+1\)。设最左侧的 \(x\)\(h_l\),最右侧的 \(x\)\(h_r\)

最优操作方案是:对于左端点,我们找到二元组 \((o,p)\),满足 \(h_o>h_l<h_p,o<l<p\),且不存在 \(h_l<h_{o'}<h_o,o'<l\),也不存在 \(h_l<h_{p'}<h_p,p'>l\)。对于右端点,同理找到一个二元组 \((q,r)\)

那么可以先进行一次 \((o,l,p)\),一次 \((l,r,q)\)\((k-2)\)\((l,i,r)\) 完成目标。总代价为 \(h_o+h_r+h_p+2k-3+3(k-1)x\)。若 \(h_p>h_q\),还可以先操作右端点再操作左端点。故最优的总代价为 \(h_o+\min(h_p,h_q)+h_r+2k-3+3(k-1)x\)

所以我们得到了一个暴力做法:遍历整个数组,提取出若干个 还需增加的并列最小值,找到两个二元组进行更新。复杂度爆炸。

考虑离散化。若出现在 \(h\) 中且大于最小值 \(x\) 的数为 \(y\),则一定会使用相同的两个二元组更新相同数量、位置的 \(x\) 一路到 \(y\)。所以我们将三元组 \((h_i,i,-1),(b_i,i,1)\) 进行排序然后扫描线,可以将复杂度优化到 \(O(n^2)\)

现在问题转化为了一个序列 \(h\),两种操作:

  • 查询前缀/后缀中大于某值的最小值;
  • 一个抽象的修改操作。

发现很难处理修改。然而仔细分析可以发现修改是不需要的。因为根据上述算法,不可能存在两个位置 \((i,j)\),最开始 \(h_i<h_j\),在 中途的某个过程时既有 \(h_j<b_j\) 又有 \(h_i>h_j\)。所以只用维护查询操作,使用主席树即可。

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

const int N = 1e6 + 10;
const ll P = 1e6 + 3;
int n;
ll a[N], b[N];
tuple<ll, int, int> q[N*2];
ll ans;

ll Sum(ll x, ll y){
	return (y * (y-1) / 2 % P - x * (x-1) / 2 % P + P) % P;
}

int t[N*100], ls[N*100], rs[N*100];
int rtl[N], rtr[N], cnt;
int add(int p, int l, int r, int x){
	++ cnt;
	tie(t[cnt], ls[cnt], rs[cnt]) = tie(t[p], ls[p], rs[p]);
	p = cnt;
	if(l == r){
		++ t[p];
	} else {
		int mid = l + r >> 1;
		if(x <= mid){
			ls[p] = add(ls[p], l, mid, x);
		} else {
			rs[p] = add(rs[p], mid+1, r, x);
		}
		t[p] = t[ls[p]] + t[rs[p]];
	}
	return p;
}
int qry(int p, int l, int r, int ge){
	if(t[p] == 0 || r <= ge){
		return -1;
	} else if(l == r){
		return l;
	} else {
		int mid = l + r >> 1, tmp;
		if((tmp = qry(ls[p], l, mid, ge)) > ge){
			return tmp;
		} else {
			return qry(rs[p], mid+1, r, ge);
		}
	}
}

int qrl(int x, int mn){
	return qry(rtl[x], 1, 1e9, mn);
}
int qrr(int x, int mn){
	return qry(rtr[x], 1, 1e9, mn);
}

void solve(){
	scanf("%d", &n);
	for(int i = 1; i <= n; ++ i){
		scanf("%lld", &a[i]);
		q[i] = { a[i], i, -1 };
	}
	ll mx = 0;
	rtl[0] = ++ cnt;
	for(int i = 1; i <= n; ++ i){
		mx = max(mx, a[i]);
		rtl[i] = add(rtl[i-1], 1, 1e9, a[i]);
		b[i] = mx;
	}
	mx = 0;
	rtr[n+1] = ++ cnt;
	for(int i = n; i >= 1; -- i){
		mx = max(mx, a[i]);
		b[i] = min(b[i], mx);
		rtr[i] = add(rtr[i+1], 1, 1e9, a[i]);
		q[i+n] = { b[i], i, 1 };
	}
	sort(q + 1, q + n + n + 1);
	set<int> st;
	for(int i = 1; i < n + n; ++ i){
		if(get<2>(q[i]) == -1){
			st.insert(get<1>(q[i]));
		} else {
			st.erase(get<1>(q[i]));
		}
		if(get<0>(q[i]) != get<0>(q[i+1]) && st.size()){
			ll fr = get<0>(q[i]), to = get<0>(q[i+1]);
			ll l = *st.begin(), r = *st.rbegin(), k = st.size();
			ll oo = qrl(l, fr), pp = qrr(l, fr), qq = qrl(r, fr), rr = qrr(r, fr);
			ll p = qrl(l, fr), q = qrr(r, fr);
			if(k == 1){
				ans += Sum(fr, to);
				ans += (oo + pp) % P * (to - fr) % P;
			} else {
				ans += (oo + rr + min(pp, qq) + k + k - 3) % P * (to - fr) % P;
				ans += 3 * (k - 1) % P * Sum(fr, to) % P;
			}
			ans %= P;
		}
	}
	printf("%lld\n", ans);
}

11. NOI2010 - 海拔 [省选/NOI-]

首先易得每个点海拔为 \(0\)\(1\),且两部分分别连通。

然后就相当于平面图最小割,等于对偶图最短路即可。

注意建图时两个方向都要建图,每条边把它顺时针翻转一个九十度建边。

点击查看代码
const int N = 510;
int n, a[4][N][N];
vector<pair<int, int> > g[N*N];
int dis[N*N], vis[N*N];

#define cg(x, y) ((x-1)*n+y+1)
#define add(u, v, w) g[u].emplace_back(v, w)

void solve(){
	scanf("%d", &n);
	for(int i = 0; i < 4; i += 2){
		for(int j = 0; j <= n; ++ j){
			for(int k = 1; k <= n; ++ k){
				scanf("%d", &a[i][j][k]);
			}
		}
		for(int j = 1; j <= n; ++ j){
			for(int k = 0; k <= n; ++ k){
				scanf("%d", &a[i+1][j][k]);
			}
		}
	}
	int st = 0, ed = 1;
	for(int i = 1; i <= n; ++ i){
		for(int j = 1; j <= n; ++ j){
			if(j == 1) add(st,       cg(i, j),   a[1][i][0]);
			if(i == n) add(st,       cg(i, j),   a[0][n][j]);
			if(j == n) add(cg(i, j), ed,         a[1][i][n]);
			if(i == 1) add(cg(i, j), ed,         a[0][0][j]);
			if(i > 1)  add(cg(i, j), cg(i-1, j), a[0][i-1][j]);
			if(i < n)  add(cg(i, j), cg(i+1, j), a[2][i][j]);
			if(j > 1)  add(cg(i, j), cg(i, j-1), a[3][i][j-1]);
			if(j < n)  add(cg(i, j), cg(i, j+1), a[1][i][j]);
		}
	}
	memset(dis, 0x3f, sizeof(dis));
	dis[0] = 0;
	priority_queue<pair<int, int> > q;
	q.push(make_pair(0, 0));
	while(!q.empty()){
		int x = q.top().second;
		q.pop();
		if(vis[x]){
			continue;
		}
		vis[x] = 1;
		for(auto i : g[x]){
			if(dis[i.first] > dis[x] + i.second){
				dis[i.first] = dis[x] + i.second;
				q.push(make_pair(-dis[i.first], i.first));
			}
		}
	}
	printf("%d\n", dis[1]);
}

12. NOI2010 - 超级钢琴 [省选/NOI-]

无脑两个 log 做法。

首先做个前缀和,定义一个状态 \((i,k)\) 权值为 \(\operatorname{kthmax}_{j\in[i+L,\min(i+R,n)]}\{s_j\}-s_i\),如果不存在这样的 kth 则为负无穷。

那么维护一个大根堆,首先将所有状态 \((i,0)\) 插入。每次取队首 \((i,j)\),添加答案,插入 \((i,j+1)\) 即可。

查询 kthmax 可用主席树。

点击查看代码
const int N = 5e5 + 10;
int n, k, L, R, a[N], s[N], b[N], m;
int rt[N], t[N*30], ls[N*30], rs[N*30], cnt = 1;

int add(int p, int l, int r, int x){
	++ cnt;
	tie(t[cnt], ls[cnt], rs[cnt]) = tie(t[p], ls[p], rs[p]);
	p = cnt;
	if(l == r){
		++ t[p];
	} else {
		int mid = l + r >> 1;
		if(x <= mid){
			ls[p] = add(ls[p], l, mid, x);
		} else {
			rs[p] = add(rs[p], mid+1, r, x);
		}
		t[p] = t[ls[p]] + t[rs[p]];
	}
	return p;
}
int qry(int p, int q, int l, int r, int x){
	if(l == r){
		return l;
	} else {
		int mid = l + r >> 1;
		int k = t[rs[p]] - t[rs[q]];
		if(k >= x){
			return qry(rs[p], rs[q], mid+1, r, x);
		} else {
			return qry(ls[p], ls[q], l, mid, x-k);
		}
	}
}

int kmn(int l, int r, int k){
	return qry(rt[r+1], rt[l], 1, m, k);
}

void solve(){
	scanf("%d%d%d%d", &n, &k, &L, &R);
	for(int i = 1; i <= n; ++ i){
		scanf("%d", &a[i]);
		s[i] = s[i-1] + a[i];
		b[i] = s[i];
	}
	b[n+1] = 0;
	sort(b + 1, b + n + 2);
	m = unique(b + 1, b + n + 2) - b - 1;
	priority_queue<tuple<int, int, int> > q;
	for(int i = 0; i <= n; ++ i){
		s[i] = lower_bound(b + 1, b + m + 1, s[i]) - b;
		rt[i+1] = add(rt[i], 1, m, s[i]);
	}
	for(int i = 0; i <= n - L; ++ i){
		q.push({ b[kmn(i+L, min(n, i+R), 1)] - b[s[i]], i, 1 });
	}
	ll ans = 0;
	while(k--){
		auto now = q.top();
		q.pop();
		ans += get<0>(now);
		int x = get<1>(now), y = get<2>(now) + 1;
		if(min(n, x+R) - (x+L) + 1 >= y){
			q.push({ b[kmn(x+L, min(n, x+R), y)] - b[s[x]], x, y });
		}
	}
	printf("%lld\n", ans);
}

13. CF838D - Airplane Arrangements [*2700]

首先考虑只从一个门进的情况。可以等价于:\(n+1\) 个位置排成一个环,\(m\) 个人,每个人选一个位置 \(i\),若 \(i\) 已经被占用则继续选 \(i+1\),若 \(n+1\) 已经被占用则继续选 \(1\),最后找到一个未被占用的位置则占用。最后合法当且仅当 \(n+1\) 这个位置是空的。所有方案总数是 \((n+1)^m\) 个,而其中会有若干个合法的。可以发现每个最终状态出现概率是相同的,所以一个状态合法的概率即为 \(\dfrac{\dbinom {n+1}m}{\dbinom nm}=\dfrac {n+1-m}{n+1}\)。答案是 \((n+1)^m\dfrac {n+1-m}{n+1}\)

而两个门其实不影响概率。所以答案是 \((2n+2)^m\dfrac {n+1-m}{n+1}\)

点击查看代码
const int N = 1e6 + 10;
typedef long long ll;
const ll P = 1e9 + 7;
int n, m;

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(){
    cin >> n >> m;
    cout << qp(n+n+2, m) * (n+1-m) % P * qp(n+1, P-2) % P;
    return 0;
}

14. USACO19DEC - Tree Depth P [省选/NOI-]

首先,一个点的深度等于它的祖先个数 \(+1\)。所以我们只用枚举 \((u,v)\),计算 \(v\)\(u\) 祖先的方案数即可。而 \(v\)\(u\) 祖先等价于 \(u,v\) 之间的数都 \(>a_v\)

考虑 dp 顺序:先 \(u,v\) 之间的点,再 \(u\),再 \(v\),最后其余所有点。发现这个 dp 实际上等价于 dp 所有点(可以看作一个分组背包)后再撤销掉一个 \([0,u-v]\) 的组。

为什么等价:首先 \([u,v],(v,n]\) 肯定等价;\([1,u)\) 可以看作翻转过后的若干项,也等价。

使用前缀和优化+撤销 dp 可以做到 \(O(n^3)\) dp \(O(n^2)\) 撤销。

之后,若 \(u<v\),则 \(v\) 会和 \([u,v)\) 构成逆序对,此时答案加上 \(f_{n-1,k-(v-u)}\);否则 \(v\) 不会和任何构成逆序对,答案加上 \(f_{n-1,k}\)。(\(n-1\) 是因为撤销掉了一个组别)。

点击查看代码
const int N = 310;
int n, k;
ll P, f[2][N*N], s[N*N], ans[N];
#define ck(x) ((x) >= P ? (x) - P : (x))

int main(){
    scanf("%d%d%lld", &n, &k, &P);
    f[0][0] = 1;
    for(int i = 1; i <= n; ++ i){
        s[0] = f[i&1][0] = 1;
        for(int j = 1; j <= n * n; ++ j){
            f[i&1][j] = s[j] = ck(s[j-1] + f[(i-1)&1][j]);
            if(j >= i){
                f[i&1][j] = ck(f[i&1][j] - s[j-i] + P);
            }
        }
    }
    for(int i = 1; i <= n; ++ i){
        ans[i] = f[n&1][k];
    }
    for(int t = 1; t < n; ++ t){
        memset(s, 0, sizeof(s));
        ll nw = P - 1;
        s[0] = 1;
        for(int i = 1; i <= n * n - t; ++ i){
            if(i > t){
                nw = ck(nw + s[i-t-1]);
            }
            s[i] = ck(f[n&1][i] + nw);
            nw = ck(nw - s[i] + P);
        }
        for(int u = 1; u <= n - t; ++ u){
            //v->u
            if(t <= k){
                ans[u] = ck(ans[u] + s[k-t]);
            }
            //u->v
            ans[u+t] = ck(ans[u+t] + s[k]);
        }
    }
    for(int i = 1; i <= n; ++ i){
        printf("%lld ", ans[i]);
    }
    puts("");
}

15. CF1528F - AmShZ Farm [*3300]

题意中 \(a\) 的限制可以转化为:有 \(n\) 个人,每个人首先走到 \(a_i\),然后往后走直到第一个没有被占用的位置并占用。由上文 13. 得这种方案数为 \((n+1)^{n-1}\)。我们在此时将 \(a\) 的值域扩展到 \([1,n+1]\)。则对于任意的一个 \(a\),其对应的 \(b\) 个数为 \(\sum_{i=1}^{n+1}cnt_i^k\)。一个合法的 \(a\) 能够映射到 \(n\) 个不合法的 \(a\),且这 \(n+1\)\(a\) 仅仅是平移可以得到。所以这 \(n+1\)\(a\) 对应的 \(b\) 个数都是相同的。所以我们仅用求出对于所有 \({(n+1)}^{n}\)\(b\) 个数总和除掉 \(n+1\) 即可。又因为对于任意的数 \(i\),统计 \(a_{b_k}=i\) 的贡献又都是相同的,所以 \(n+1\)\(i\),两个 \(n+1\) 抵消,答案及为:

\(ans=\sum_{i=0}^n\dbinom nii^kn^{n-i}\),即选择 \(i\) 个位置为钦定值,其它位置为剩余 \(n\) 种值。

推式子,参考 可得:

\(ans=\sum_{i=0}^n n^{\underline i}(n+1)^{n-i}{{k}\brace{i}}\)

因为当 \(i>k\)\({{k}\brace{i}}=0\),则:

\(ans=\sum_{i=0}^{\min(n,k)} n^{\underline i}(n+1)^{n-i}{{k}\brace{i}}\)

前面两项可以线性预处理。最后一项需使用第二类斯特林数行。

点击查看代码
const int N = 4e5 + 10;
int n, k, m, tr[N];
typedef long long ll;
const ll P = 998244353, G = 3, iG = (P + 1) / G;
ll f[N], g[N], fac[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;
}
void ntt(ll *f, int n, bool op){
	for(int i = 0; i < n; ++ i){
		if(i < tr[i]){
			swap(f[i], f[tr[i]]);
		}
	}
	for(int p = 2; p <= n; p <<= 1){
		int len = p >> 1;
		ll tg = qp(op ? G : iG, (P-1) / p);
		for(int k = 0; k < n; k += p){
			ll buf = 1;
			for(int l = k; l < k + len; ++ l){
				int tmp = buf * f[l+len] % P;
				f[l+len] = (f[l] - tmp + P) % P;
				f[l] = (f[l] + tmp) % P;
				buf = buf * tg % P;
			}
		}
	}
}

int main(){
    cin >> n >> k;
    int kk = k;
    fac[0] = 1;
    for(int i = 1; i <= k; ++ i){
        fac[i] = fac[i-1] * i % P;
    }
    for(int i = 0; i <= k; ++ i){
        f[i] = qp(i, k) * qp(fac[i], P-2) % P;
        g[i] = qp(P-1, i) * qp(fac[i], P-2) % P;
    }
    for(m = k + k, k = 1; k <= m; k <<= 1);
    for(int i = 0; i < k; ++ i){
        tr[i] = (tr[i>>1] >> 1) | ((i & 1) ? k >> 1 : 0);
    }
    ntt(f, k, 1);
    ntt(g, k, 1);
    for(int i = 0; i < k; ++ i){
        f[i] = f[i] * g[i] % P;
    }
    ntt(f, k, 0);
    ll ans = 0, dm = 1, pw = qp(n+1, n), inv = qp(n+1, P-2), in = qp(k, P-2);
    for(int i = 0; i <= min(n, kk); ++ i){
        ans = (ans + dm * pw % P * f[i] % P * in % P) % P;
        dm = dm * (n-i) % P;
        pw = pw * inv % P;
    }
    cout << ans << '\n';
    return 0;
}

16. BalkanOI2018 - Popa [省选/NOI-]

你怎么知道我不会笛卡尔树?

我们可以发现动态维护的右链上一定能找到一个目前点的因数,所以做法是对的。

点击查看代码
int query(int a, int b, int c, int d);
int solve(int N, int* Left, int* Right){
    stack<int> st;
    for(int i = 0; i < N; ++ i){
        int le = -1;
        while(!st.empty() && query(st.top(), i, i, i)){
            le = st.top();
            st.pop();
        }
        Left[i] = le;
        Right[i] = -1;
        if(!st.empty()){
            Right[st.top()] = i;
        }
        st.push(i);
    }
    int le = -1;
    while(!st.empty()){
        le = st.top();
        st.pop();
    }
    return le;
}

17. Ynoi ER 2024 - TEST_132 [省选/NOI-]

你可以根号分治的。

点击查看代码
const int N = 1e6 + 10;
typedef long long ll;
const ll P = 1e9 + 7, Q = 1e9 + 6;
int n, m, x[N], y[N], c[N];
vector<int> g[N], mx;
vector<pair<int, int> > f[N];
ll v[N], ans[N], pw[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;
}

int main(){
    scanf("%d%d", &n, &m);
    int B = 1000000;
    for(int i = 1; i <= n; ++ i){
        scanf("%d%d%lld", &x[i], &y[i], &v[i]);
        g[x[i]].push_back(i);
        ++ c[x[i]];
    }
    for(int i = 1; i <= n; ++ i){
        pw[i] = 1;
        if(c[i] > B){
            mx.push_back(i);
        }
    }
    for(int i = 1; i <= n; ++ i){
        if(c[x[i]] > B){
            f[y[i]].emplace_back(v[i], x[i]);
        } else {
            ans[y[i]] = (ans[y[i]] + v[i]) % P;
        }
    }
    while(m--){
        int op, p;
        scanf("%d%d", &op, &p);
        if(op == 1){
            if(c[p] <= B){
                for(int i : g[p]){
                    ans[y[i]] = (ans[y[i]] + P - v[i]) % P;
                    v[i] = v[i] * v[i] % P;
                    ans[y[i]] = (ans[y[i]] + v[i]) % P;
                }
            } else {
                pw[p] = pw[p] * 2 % Q;
            }
        } else {
            ll res = ans[p];
            for(auto i : f[p]){
                res = (res + qp(i.first, pw[i.second])) % P;
            }
            printf("%lld\n", res);
        }
    }
    return 0;
}
posted @ 2024-03-01 11:13  KiharaTouma  阅读(11)  评论(0编辑  收藏  举报