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)    收藏  举报

导航