W
e
l
c
o
m
e
: )

[考试记录] 2024.5.19

T1

题目描述

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

思路

看到求方案数直接想到dp,接着思考对于一块 \((n,m)\) 的蛋糕,一刀可以切成 \((n-k,m)\)\((k,m)\) 或者是 \((n,m-k)\)\((n,k)\)。根据乘法原理,切nm的蛋糕方案数即为所有切分后的两块蛋糕方案数的积的和。由此可以推出转移方程。

\[\begin{split} dp[i][j]&=\sum_\limits{k=1}^{i-1}dp[i-k][j]*dp[k][j]+\\ &=\sum_\limits{k=1}^{j-1}dp[i][j-k]*dp[i][k] \end{split} \]

又因为这个蛋糕是对称的,也就是 \(dp[i][j]=dp[j][i]\),可以小小优化一下。

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n, m, dp[310][310], mod = 1e9 + 7;
inline int DP(int a, int b){
	if(dp[a][b]) return (dp[b][a] = dp[a][b]);
	if(dp[b][a]) return (dp[a][b] = dp[b][a]);
	for(int i=1; i<a; ++i) dp[a][b] = (__int128)(dp[a][b] + DP(a-i, b) * DP(i, b)) % mod;
	for(int i=1; i<b; ++i) dp[a][b] = (__int128)(dp[a][b] + DP(a, b-i) * DP(a, i)) % mod;
	return (dp[b][a] = dp[a][b]);
}
signed main(){
	freopen("cake.in", "r", stdin);
	freopen("cake.out", "w", stdout);
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	dp[1][2] = dp[2][1] = dp[1][1] = 1;
	cin>>n>>m;
	return cout<<DP(n, m), 0;
}

T2

题目描述

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

INPUT

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

接下来n行每行三个整数ai,bi,ci,分别表示第i种纸币的面值,小X拥有的该种纸币数量,店员拥有的该种纸币数量,保证面值ai单调增加。

思路

看到方案数直接想到dp,但此题需要用单调队列优化,所以先咕着。

T3

题目描述

φφ打算去旅行。OI国有n座城市,编号1,N和
M条单向路径。其中第i条路径连接城市ai,bi,表示你可以从ai走向bi,但不能从bi走向ai。φφ计划从某个城市出发,经过0条或更多路径,最终停在某个城市。φφ想知道,在OI国中有多少种不同的起点终点选择方案。

思路

法一

暴力建图并搜索

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int n, m; ll ans;
vector<int> G[2001];
bitset<2001> vis;
inline void dfs(int k){
	++ans; vis[k] = 1;
	for(int v : G[k]) if(!vis[v]) dfs(v);
}
signed main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n>>m;
	for(int i=1, a, b; i<=m; ++i) cin>>a>>b, G[a].push_back(b);
	for(int i=1; i<=n; ++i) vis.reset(), dfs(i);
	return cout<<ans, 0;
}

法二

考虑到存在大强连通分量,使用法一会产生无用搜索,所以使用tarjan缩点后再搜索。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 2005;
int n, m, low[N], dfn[N], scc[N], cnt, tot, p[N], sn[N], num[N];
ll ans;
vector<int> G[N], sG[N];
bitset<N> vis;
inline void tarjan(int k){
	vis[k] = 1;
	dfn[k] = low[k] = ++cnt;
	p[++p[0]] = k;
	for(int v : G[k]){
		if(!dfn[v]){
			tarjan(v);
			low[k] = min(low[k], low[v]);
		}else if(vis[v]) low[k] = min(low[k], dfn[v]);
	}
	if(dfn[k] == low[k]){
		int v; ++tot;
		do{ v = p[p[0]--];
			scc[v] = tot;
			++num[tot];
			vis[v] = 0;
		}while(v != k);
	} return;
}
inline void dfs(int k, ll sum){
	vis[k] = 1;
	ans += sum * num[k];
	for(int v : sG[k]) if(!vis[v]) dfs(v, sum);
}
int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n>>m;
	for(int i=1, a, b; i<=m; ++i) cin>>a>>b, G[a].push_back(b);
	for(int i=1; i<=n; ++i) if(!dfn[i]) tarjan(i);
	for(int i=1; i<=n; ++i) for(int v : G[i])
		sG[scc[i]].push_back(scc[v]);
	for(int i=1; i<=tot; ++i) vis.reset(), dfs(i, num[i]);
	return cout<<ans, 0;
}
posted @ 2024-05-21 17:32  XiaoLe_MC  阅读(11)  评论(0编辑  收藏  举报