做题记录

考试之前了,要初赛写的这个。希望能把最近的一些题目记录下来。

P8321 『JROI-4』沈阳大街 2

statement

给定两个长度为 \(n\) 的序列 \(A,B\),

\(\pi\) 是一个长度为 \(n\) 的排列,定义价值函数 \(f(\pi)\)

\[f(\pi)=\prod_{i=1}^n\min(A_i,B_{\pi(i)}) \]

每种排列出现的概率相等,求 \(f(\pi)\) 的期望对 \(998244353\) 取模的结果。

solution

有一个巧夺天工的 \(Dp\) 思路是这样的,我们先看到 \(\min,\max\) 然后考虑排序。

发现排完序也没什么用,我们直接合并两个序列,合并完再排序。为了突出每个元素属于哪个集合,我们把来源于 \(A\) 集合的标为红色,来源于 \(B\) 集合的标为蓝色。

考虑最后是在一个红色和一个蓝色里面求最值,我们直接让他们进行配对。但是发现配对之后的 \(\min\) 正好是那一对的末尾一个,而且可以不重不漏计算排列,我们用这样的方法进行计算。

考虑 \(Dp\)

\(f_{i,j}\) 是前 \(i\) 个数配对 \(j\) 个的总权值,我们把最后的期望写成 \(\dfrac{f_{2n,n}}{n!}\) 的形式。

之后 \(dp\) 考虑 \(i\) 位置参不参与配对。用这个直接进行 \(Dp\)

\(f_{i,j}=f_{i-1,j}+(cnt-j+1)f_{i-1,j-1}\)

其中 \(cnt\) 是在 \(i\) 之前可以和 \(i\) 配对的点的数量。

P4065 [JXOI2017] 颜色

发现每段区间合法的充分必要条件是其中包含的颜色在区间外不存在。于是只要给每一个点上一个值 \(val=rand\),之后只要随便统计一下有多少个区间异或和为 \(0\) 就好了。非常有意思的做法。

复杂度 \(\mathcal{O(nd)}\)\(d\) 是你统计的数据结构的复杂度,比如 \(hash\) 就会更小,\(map\) 就会更大。

P6247 [SDOI2012] 最近最远点对

直接用 \(x\) 排序之后找后面的几个点就好了 \(qwq\)

ABC428E Farthest Vertex

感觉很弱的题,当时没想到。

可以考虑用 \(1\) 找到一个编号最大的最远点 \(u\),然后再用 \(u\) 找到一个编号最大的最远点 \(v\),答案一定出于 \(u,v\) 之中,因为 \(\{u,v\}\) 是一条直径。

用这个思路模拟就好。

All Letter Subgrid Count I

感觉比较弱智的 \(dp\),对于每个字母,设 \(f_{k,i,j}\) 表示包含字母 \(k\) 的最小正方形边长,\(f_{k,i,j}=\min\{f_{k,i-1,j},f_{k,i-1,j-1},f_{i,j-1}\}\),最后统计答案用到边框的正方形边长去减就好了,答案是 \(\mathcal{O(|\Sigma|n^2)}\) 的,跑的飞快。

字符串

给定一个 \(n\) 长度串,然后求串中形如 \(AACBAC\) 的序列数量,其中每一位均为字符,数据范围是 \(|\Sigma| \leq 62,n\leq 10^6\)

先定义 \(f_{i,j}\) 是前 \(i\) 个字符组成的子串中决策前 \(k\) 位的方案数,注意不必要最后一位恰好是 \(i\)

比如 \(f_{i,4}\) 表示前 \(i\) 个字符组成的子串中子序列为 \(AACB\) 的数量。

之后发现这个样子要记录太多东西,所以枚举一下 \(A=x,B=y\) 的子序列数量,这样一共做 \(m^2\) 次最后把结果加起来。

然后考虑这样的操作怎么维护,此时子序列的形态会变成 \(xxyBxy\),我们 不处理 \(i\not\in \{x,y\}\) 的点,之后的 \(f\) 可以直接求。

