2026-10-08 hetao1733837 的刷题记录

QOJ15801 Tournament

原题链接:Tournament

分析

原来要科普竞赛图吗?

竞赛图就是无向完全图给每条边加上一个任意的方向。

我目前只知道 竞赛图SCC缩点之后会变成一条链 ,但是还不清楚具体怎么计数......

难道今天我要先学 Tarjan 吗?

我觉得可以进行一下 DP,别管为什么,反正你肯定要写这一步!

我们设 d p i dp_{i} dpi 表示缩点之后的链长为 i i i 的竞赛图的个数吗?

但是,这个真的可以转移吗?我觉得前途很有限......

呃......难道说,我们发现,这个东西可以递归下去?那也不对啊......


什么?我竟然开错题了😭

我们设 f n f_n fn 表示 n n n 个点的强连通竞赛图数量, g n g_n gn 表示 n n n 个点的竞赛图数量。

可知:

f n = g n − ∑ i = 1 n − 1 C n i × f i × g n − i f_n=g_n-\sum\limits_{i=1}^{n-1}{C_{n}^{i}\times f_i\times g_{n-i}} fn=gn−i=1∑n−1Cni×fi×gn−i

那么,我们设 d p i , j dp_{i,j} dpi,j 表示缩点之后链长为 j j j,有 i i i 个点的图的个数。

那么,有转移:

d p i , j = ∑ k = 1 i C n k × f k × d p i − k , j − 1 dp_{i,j}=\sum\limits_{k=1}^{i}{C_{n}^{k}\times f_{k}\times dp_{i-k,j-1}} dpi,j=k=1∑iCnk×fk×dpi−k,j−1

这个转移是 O ( n 3 ) O(n^3) O(n3) 的。答案是 ∑ j = 1 n d p n , j \sum\limits_{j=1}^{n}dp_{n,j} j=1∑ndpn,j。

我们使用 EGF,设 F ( x ) = ∑ f n × x n n ! F(x)=\sum{f_n\times\frac{x^n}{n!}} F(x)=∑fn×n!xn,答案即为 n ! × x n × F ( x ) ( 1 − F ( x ) ) 2 n!\times x\^n\times\frac{F(x)}{(1-F(x))^2} n!×xn×(1−F(x))2F(x),其中, k k k 为枚举的链长。使用多项式快速幂+任意模数 NTT 即可优化到 O ( n log ⁡ n ) O(n\log n) O(nlogn)。


这个竟然是纯纯的式子题😭

式子就是:

a n s = ∑ i = 1 n f ( i ) × f ( n − i ) × C n i ans=\sum\limits_{i=1}^{n}{f(i)\times f(n-i)\times C_{n}^{i}} ans=i=1∑nf(i)×f(n−i)×Cni

其中,

f i = 2 C i 2 f_{i}=2^{C_{i}^{2}} fi=2Ci2


相当于枚举断点,然后由于还有一个方向,所以,两遍是对的,就做完了。

