2023.10.18测试

T1 蛋糕(cake)

Description

你有一块大小为 \(n \times m\) 的蛋糕,你想把它切成 \(1 \times 1\) 的小块。每次你可以选择横着或者竖着切一刀,把蛋糕切成两部分,这两部分再分别进行切割,直到全都变成 \(1 \times 1\) 的小块。你想知道有多少种不同的切法(交换任意两块蛋糕的切割顺序算同一种方案),对 \(1000000007\) 取模。

Format

Input

一行两个整数 \(n,m\),表示开始时蛋糕的大小。

Output

输出一行一个整数,表示切蛋糕的方案数对 \(1000000007\) 取模的结果。

Samples

输入数据 1

2 3

输出数据 1

8

输入数据 2

3 5

输出数据 2

13008

Limitation

对于 \(30\%\) 的数据,\(n,m \le 8\)

对于另外 \(20\%\) 的数据,\(n = 1\)

对于另外 \(20\%\) 的数据,\(n = 2\)

对于 \(100\%\) 的数据,\(n,m\le 300\)

思路

刚开始以为是二分,想了大概半个小时,还敲了代码,才发现不太对劲,应该是 \(\text{DP}\)。刚开始看确实没什么思路,但其实想通了还是很简单的。用一个二维的 \(\text{DP}\) 数组动态转移即可。

\(\text{DP方程:}\begin{cases} dp[i][j] = (dp[i][j] + dp[i - k][j] \times dp[k][j] \% \operatorname{mod}) \% \operatorname{mod} \text{(横切)}\\ dp[i][j] = (dp[i][j] + dp[i][j - k] \times dp[i][k] \% \operatorname{mod}) \% \operatorname{mod} \text{(竖切)} \end{cases}\)
注意需要考虑两次(横着切和竖着切)。

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod = 1000000007;
int n, m;
ll dp[305][305];
int main() {
	freopen("cake.in", "r", stdin);
	freopen("cake.out", "w", stdout);
	scanf("%d%d", &n, &m);
	dp[1][1] = 1;
	for (int i = 1; i <= n; i++) {
		for (int j = 1; j <= m; j++) {
			for (int k = 1; k < i; k++)//横切
				dp[i][j] = (dp[i][j] + dp[i - k][j] * dp[k][j] % mod) % mod;
			for (int k = 1; k < j; k++)//竖切
				dp[i][j] = (dp[i][j] + dp[i][j - k] * dp[i][k] % mod) % mod;
		}
	}
	printf("%lld", dp[n][m]);
	return 0;
}

T2 找钱(deal)

Description

小L所在的L国由于没有普及移动支付,依然在大规模使用纸币。一共有 \(n\) 种面值的纸币,面值互不相同。一天小L去商店购买一个价格为 \(X\) 元的物品,他提前知道了自己手里和店员手里每种面值的纸币的数量,他想知道一共有多少种付钱-找钱的方式。两种付钱-找钱的方式不同,当且仅当存在一种面值,在两种方案中小L付出的该种面值的纸币数量不同或店员找的该种面值的纸币数量不同。此外,设小L付出的纸币面值总数为 \(Y\),则小L付出的纸币中不能存在面值小于等于 \(Y-X\) 的纸币(不然就没有必要付这张纸币了)。

Format

Input

第一行输入两个正整数 \(n,X\),分别表示纸币面值的数量以及小L想要购买的商品的价格。

接下来 \(n\) 行每行三个整数 \(a_i,b_i,c_i\),分别表示第 \(i\) 种纸币的面值,小L拥有的该种纸币数量,店员拥有的该种纸币数量,保证面值 \(a_i\) 单调增加。

Output

一行输出一个整数,表示总方案数对 \(1000000007\) 取模的结果。

Samples

输入数据

3 10
1 5 3
3 2 2
5 3 2

输出数据

5

Limitation

对于所有数据,满足 \(a_i>0\)

对于 \(30\%\) 数据,\(n,X,a_i,b_i,c_i \le 8\)

对于 \(60\%\) 的数据,\(n,X,a_i,b_i,c_i \le 100\)

对于 \(100\%\) 的数据,\(n \le 1000\)\(X,a_i,b_i,c_i \le 10000\)

思路

