2025-11-23~24 hetao1733837的刷题记录
2025-11-23~24 hetao1733837的刷题记录
11.23
LG14362 [CSP-S2025] road
一些补充:对于一个图,我们求出其最小生成树后,再添加一些边建成新图,新图最小生成树一定不使用原图非树边。
LG14394/LOJ2729 [JOISC 2016] Matryoshka
原题链接1:[JOISC 2016] Matryoshka
原题链接2:「JOISC 2016 Day 1」俄罗斯套娃
分析
fqh一个半小时的课让我倍速40分钟听完了。好的,言归正传。
我们好像可以预处理出来所订购所有套娃封装好之后的堆数,每次询问似乎二分一下就可以写了。但是最劣的复杂度大概是$O(Qnlogn)$之类的,不太能过啊?而且封装的部分类似贪心却神似$DP$,tag里居然还有神秘的$Dilworth$定理,题目有点扑朔迷离了……
17点07分
mhh这么强!在线不好做,转化为离线,把底面直径和高度抽象为平面直角坐标系上的点,变成了第一象限右上角的区域,然后求出最长上升子序列状物,树状数组优化$DP$即可。考虑到$R_i,H_i,A_j,B_j\le 10^9$,需做离散化。
/bx@mhh!
正解
#include <bits/stdc++.h>
using namespace std;
const int N = 200005;
int n, q;
struct node {
int r, h, id;
} inp[N << 1];
int b[N << 1], c[N << 1];
int ans[N];
void add(int x, int v) {
for (int i = x; i < (N << 1); i += i & (-i))
c[i] = max(c[i], v);
}
int query(int x) {
int res = 0;
for (int i = x; i; i -= i & (-i))
res = max(res, c[i]);
return res;
}
bool cmp(node x, node y) {
if (x.r != y.r)
return x.r > y.r;
if (x.h != y.h)
return x.h < y.h;
return x.id < y.id;
}
int main() {
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> q;
int m = 0;
for (int i = 1; i <= n + q; i++) {
int R, H;
cin >> R >> H;
b[++m] = H;
if (i > n) {
inp[i] = (node) {
R, H, i - n
};
} else {
inp[i] = (node) {
R, H, 0
};
}
}
sort(inp + 1, inp + n + q + 1, cmp);
sort(b + 1, b + m + 1);
m = unique(b + 1, b + m + 1) - b - 1;
for (int i = 1; i <= n + q; i++)
inp[i].h = lower_bound(b + 1, b + m + 1, inp[i].h) - b;
for (int i = 1; i <= n + q; i++) {
if (inp[i].id) {
ans[inp[i].id] = query(inp[i].h);
} else {
add(inp[i].h, query(inp[i].h) + 1);
}
}
for (int i = 1; i <= q; i++)
cout << ans[i] << '\n';
}
LG14396/LOJ2731 [JOISC 2016] Solitaire
原题链接1:[JOISC 2016] Solitaire
原题链接2:「JOISC 2016 Day1」棋盘游戏
分析
还在计数?我会判无解!试一下!
欸,过了一个点!但是没有分……就是不存在任意一条边长大于$2$的矩形全是$'x'$。
好的,考虑正解!
显然是通过$DP$进行计数,考虑设出状态。突然发现无解判假了!!!无解当且仅当第一行或第三行出现两个以上连续的$'x'$或者四个角上是$'x'$!
我是**!咦,好像差不多?我不懂。
无解判断:
if (c[1][1] == 'x' || c[1][n] == 'x' || c[3][1] == 'x' || c[3][n] == 'x'){
cout << 0;
return 0;
}
for (int i = 1; i < n; i++){
if ((c[1][i] == 'x' && c[1][i + 1] == 'x') || (c[3][i] == 'x' && c[3][i + 1] == 'x')){
cout << 0;
return 0;
}
}
设一下状态,3行是确定的,所以,我们考虑按行$DP$。设$f_{i,0/1}$表示考虑前$i$列,第$i$列是从行放置的还是列放置的。但是,有的时候需要从行转移,所以,还要额外记录一维,记$f_{i,j,0/1}$表示考虑前$i$列,第$i$列是第$j$个放的,第$i$列是从行放置的还是列放置的。
转移就比较显然,
从$1$转移到$1$,只需保证第$i$列第$2$行上下已经放好即可。
从$0$转移到$1$,需要保证第$i$列第$2$行在第$i-1$列第$2$行之前放置,且第$i$列的上下两格在中心格放置之前被放置。
从$1$转移到$0$,需要保证第$i$列中心格在第$i-1$列的中心格之后放置,且第$i$列的上下两格不能在中心格之前被放置。
做一下前缀和优化,同时第$1,3$行孤立处理,阶乘前缀和可以算总的。
似懂非懂……
正解
//好屎
#include <bits/stdc++.h>
#define int long long
#define mod 1000000007
using namespace std;
const int N = 2005;
void calc(int &x, int y){
x += y;
if (x >= mod)
x -= mod;
}
int n;
string s[3];
int f[N][N * 3][2];
int C[N * 3][N * 3], fac[N * 3];
int p[6010][3];
int cnt;
int work(int l, int r){
int cnt;
cnt = (s[0][l] == 'x') + (s[1][l] == 'x') + (s[2][l] == 'x');
f[l][cnt][1] = fac[cnt - 1];
if (l != 1){
if (cnt == 2)
f[l][1][0] = 1;
if (cnt == 3)
f[l][1][0] = f[l][2][0] = 2;
}
memset(p, 0, sizeof(p));
for (int j = 1; j <= cnt + 5; j++)
p[j][1] = (p[j - 1][1] + f[l][j][1]) % mod;
for (int j = 1; j <= cnt + 5; j++)
p[j][2] = (p[j - 1][2] + j * f[l][j][1]) % mod;
for (int j = cnt; j; j--)
p[j][0] = (p[j + 1][0] + f[l][j][0]) % mod;
for (int i = l + 1; i <= r; i++){
int tmp = (s[0][i] == 'x') + (s[2][i] == 'x');
cnt += tmp + 1;
for (int j = 1; j <= cnt; j++){
int coe = fac[tmp] * C[j - 1][tmp] % mod;
calc(f[i][j][1], p[cnt - tmp - 1][1] * coe % mod);
calc(f[i][j][1], p[j - tmp][0] * coe % mod);
if (tmp){
calc(f[i][j][0], p[j - 1][1] * C[cnt - j][tmp] % mod * fac[tmp] % mod);
if (tmp != 1){
coe = (cnt - j) * fac[tmp] % mod;
calc(f[i][j][0], (p[j - 1][1] * (j - 1) % mod - p[j - 1][2] + mod) % mod * coe % mod);
if (j >= 2)
calc(f[i][j][0], p[j - 2][2] * coe % mod);
}
}
}
for (int j = 1; j <= cnt + 5; j++)
p[j][1] = (p[j - 1][1] + f[i][j][1]) % mod;
for (int j = 1; j <= cnt + 5; j++)
p[j][2] = (p[j - 1][2] + j * f[i][j][1]) % mod;
for (int j = cnt; j; j--)
p[j][0] = (p[j + 1][0] + f[i][j][0]) % mod;
}
::cnt += cnt;
int ret = 0;
for (int i = 1; i <= cnt; i++)
calc(ret, (f[r][i][1] + (r != n) * f[r][i][0]) % mod);
return ret % mod;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n;
for (int i = C[0][0] = fac[0] = 1; i <= n * 3; i++){
fac[i] = fac[i - 1] * i % mod;
for (int j = C[i][0] = 1; j <= i; j++)
C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % mod;
}
cin >> s[0] >> s[1] >> s[2];
s[0] = " " + s[0];
s[1] = " " + s[1];
s[2] = " " + s[2];
if (s[0][1] == 'x' || s[0][n] == 'x' || s[2][1] == 'x' || s[2][n] == 'x'){
cout << 0;
return 0;
}
int c = 0, maxn = 0;
for (int i = 1; i <= n; i++){
if (s[0][i] == 'x'){
maxn = max(maxn, ++c);
}
else{
c = 0;
}
}
if (maxn > 1){
cout << 0;
return 0;
}
c = 0;
for (int i = 1; i <= n; i++){
if (s[2][i] == 'x'){
maxn = max(maxn, ++c);
}
else{
c = 0;
}
}
if (maxn > 1){
cout << 0;
return 0;
}
int tmp = 0, ans = 1;
for (int i = 1; i <= n; i++){
if (s[1][i] == 'o')
tmp += (s[0][i] == 'x') + (s[2][i] == 'x');
else{
int k = cnt;
int j = i;
while (j <= n && s[1][j] == 'x')
j++;
ans = ans * work(i, j - 1) % mod * C[cnt][k] % mod;
i = j - 1;
}
}
cout << ans * C[cnt + tmp][tmp] % mod * fac[tmp] % mod;
}
我并不理解。
我要写亿点计数吗?
11.24
LG8321 『JROI-4』沈阳大街 2
原题链接:『JROI-4』沈阳大街 2
分析
首先,翻译一下人话,$A$单调不增,且$A$的最大值不小于$B$的最小值。
这是何意味啊!!!优化式子吗?
咦,听了Taoran的讲解,需要把$A,B$合并起来排序,这样可以把$\min$这个限制条件融掉,变成匹配状物,给定的性质在实际操作中并非有用,然后就可以$DP$了。
好的,设出状态$f_{i,j}$表示新序列(不妨即为$C$)$[1,i]$当中配对了$j$个的方案数。
转移方程:$f_{i,j}=f[i-1][j-1]*C[i] \times (diff-(j-1))+f_{i-1,j}$
即当前位置选择匹配与选择不匹配做加法原理。
diff是原来属于不同数组$A,B$的个数。
正解
#include <bits/stdc++.h>
#define int long long
#define mod 998244353
using namespace std;
const int N = 5005;
int n, a[N], b[N];
struct node{
int val, id;
};
node c[N << 1];
int m;
long long f[N << 1][N];
bool cmp(node x, node y){
return x.val > y.val;
}
int cnt[2][N << 1];
long long 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;
}
signed main(){
cin >> n;
for (int i = 1; i <= n; i++){
cin >> a[i];
c[++m].val = a[i];
c[m].id = 0;
}
for (int i = 1; i <= n; i++){
cin >> b[i];
c[++m].val = b[i];
c[m].id = 1;
}
sort(c + 1, c + m + 1, cmp);
for (int i = 1; i <= m; i++){
cnt[0][i] = cnt[0][i - 1];
cnt[1][i] = cnt[1][i - 1];
cnt[c[i].id][i]++;
}
f[0][0] = 1;
for (int i = 1; i <= m; i++){
long long diff = cnt[!c[i].id][i];
f[i][0] = 1;
for (int j = 1; j <= min(n, i); j++){
if (j <= diff)
f[i][j] = f[i - 1][j - 1] * c[i].val % mod * (diff - (j - 1)) % mod;
f[i][j] = (f[i - 1][j] + f[i][j]) % mod;
}
}
long long fac = 1;
for (int i = 1; i <= n; i++)
fac = fac * i % mod;
cout << qpow(fac, mod - 2) * f[m][n] % mod;
}
何意味?
LG8594 「KDOI-02」一个仇的复
原题链接:「KDOI-02」一个仇的复
分析
感觉有点小学奥数······虽然我不会,但是,感觉是很典的。我先思考一会。
$2$这个限制似乎很······呃,我不知道。
哦?好像很$DP$?我的感觉是,这些都可以通过更小的方格转移而来,呃,我们设$f_{i,j}$表示长$i$宽$j$的矩形用$k$个条状物覆盖的覆盖方式?我觉得看一眼题解是对的。
原来真的是计数吗?我是**。
先考虑一个较为简单的问题,又$a$个$1\times x$的木板,要拼$2\times b$的大木板,只允许横着放,问方案数。
比较显然,插$b$的空,所以
$$
\sum\limits_{i=0}^{a}\tbinom{b-1}{i-1}\times\tbinom{b-1}{a-i-1}=\tbinom{2b-2}{a-2}
$$
然后,我们需要$j$个$1\times 2$的竖放木块把原序列分成若干段,依旧插板。然后,结合上面的式子,将剩下的若干段木板也铺上,然后吃吃吃,得到下面的式子:
$$
\sum\limits_{i=1}{k}\sum\limits_{j=0}\tbinom{2n-2j-2i}{k-j-2i}\times\tbinom{j+1}{i}\times\tbinom{n-j-1}{i-1}+[n=k]
$$
🤮
正解
#include <bits/stdc++.h>
#define mod 998244353
using namespace std;
const int N = 40000005;
int qpow(int a, int b){
int res = 1;
while (b){
if (b & 1)
res = 1ll * res * a % mod;
a = 1ll * a * a % mod;
b >>= 1;
}
return res;
}
int fac[N], inv[N];
long long C(int x, int y){
return 1ll * inv[y] * inv[x - y] % mod * fac[x] % mod;
}
int n, k;
int main(){
int tmp = 40000000;
fac[0] = 1;
for (int i = 1; i <= tmp; i++){
fac[i] = 1ll * fac[i - 1] * i % mod;
}
inv[tmp] = qpow(fac[tmp],mod - 2);
for (int i = tmp - 1; i >= 0; i--){
inv[i] = 1ll * inv[i + 1] * (i + 1) % mod;
}
cin >> n >> k;
int ans = 0;
for (int i = 1; i <= k; i++){
for (int j = 0; j <= k && k - j - 2 * i >= 0; j++){
if (2 * (n - i - j) < 0 || k - j - 2 * i < 0 || n - j - 1 < 0 || 2 * (n - i - j) < k - j - 2 * i || j + 1 < i || n - j - 1 < i - 1)
continue;
ans = (ans + C(2 * n - 2 * i - 2 * j, k - j - 2 * i) * C(j + 1, i) % mod * C(n - j - 1, i - 1) % mod) % mod;
}
}
cout << (ans + (n == k)) % mod;
}
LG4141 消失之物
原题链接:消失之物
分析
就是那结果推初始值,呃,我并不会。设 $f_{i,0}$ 表示不算消失物品,容积为 i 的方案数,转移是背包板子。$f_{i,1}$ 表示某个物品被删除后容积为 i 的方案数。转移即为
$f_{j,1}=f_{j,0}-f_{j-w_i,1}(j\ge w_i)$
$f_{j,1}=f_{j,0}\mod 10(j < w_i)$
为何 mod 10?因为要末尾数字。别忘了预处理!
正解
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2005;
int n, m, w[N], f[N][2];
signed main(){
cin >> n >> m;
for (int i = 1; i <= n; i++)
cin >> w[i];
f[0][0] = 1;
for (int i = 1; i <= n; i++){
for (int j = m; j >= w[i]; j--){
f[j][0] = (f[j][0] + f[j - w[i]][0]) % 10;
}
}
for (int i = 1; i <= n; i++){
for (int j = 0; j <= m; j++){
f[j][1] = f[j][0];
}
for (int j = w[i]; j <= m; j++){
f[j][1] = (f[j][1] - f[j - w[i]][1] + 10) % 10;
}
for (int j = 1; j <= m; j++){
cout << f[j][1] % 10;
}
cout << '\n';
}
}
模拟赛补完了,收获为0,奖励自己看《星轨》刷题!
LG13349 「ZYZ 2025」自然数序列
原题链接:「ZYZ 2025」自然数序列
分析
不想学 OI,想看《星轨》/(ㄒoㄒ)/~~
居然是我们ZYZ自己组的题🐂🍺👍
何意味,硬构造吗?有点意思了……假的,需要 DP!好像计数和 DP 是不分家的。
我懂了!对于限制 $b_x=y$,转化为 $l,r$ 都减去 $y\times a_x$,并要求 $b_x=0$。
简化一下,只考虑 $l=r=V$。
若忽略 $b_x=0$,则变成完全背包,$n$ 个物品,体积分别为 $a_1,a_2,···,a_n$,填满体积 $V$ 的方案数。
若只限制 $b_x=0$,那么,利用出题人@ask_silently提出的广义HXF容斥,求出 $b_x\neq 0$ 的方案数,用总方案数减一下即可。
然后,综合一下,退背包,假设 $b_{x_1}=b_{x_2}=0$,得出转移方程 $f_V-f_{V-a_{x_1}}-f_{V-a_{x_2}}+f_{V-a_{x_1}-a_{x_2}}$。
对于 $l\neq r$,前缀和模拟上面的即可。
正解
#include <bits/stdc++.h>
#define mod 998244353
using namespace std;
const int N = 3005;
int n, q, l, r, k, op, x, y, a[N << 1], f[N << 1];
void add(int &x, int y){
x += y;
if (x >= mod)
x -= mod;
}
void reduce(int &x, int y){
x -= y;
if (x < 0)
x += mod;
}
int calc(int l, int r){
if (r >= 0){
if (l - 1 >= 0){
return (f[r] - f[l - 1] + mod) % mod;
}
else{
return (f[r] - 0 + mod) % mod;
}
}
else{
if (l - 1 >= 0){
return (0 - f[l - 1] + mod) % mod;
}
else{
return (0 - 0 + mod) % mod;
}
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin >> n >> q;
f[0] = 1;
for (int i = 1; i <= n; i++){
cin >> a[i];
for (int j = a[i]; j <= 5000; j++)
add(f[j], f[j - a[i]]);
}
for (int i = 1; i <= 5000; i++)
add(f[i], f[i - 1]);
while (q--){
vector<int> tmp;
cin >> l >> r >> k;
for (int i = 1; i <= k; i++){
cin >> x >> y;
tmp.push_back(a[x]);
l -= a[x] * y;
r -= a[x] * y;
}
if (r < 0){
cout << 0 << '\n';
continue;
}
if (l < 0)
l = 0;
int m = tmp.size(), ans = 0;
for (int i = 0; i < (1 << m); i++){
int tot = 0;
for (int j = 0; j < m; j++){
if ((i >> j) & 1){
add(tot, tmp[j]);
}
}
if (__builtin_popcount(i) % 2 == 0)
add(ans, calc(l - tot, r - tot));
else
reduce(ans, calc(l - tot, r - tot));
}
cout << ans << '\n';
}
}
posted on 2025-11-24 21:24 hetao1733837 阅读(0) 评论(0) 收藏 举报
浙公网安备 33010602011771号