比如说间隔了 \(q\) 个单位长度,\(f_4:=f_4+qf_3\),其他五个 \(f\) 值不变,然后就如此维护 \(f\) 值。对于每一对 \(\{x,y\}\) 求出最终的 \(f_{n,6}\) 最后加起来。

\(f_{i,j}\)\(i\) 那一维可以滚动掉。

#include <bits/stdc++.h>
#define E(i, l, r) for(int i=l; i<=r; ++i)
#define int long long
#define usg unsigned
#define FILE(filename) { \
	freopen(#filename ".in", "r", stdin); \
	freopen(#filename ".out", "w", stdout); \
}
using ll = long long;
using namespace std;
const ll MOD = 998244353;
const int N=1e6+10,K=8,M=64;
ll n,ans,a[N],dp[10];
char ch[N];
inline int Func(char ch){
	if(isdigit(ch))return ch-'0';
	else if('a' <= ch && ch <= 'z')return ch-'a'+10;
	else return ch-'A'+36;
}
vector<int> v[80];
signed main(){
	FILE(string);
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin >> (ch + 1), n = strlen(ch + 1);
	E(i, 1, n){
		a[i] = Func(ch[i]) + 1;
		v[a[i]].emplace_back(i);
	}
	E(x, 1, M) E(y, 1, M){
		memset (dp, 0, sizeof dp);
		if(x == y)continue;
		int cx = 0, cy = 0, lst = 0, cur = 0;
		if(v[x].empty() || v[y].empty()) continue;
		while(cx < (int)v[x].size() || cy < (int)v[y].size()){
			lst = cur;
			if(!((usg)cy == v[y].size()) && ((usg)cx == v[x].size() || v[x][cx] > v[y][cy])) cur = v[y][cy ++];
			else cur = v[x][cx ++];
			dp[4] += dp[3] * (cur - lst - 1)%MOD;
			dp[4] %= MOD;
			if(a[cur] == x){
				dp[5] += dp[4], dp[5] %= MOD;
				dp[2] += dp[1], dp[2] %= MOD;
				++ dp[1], dp[1] %= MOD;
			} else {
				dp[3] += dp[2], dp[3] %= MOD;
				dp[6] += dp[5], dp[6] %= MOD;
			}
		}
		ans += dp[6], ans %= MOD;
	}
	return cout << (ans % MOD + MOD) % MOD << "\n",0;
}

P6822 [PA 2012 Finals] Tax

感觉很神奇的建图,考虑把边拆成点。

P5502 [JSOI2015] 最大公约数

有一个比较重要的 trick 是区间 gcd 的值域大小不超过 \(\log n\),比较易证。

开个 vector 动态维护一下每个点往左看的最远的 \(gcd = x\) 的端点就好了,由引理可以知道这样的端点总数只有 \(\mathcal{O(n\log n)}\) 个。

用这个思路模拟就好了。

健美猫

编号过的环上有 \(n\) 点上面分别写着 \(a_1,a_2,\cdots,a_n\),然后可以转动圆环任意角度,使得对于每个位置 \(i\),满足 \(\sum _{i=1}^n |i-a_i|\) 的和最小

比较常规的一道题,没有很常见的套路,依题意模拟是 \(\mathcal{O(n^2)}\) 的,过不去。

先作差算出 \(d_i = |i-a_i|\),之后算出初始的总和 \(S\),每次转动一下圆环负数位置上的数都会使答案 \(-1\),否则都会让答案 \(+1\),用扫描线的思路提前插入开始点变号的挡板就能比较简单的统计正负号。

比较烦的一点是每次移动圆环都有 \(\mathcal{O(1)}\) 个点会进行特殊变化,所以特殊处理就好了。

最终复杂度是 \(\mathcal{O(n)}\),我教练给的,比较有意思,和 abc398f 比较像。那道题把计算式子方式换成了逆序对数量。

posted @ 2025-09-19 19:15  ChihiroFujisaki  阅读(7)  评论(0)    收藏  举报