这是这次测试最有含金量的一道题,虽然很明显的一眼就可以看得出是 \(\text{DP}\),但是究竟如何理解题意和如何推状态转移方程就非常困难了。

这道题的第一个难点在于如何理解 设小L付出的纸币面值总数为Y,则小L付出的纸币中不能存在面值小于等于Y-X的纸币(不然就没有必要付这张纸币了) 这句话了。这说明如果你付的钱中最小面值是 \(a\),那么你最多付给店员 \(X + a - 1\) 元钱。

在理解了这句话之后,题目中非常明显的多重背包的特点就可以派上用场了,同上一题一样,同样需要推两遍 \(\text{DP}\) ,分别为小L付钱和店员找钱。

\(\text{DP方程:}\begin{cases} dp[i][j] = dp[i][j] + dp[i + 1][j - k \times a[i]] \% \operatorname{mod} \text{(付钱)}\\ dp[i][j] = dp[i][j] + dp[i - 1][j - k \times a[i]] \% \operatorname{mod} \text{(找钱)} \end{cases}\)
注意:取 \(ans\) 时依旧需要对 \(dp1\)\(dp2\) 进行一定的处理。

代码

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod = 1000000007;
const int N = 1005, M = 20005;
ll dp1[N][M], dp2[N][M];
ll x, a[N], b[N], c[N], maxn = -114514;
int n;
inline ll read() {
	char ch = getchar();
	ll x = 0, dp1 = 1;
	while (ch < '0' || ch > '9') {
		if (ch == '-') dp1 = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + ch - '0';
		ch = getchar();
	}
	return x * dp1;
}
int main() {
	freopen("deal.in","r",stdin);
	freopen("deal.out","w",stdout);
	n = read(), x = read();
	for (int i = 1; i <= n; i++) {
		a[i] = read(), b[i] = read(), c[i] = read();
		maxn = max(maxn, a[i]);
	}
	dp1[n + 1][0] = 1, dp2[0][0] = 1;
	for (int i = n; i >= 1; i--) {
		for (int j = 0; j < x + a[i]; j++) {
			for (int k = 0; k <= b[i] && k * a[i] <= j; k++) {
				dp1[i][j] = dp1[i][j] + dp1[i + 1][j - k * a[i]];
				if (dp1[i][j] > mod) dp1[i][j] = dp1[i][j] - mod;
			}
		}
		for (int j = x + a[i]; j < x + maxn; j++)
			dp1[i][j] = dp1[i + 1][j];
	}
	for (int i = 1; i <= n; i++) {
		for (int j = 0; j < maxn; j++) {
			for (int k = 0; k <= c[i] && k * a[i] <= j; k++) {
				dp2[i][j] = dp2[i][j] + dp2[i - 1][j - k * a[i]];
				if (dp2[i][j] > mod) dp2[i][j] = dp2[i][j] - mod;
			}
		}
	}
	ll ans = 0;
	for (int j = x; j < x + maxn; j++) {
		ans += dp1[1][j] * dp2[n][j - x];
		if (ans > mod) ans = ans % mod;
	}
	printf("%lld\n", ans);
	return 0;
}

提示:这是一个 \(\text{TLE}\) 的代码。
需要用类似于前缀和的思路优化为一维 \(\text{DP}\)