正解

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
#define mod 1000000007
using namespace std;
const int N = 100005;
int n;
int fac[N], inv[N];
int qpow(int a, int b){
	int res = 1;
	while (b){
		if (b & 1)
			res = res * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
int C(int n, int m){
	if (n < 0 || m < 0 || n < m){
		return 0;
	}
	return fac[n] * inv[n - m] % mod * inv[m] % mod;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	fac[0] = 1;
	for (int i = 1; i < N; i++){
		fac[i] = fac[i - 1] * i % mod;
	}
	inv[N - 1] = qpow(fac[N - 1], mod - 2);
	for (int i = N - 2; i >= 0; i--){
		inv[i] = inv[i + 1] * (i + 1) % mod;
	}
	int ans = 0;
	for (int i = 1; i <= n; i++){
	    long long e1 = i * (i - 1) / 2;
	    long long e2 = (n - i) * (n - i - 1) / 2;
	    ans = (ans + qpow(2, e1) * qpow(2, e2) % mod * C(n, i) % mod) % mod;
	}
	cout << ans;
}

QOJ20027 Longest Common Prefix

原题链接:Longest Common Prefix

分析

那就是字典树在线维护即可。然后,cnt 就是经过的个数,就是 pos 的相关。好的,你做完了!

正解

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 500005;
int n, ch[N][26], tot;
int de[N], dp[N];
int ans;
int cnt[N];
void insert(string s){
	int u = 0;
	for (int i = 0; i < (int)s.length(); i++){
		if (!ch[u][s[i] - 'a']){
			ch[u][s[i] - 'a'] = ++tot;
			de[tot] = de[u] + 1;
		}
		u = ch[u][s[i] - 'a'];
		int tmp = cnt[u];
		cnt[u] = tmp + 1;
		int pos = tmp + 1;
		if (de[u] > dp[pos]){
			int tmp2 = dp[pos];
			dp[pos] = de[u];
			ans += (dp[pos] ^ pos) - (tmp2 ^ pos);
		}
	}
}
string s;
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> n;
	for (int i = 1; i <= n; i++){
		cin >> s;
		ans += i;
		insert(s);
		cout << ans << '\n';
	}
}
/*
4
a
ab
abc
abcd
*/ 

/*
8
ab
b
ba
ab
aa 
ba
a
bb
*/ 

LGP5666 CSP-S 2019 树的重心

原题链接:CSP-S 2019 树的重心

分析

我是不是会写 O ( n 2 ) O(n^2) O(n2)?要不要写一下呢?呃......不手懒了,写!呃......卡宕机了😭

哦,其实只需要在 dfs 时,循环外面进行计算即可,这波躺了😭


考虑怎么换根......

我会树上倍增求 LCA,但是,这个该怎么求呢?我们发现,在换根的时候,子树的大小只会变化 1 1 1!

然后呢?这个时方便换根的......呃......倍增......

那么,我设 d p u , i dp_{u,i} dpu,i 表示以 u u u 为根的子树在......的 2 i 2^i 2i 的情况下的......不知道😭


我不可能直接把子树大小扔进去吧 (*Φ皿Φ*)


我们开始剖分!就是说,我们之前从 CF1174F 中知道,重心一定在重儿子里,那么,直接写一个倍增就可以开始跳了!

原来儿子也可以这样跳吗?

正解

cpp 复制代码
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 300005;
int T;
int n;
vector<int> e[N];
int s[N][20], ss[N], pa[N];
int sz[N];
int ans;
void dfs1(int u, int fa){
	sz[u] = 1;
	s[u][0] = ss[u] = 0;
	pa[u] = fa;
	for (auto v : e[u]){
		if (v == fa)
			continue;
		dfs1(v, u);
		sz[u] += sz[v];
		if (sz[v] > sz[s[u][0]]){
			ss[u] = s[u][0];
			s[u][0] = v;
		} 
		else if (sz[v] > sz[ss[u]]){
			ss[u] = v;
		}
	}
}
void dfs2(int u, int fa){
	int son = s[u][0], siz = sz[u];
	pa[fa] = u;
	for (auto v : e[u]){
		if (v == fa)
			continue;
		if (v == son)
			s[u][0] = ss[u];
		else
			s[u][0] = son;
		if (sz[fa] > sz[s[u][0]])
			s[u][0] = fa;
		for (int i = 1; i <= 19; i++){
			s[u][i] = s[s[u][i - 1]][i - 1];
		}
		sz[u] = n - sz[v];
		pa[u] = pa[v] = 0;
		int pos = u;
		for (int i = 19; i >= 0; i--){
			if (sz[u] - sz[s[pos][i]] <= sz[u] / 2)
				pos = s[pos][i];
		}
		ans += pos + pa[pos] * (max(sz[u] - sz[pa[pos]], sz[s[pa[pos]][0]]) <= sz[u] / 2);
		pos = v;
		for (int i = 19; i >= 0; i--){
			if (sz[v] - sz[s[pos][i]] <= sz[v] / 2)
				pos = s[pos][i];
		}
		ans += pos + pa[pos] * (max(sz[v] - sz[pa[pos]], sz[s[pa[pos]][0]]) <= sz[v] / 2);
		dfs2(v, u);
	}
	s[u][0] = son;
	sz[u] = siz;
	pa[u] = fa;
	for (int i = 1; i <= 19; i++){
		s[u][i] = s[s[u][i - 1]][i - 1];
	}
}
void init(){
	for (int i = 1; i <= n; i++){
		e[i].clear();
	}
	ans = 0;
}
signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	cin >> T;
	for (int cs = 1; cs <= T; cs++){
		init();
		cin >> n;
		for (int i = 1, u, v; i < n; i++){
			cin >> u >> v;
			e[u].push_back(v);
			e[v].push_back(u);
		}
		dfs1(1, 0);
		for (int i = 1; i <= 19; i++){
			for (int u = 1; u <= n; u++){
				s[u][i] = s[s[u][i - 1]][i - 1];
			}
		}
		dfs2(1, 0);
		cout << ans << '\n';
	}
}
相关推荐
CoderYanger1 小时前
一轮复习——F.动态规划模型总结(入门篇)
java·开发语言·数据结构·算法·leetcode·职场和发展·动态规划
Frank_refuel1 小时前
【C++】智能指针
开发语言·c++
浅念-2 小时前
动态规划专题:斐波那契数列模型 + 网格路径DP(LeetCode例题全解析)
笔记·算法·leetcode·面试·职场和发展·动态规划·推荐算法
一只旭宝2 小时前
数据结构与算法复习手册:从写过,到讲清楚
开发语言·数据结构·c++·算法
十五年专注C++开发2 小时前
std::filesystem::exists 在有的win10系统抛异常原因分析
c++·文件系统·filesystem
夏幻灵3 小时前
每日一题:LeetCode 20:有效的括号——栈与哈希映射详解
算法·leetcode·哈希算法
hetao17338373 小时前
2026-10-07 hetao1733837 的刷题记录
c++·算法
Nil2084 小时前
leetcode 279完全平方数
算法·leetcode·职场和发展
longlongzihan4 小时前
前缀积与后缀积 —— LeetCode 238. 除了自身以外数组的乘积
开发语言·c++·leetcode