2026-01-04~06 hetao1733837 的刷题笔记
01-04
LG1976 鸡蛋饼
原题链接:鸡蛋饼
分析
bur, 1 e 8 + 7 1e8+7 1e8+7 也是质数?有点恐怖啊......
这是个黄?好在放了我 n 2 n^2 n2,哦,放我 n 2 n^2 n2 就是橙黄,不放就是绿蓝,彳亍......
那我似乎可以做一些类似于钦定得操作,就是设 d p i dp_i dpi 表示有 2 i 2i 2i 个点的方案数。
转移显然从那些小的转移吧,怎么说呢,由于禁止相交,所以,那不就相当于整出来两个多边形吗?那枚举一下岂不是很香?
喔,好卡,先把前面的发了。
那不是爽完了?不太敢写啊。看一眼题解,对不对。
还是回到了卡特兰数......
等一下,我的思路似乎很接近啊?
我的转移方程大概是
f i = ∑ j = 1 i f j × f n − j f_i=\sum\limits_{j=1}^{i}{f_j\times f_{n-j}} fi=j=1∑ifj×fn−j
好像确实长这样......是不是乘个 n − j n-j n−j 更合理?
好像挺对的,但,我,并没有,看出来,这是个 Catalan 数......
似乎也不对,但是,把圆看成多边形,再分成两个多边形是合理而无比正确的。
模数**打错了。
正解
cpp
#include <bits/stdc++.h>
#define int long long
#define mod 100000007
using namespace std;
const int N = 3005;
int n, c[N];
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
c[0] = 1;
c[1] = 1;
c[2] = 2;
for (int i = 3; i <= n; i++){
for (int j = 0; j < i; j++){
c[i] = (c[i] + c[i - j - 1] * c[j]) % mod;
}
}
cout << c[n];
}
就这吧,感觉很......难评......
LG3200 [HNOI2009] 有趣的数列
原题链接:[HNOI2009] 有趣的数列
分析
写完这题, Catalan \operatorname{Catalan} Catalan 数就只剩一紫一黑了......
这个用公式里的 \operatorname{} 写英文打出来确实好看!
这是绿?™ 模数还不保证质数!
我决定掏出 Catalan \operatorname{Catalan} Catalan 数乱艹一下样例。咋是对的啊?为啥啊?但是,问题又来了,怎么求逆元?
还是要复习一下 exgcd \operatorname{exgcd} exgcd 的。
有点小累啊......要不直接 he \operatorname{he} he?我非常需要知道为什么这个是 Catalan \operatorname{Catalan} Catalan 数!
呃......并没有用到 exgcd \operatorname{exgcd} exgcd,而是分解质因数。
根据要求,偶数位上的数大于左边偶数位上的,还大于与其相邻左边的那个数,又因为这是一个排列,所以,偶数位上的数大于等于它的下标。
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2000005;
int n, p, cnt[N], mn[N];
bool vis[N];
vector<int> e;
int qpow(int a, int b){
int res = 1;
while (b){
if (b & 1)
res = res * a % p;
a = a * a % p;
b >>= 1;
}
return res;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> p;
memset(vis, true, sizeof(vis));
vis[1] = 0;
for (int i = 2; i <= (n << 1); i++){
if (vis[i]){
e.push_back(i);
mn[i] = i;
}
for (int j = 0; j < e.size() && e[j] * i <= (n << 1); j++){
vis[e[j] * i] = false;
mn[e[j] * i] = e[j];
if (i % e[j] == 0)
break;
}
}
for (int i = 1; i <= n; i++){
cnt[i] = -1;
}
for (int i = n + 2; i <= (n << 1); i++){
cnt[i] = 1;
}
for (int i = (n << 1); i >= 2; i--){
if (mn[i] < i){
cnt[mn[i]] += cnt[i];
cnt[i / mn[i]] += cnt[i];
}
}
int ans = 1;
for (int i = 2; i <= (n << 1); i++){
if (mn[i] == i){
ans = ans * qpow(i, cnt[i]) % p;
}
}
cout << ans;
}
那么,我是否可以总结一下,当有两种甚至多种分讨,或可以转化为两种及多种分讨的计数题,可以考虑Catalan数。
明天问一下 elk。
01-05
LG1655 小朋友的球
原题链接:小朋友的球
分析
斯特林数板子吧......
先不写高精度,先写一个暴力的式子,我艹™,咋又是高精度?
非常好,有了给出的 20pts 部分分!
好了,高精度直接粘就行了!
正解
cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 105;
const int M = 200;
struct bigint{
int len, a[M];
bigint(int x = 0){
memset(a, 0, sizeof(a));
len = 0;
if (x == 0){
len = 1;
}
else{
while (x){
a[++len] = x % 10;
x /= 10;
}
}
}
int &operator[](int i){
return a[i];
}
void flatten(int L){
len = L;
for (int i = 1; i <= len; i++){
a[i + 1] += a[i] / 10;
a[i] %= 10;
}
while (len > 1 && a[len] == 0){
len--;
}
}
void print(){
for (int i = max(len, 1); i >= 1; i--){
printf("%d", a[i]);
}
}
};
bigint operator+(bigint a, bigint b){
bigint c;
int len = max(a.len, b.len);
for (int i = 1; i <= len; i++){
c[i] += a[i] + b[i];
}
c.flatten(len + 1);
return c;
}
bigint operator*(bigint a, int b){
bigint c;
int len = a.len;
for (int i = 1; i <= len; i++){
c[i] = a[i] * b;
}
c.flatten(len + 11);
return c;
}
int n, m;
bigint s[N][N];
int main(){
s[0][0] = bigint(1);
for (int i = 1; i < N; i++){
s[i][0] = bigint(0);
}
for (int i = 1; i < N; i++){
for (int k = 1; k <= i; k++){
s[i][k] = s[i - 1][k] * k + s[i - 1][k - 1];
}
}
while (scanf("%d %d", &n, &m) != EOF){
s[n][m].print();
printf("\n");
}
}
那,继续打斯特林数吧!
哥们,现在 Catalan \operatorname{Catalan} Catalan 数和 Stirling \operatorname{Stirling} Stirling 数一共剩了两紫五黑,看笑了......
先打紫吧......
打不了啊? Stirling \operatorname{Stirling} Stirling 数这几个都是卷积, NTT \operatorname{NTT} NTT,能打一点的可能是一个 Catalan \operatorname{Catalan} Catalan 数的黑......
那......打一下?
打不了啊?
开容斥吧......
LG3349 [ZJOI2016] 小星星
原题链接:[ZJOI2016] 小星星
分析
受着吧......到这个阶段了,是时候写的题几乎全是紫黑了......谁能想到我 25 25 25 年暑假刚过了第一道蓝......
变成树了?给哥们看蒙了。
bur,相当于把图重构成了一棵树?都是啥啊/ll
终于给题看懂了/ll
n ≤ 17 n\le 17 n≤17,指向了状压 DP,由此,可以想到一个暴力 D P DP DP,即 f i , j , S f_{i,j,S} fi,j,S 表示 i i i 号节点映射到 j j j, i i i 子树的编号集合为 S S S 的方案数。
时间复杂度爆了,那么,考虑优化 S S S 这一维,容斥掉重复映射即可。
那么,每次 O ( 2 n ) O(2^n) O(2n) 枚举 { 1 , 2 , ... , n } \{1,2,\dots,n\} {1,2,...,n} 的一个子集,强制树上每个点编号为这个子集,可以有一个 O ( n 3 ) O(n^3) O(n3) 的 D P DP DP,则答案为:
∣ S ∣ = n 的方案数 − ∣ S ∣ = n − 1 的方案数 + ∣ S ∣ = n − 2 的方案数 = ... |S|=n的方案数-|S|=n-1的方案数+|S|=n-2的方案数=\dots ∣S∣=n的方案数−∣S∣=n−1的方案数+∣S∣=n−2的方案数=...
那我也不会 D P DP DP 啊?
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 20;
int n, m, u, v;
bool edge[N][N], vis[N];
int w[N], f[N][N];
int ans = 0;
vector<int> e[N];
int cur;
void dfs(int u, int fa){
for (auto v : e[u]){
if (v == fa)
continue;
dfs(v, u);
}
for (int i = 1; i <= cur; i++){
int x = w[i];
f[u][i] = 1;
for (auto v : e[u]){
if (v == fa)
continue;
int sum = 0;
for (int j = 1; j <= cur; j++){
int y = w[j];
if (!edge[x][y])
continue;
sum += f[v][j];
}
f[u][i] *= sum;
}
}
}
void work(){
cur = 0;
for (int i = 1; i <= n; i++){
if (vis[i]){
w[++cur] = i;
}
}
memset(f, 0, sizeof(f));
dfs(1, 0);
for (int i = 1; i <= cur; i++){
if ((n - cur) % 2 == 1){
ans -= f[1][i];
}
else{
ans += f[1][i];
}
}
}
void DFS(int p){
if (p == n + 1){
work();
return;
}
vis[p] = 0;
DFS(p + 1);
vis[p] = 1;
DFS(p + 1);
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> m;
memset(edge, 0, sizeof(edge));
for (int i = 1; i <= m; i++){
cin >> u >> v;
edge[u][v] = edge[v][u] = 1;
}
for (int i = 1; i < n; i++){
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
DFS(1);
cout << ans;
}
LG3270 [JLOI2016] 成绩比较
原题链接:[JLOI2016] 成绩比较
分析
这个可以看出来是容斥,但是......我不会......
手玩样例!手玩不出来/ll
我决定不写了,啥都打不开,高二模考™啊,一点素质都没有......
正解
cpp
那么,开始 D P o f D P DP\,\,of\,\,DP DPofDP。
网好了,但我不打算写上面那一题了,计数学久了,啥都是萌萌的😊
01-06
CF1658D2 388535 (Hard Version)
原题链接1:CF1658D2 388535 (Hard Version)
原题链接2:D2. 388535 (Hard Version)
分析
朴素二进制分解假了/ll
考虑枚举所有可能答案,然后拿一个 Trie \operatorname{Trie} Trie 树维护一下做完了。
没做完......昨天晚上 aoao 给的,然后读题的时候闪过了字典树,没往这个方向想,然后给了个假思路,没过 xyd 的大样例。
盒出来原题,看 LG 的题解全 WA 了......等一下,这题好像是 SPJ......
我是🍬🍬!
并不能过吧......
还是学官方题解吧......
同样枚举答案,不过这次拿了一个 set,可以接受。
法一
若 l l l 为偶数, r r r 为奇数,忽略最后一位,区间除以 2 2 2 递归求解。否则,将 a i a_i ai 与 a i ⊕ 1 a_i\oplus1 ai⊕1 配对,最多剩余两个可能的 x x x 值。
法二
就是字典树。
按法一吧......
正解
cpp
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 200005;
int T;
int a[N], l, r;
set<int> s1, s2;
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> T;
while (T--){
int mul = 1;
s1.clear();
cin >> l >> r;
for (int i = l; i <= r; i++){
cin >> a[i];
s1.insert(a[i]);
}
while (l % 2 == 0 && r % 2 == 1){
s2.clear();
for (auto v : s1)
s2.insert(v >> 1);
swap(s1, s2);
l >>= 1;
r >>= 1;
mul <<= 1;
}
int ans;
if (l % 2 == 0)
ans = r;
else
ans = l;
for (auto v : s1){
if (s1.find(v ^ 1) == s1.end()){
int cur = v ^ ans;
bool flag = true;
for (auto u : s1){
flag &= ((cur ^ u) >= l && (cur ^ u) <= r);
}
if (flag){
ans = cur;
break;
}
}
}
cout << ans * mul << '\n';
}
}
题解为啥是错的?
这个代码 D1 也能过!
数数学久了,学会图论对冲一下吧......
我先去吃个饭......
POJ1270 Following Orders
POJ 应该是彻底没了。
大部分题都搬到 openjudge 了。
原题链接:Following Orders
分析
好像是杜克大学 1993 1993 1993 年的题,比 lz 年龄都大了吧......
POJ 没死,只是换了一种方式活着,openjudge 的图标还是 POJ。
而且开了 C++17,✋😭🤚
呃......没看懂......也不想看了......
正解
cpp
#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <string>
#include <sstream>
using namespace std;
const int N = 30;
int n, a[N], d[N][N], topo[N];
bool vis[N];
int indeg[N];
void dfs(int cnt){
if (cnt == n){
for (int i = 0; i < n; i++)
cout << (char)(topo[i] + 'a');
cout << '\n';
return ;
}
for (int i = 0; i < n; i++){
int u = a[i];
if (!vis[u] && indeg[u] == 0){
vis[u] = true;
topo[cnt] = u;
for (int j = 0; j < n; j++){
int v = a[j];
if (d[u][v]){
indeg[v]--;
}
}
dfs(cnt + 1);
for (int j = 0; j < n; j++){
int v = a[j];
if (d[u][v]){
indeg[v]++;
}
}
vis[u] = false;
}
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
string s;
bool fi = true;
while (getline(cin, s)) {
if (s.empty())
continue;
if (!fi){
cout << '\n';
}
fi = false;
memset(d, 0, sizeof(d));
memset(vis, 0, sizeof(vis));
memset(indeg, 0, sizeof(indeg));
n = 0;
for (auto v : s){
if (v >= 'a' && v <= 'z'){
a[n++] = v - 'a';
}
}
sort(a, a + n);
getline(cin, s);
for (int i = 0; i < s.length(); i++){
if (s[i] >= 'a' && s[i] <= 'z'){
int st = s[i] - 'a';
i++;
while (i < s.length() && (s[i] == ' ' || s[i] == '<'))
i++;
if (i < s.length() && s[i] >= 'a' && s[i] <= 'z'){
int ed = s[i] - 'a';
d[st][ed] = 1;
indeg[ed]++;
}
}
}
dfs(0);
}
}
其实,和板子没啥区别......
OpenJudge 是真的效率!快到飞起来!