#include <bits/stdc++.h>
#define ll long long
using namespace std;
const ll mod = 1000000007;
const int N = 10005;
ll n, x, cnt1, cnt2, ans, maxn;
ll a[N], b[N], c[N], f[N << 1], dp1[N << 1], dp2[N << 1];
inline ll read() {
	char ch = getchar();
	ll x = 0, f = 1;
	while (ch < '0' || ch > '9') {
		if (ch == '-') f = -1;
		ch = getchar();
	}
	while (ch >= '0' && ch <= '9') {
		x = x * 10 + ch - '0';
		ch = getchar();
	}
	return x * f;
}
void solve1() {
	for (int i = n; i >= 1; i--) {
		memset(f, 0, sizeof(f)), f[0] = 1;
		for (int k = a[i]; k < x + a[i]; k++) {
			f[k] = (f[k - a[i]] + dp1[k]) % mod;
			if (k - a[i] * b[i] - a[i] >= 0) {
				f[k] -= dp1[k - a[i] * b[i] - a[i]];
				if (f[k] < 0) f[k] += mod;
			}
		}
		for (int k = 0; k < x + a[i]; k++) dp1[k] = f[k];
	}
}
void solve2(ll maxx) {
	for (int i = n; i >= 1; i--) {
		memset(f, 0, sizeof(f)), f[0] = 1;
		for (int k = a[i]; k < maxx; k++) {
			f[k] = (f[k - a[i]] + dp2[k]) % mod;
			if (k - a[i] * c[i] - a[i] >= 0) {
				f[k] -= dp2[k - a[i] * c[i] - a[i]];
				if (f[k] < 0) f[k] += mod;
			}
		}
		for (int k = 0; k < 10000; k++) dp2[k] = f[k];
	}
}
int main() {
	freopen("deal.in", "r", stdin);
	freopen("deal.out", "w", stdout);
	n = read(), x = read();
	dp1[0] = dp2[0] = f[0] = 1;
	for (int i = 1; i <= n; i++) {
		a[i] = read(), b[i] = read(), c[i] = read();
		maxn = max(maxn, a[i]);
	}
	solve1(), solve2(x + maxn);
	for (int i = 0; i < x + maxn; i++) {
		ans += 1ll * dp1[x + i] * dp2[i] % mod;
		if (ans > mod) ans -= mod;
	}
	printf("%d", ans);
	return 0;
}
//确实是非常明显的dp,但是没有想到多重背包+两次遍历
//需要优化成一维dp,否则TLE或WA

T3 旅行(tour)

Description

\(\varphi \varphi\)打算去旅行。OI国有 \(n\) 座城市,编号 \(1\)\(N\),和 \(M\) 条单向路径。其中第 \(i\) 条路径连接城市 \(a_i,b_i\),表示你可以从 \(a_i\) 走向 \(b_i\),但不能从 \(b_i\) 走向 \(a_i\)

\(\varphi \varphi\)计划从某个城市出发,经过 \(0\) 条或更多路径,最终停在某个城市。

\(\varphi \varphi\) 想知道,在OI国中有多少种不同的起点终点选择方案。

Format

Input

第一行包含两个整数 \(N,M\)

接下来 \(M\) 行,第 \(i + 1\) 行包含两个整数表示 \(a_i,b_i\)

Output

一行一个整数,表示方案个数​。

Samples

输入数据 1

3 3
1 2
2 3
3 2

输出数据 1

7

\((1,1),(1,2),(1,3),(2,2),(2,3),(3,2),(3,3)\) 满足题意。

输入数据 2

3 0

输出数据 2

\((1,1),(2,2),(3,3)\) 满足题意。

输入数据 3

4 4
1 2
2 3
3 4
4 1

输出数据 3

16

每一对点都可以。

Limitation

  • \(2 \le N \le 2000\)
  • \(0 \le M \le \min(2000, N(N - 1))\)
  • \(1 \le a_i,b_i \le N\)
  • \(a_i \neq b_i\)
  • \(\{a_i,b_i\}\) 互不相同

思路

简单的模拟深搜,直接 \(\text{DFS}\) 即可,不需要时间复杂度的优化。(数据太水)

代码

#include <bits/stdc++.h>
using namespace std;
const int N = 500005;
int n, m, a, b, ans;
vector<int> v[N];
bool vis[N];
void dfs(int x) {
	for (int i = 0; i < (int)v[x].size(); i++) {
		int tmp = v[x][i];
		if (vis[tmp]) continue;
		vis[tmp] = 1, ans++;
		dfs(tmp);
	}
}
int main() {
	freopen("tour.in", "r", stdin);
	freopen("tour.out", "w", stdout);
	cin >> n >> m;
	for (int i = 1; i <= m; i++) {
		cin >> a >> b;
		v[a].push_back(b);
	}
	for (int i = 1; i <= n; i++) {
		memset(vis, 0, sizeof(vis));
		vis[i] = 1, ans++;
		dfs(i);
	}
	cout << ans;
	return 0;
}

思考

对于比赛(模拟赛),特别是比较简单的,一定要多想多试,不要被题目的描述吓到,才能拿到更好的成绩。

posted @ 2024-01-07 16:13  Foiled  阅读(26)  评论(0编辑  收藏  举报