2024年ACM 竞赛班(七:动态规划)

A:超级楼梯

题意:上楼梯,每次上一阶或两阶,问有多少种方案走到第n阶

思路:用dp[i]表示走到第 i 阶楼梯时有多少种走法,状态转移方程为dp[i]=dp[i-1]+dp[i-2]dp[n]即为所求

时间复杂度:O(n)

int n;
int dp[N];

void solve() {
	cin >> n;
	fill(dp, dp + n + 1, 0);
	dp[1] = 1;
	for (int i = 2; i <= n; i++) dp[i] = dp[i - 1] + dp[i - 2];
	cout << dp[n] << '\n';
}

B:最长上升子序列

题意:求原序列单调递增子序列的最大长度

思路:用dp[i]表示以i结尾的上升子序列的最大长度,状态转移方程为dp[i]=min(dp[j] + 1), (a[j]<a[i]),结果为max(dp[i])

时间复杂度:O(n^{2})

int n;
int a[N], dp[N];

void solve() {
    cin >> n;
    for (int i = 1; i <= n; i++) cin >> a[i];
    for (int i = 1; i <= n; i++) {
        dp[i] = 1;
        for (int j = 1; j < i; j++) {
            if (a[j] < a[i]) dp[i] = max(dp[i], dp[j] + 1);
        }
    }
    int ans = 0;
    for (int i = 1; i <= n; i++) ans = max(ans, dp[i]);
    cout << ans << '\n';
}

C:骨头收藏家

题意:不同的骨头有不同的体积和价值。这个收藏家有一个体积为 V 的背包,请计算他可以收藏的最大价值。

思路:01背包问题,用dp[i][j]表示只选前 i 个物品,体积为 j 时可收藏的最大价值, 状态转移方程为dp[i][j]=max(dp[i-1][j], dp[i-1][j-v[i]]+w[i]),可以用滚动数组优化为dp[j]=max(dp[j], dp[j-v[i]]+w[i]),最终dp[m]即为所求

时间复杂度:O(nm)

int n, m;
int w[N], v[N], dp[N];

void solve() {
	cin >> n >> m;
	for (int i = 1; i <= n; i++) cin >> w[i];
	for (int i = 1; i <= n; i++) cin >> v[i];
	fill(dp, dp + n + 1, 0);
	for (int i = 1; i <= n; i++) {
		for (int j = m; j >= v[i]; j--) {
			dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
		}
	}
	cout << dp[m] << '\n';
}

D:完全背包问题

题意:有n种重量和价值分别为w[i], v[i]的物品。从这些物品中挑选总重量不超过 W 的物品,求出挑选物品价值总和的最大值。在这里,每种物品可以挑选任意多件

思路:用dp[i][j]表示只选前 i 个物品,体积为 j 时可挑选的最大价值,状态转移方程为dp[i][j]=max(dp[i-1][j], dp[i-1][j-k*v[i]]+k*w[i]),同理可用滚动数组优化得dp[j]=max(dp[j], dp[j-k*v[i]]+k*w[i]),观察dp[j], dp[j-v[i]]两项可发现dp[j]=max(dp[j], dp[j-v[i]]+w[i]),最终dp[m]即为所求

时间复杂度:O(nm)

int n, m;
int w[N], v[N], dp[N];

void solve() {
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> v[i] >> w[i];
	cin >> m;
	fill(dp, dp + n + 1, 0);
	for (int i = 1; i <= n; i++) {
		for (int j = v[i]; j <= m; j++) {
			dp[j] = max(dp[j], dp[j - v[i]] + w[i]);
		}
	}
	cout << dp[m] << '\n';
}

E:最短Hamilton路径

题意:给定一张无向图,求起点 0 到终点 n-1 的最短Hamilton路径,Hamilton路径的定义是从 0 到 n-1 不重不漏地经过每个点恰好一次。

思路:用dp[state][j]表示已经经过的点的集合为state,且最后位于点 j 的路径的最小值,则状态转移方程为dp[state+j][j]=min(dp[state+j][k], dp[state][k]+a[j][k]),其中state可以考虑用状态压缩表示为二进制数, state二进制第 i 位为1时表示已经经过第 i 个点,为0表示未经过第 i 个点,即dp[i | (1 << j)][j]=min(dp[i | (1 << j)][j], dp[i][k]+a[j][k]),最终dp[(1<<n)-1][n-1]即为所求

时间复杂度:O(n^{2}2^{n})

int n, m;
int a[N][N];
int dp[1 << 21][21];

void solve() {
	cin >> n;
	for (int i = 0; i < n; i++) {
		for (int j = 0; j < n; j++) {
			cin >> a[i][j];
		}
	}
	memset(dp, 0x3f, sizeof dp);
	dp[1][0] = 0;
	for (int i = 1; i < 1 << n; i++) {
		for (int j = 0; j < n; j++) {
			if (i & (1 << j) == 0) {
				for (int k = 0; k < n; k++) {
					if (i >> k & 1) {
						dp[i | (1 << j)][j] = min(dp[i | (1 << j)][j], dp[i][k] + a[j][k]);
					}
				}
			}
		}
	}
	cout << dp[(1 << n) - 1][n - 1] << '\n';
}

F:石子合并

题意:有n堆石子,每次将相邻的两堆合成一堆,新的一堆石子数为合并的体力花费,求将n堆石子合并成一堆的最小花费

思路:用dp[i][j]表示将区间[i, j]内的石子合成一堆所需的最小花费,则状态转移方程为dp[i][j]=min(dp[i][k]+dp[k+1][j]+sum[j]-sum[i - 1]),最终dp[1][n]即为所求

时间复杂度:O(n^{2})

int n;
int a[N], sa[N];
int dp[N][N];

int find(int l, int r) {
	if (l == r) return 0;
	if (dp[l][r]) return dp[l][r];
	dp[l][r] = 1e9;
	for (int k = l; k < r; k++) {
		dp[l][r] = min(dp[l][r], find(l, k) + find(k + 1, r) + sa[r] - sa[l - 1]);
	}
	return dp[l][r];
}

void solve() {
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> a[i], sa[i] = sa[i - 1] + a[i];
	cout << find(1, n) << '\n';
}

G:没有上司的舞会

题意:一所大学,职员的关系就像一棵以校长为根的树,每个职员有一个快乐指数。现在要召开舞会,没有职员愿意和直接上司一起参会,求所有参会职员的快乐指数总和最大值

思路:用dp[u][0]表示在 u 未参加舞会时,以 u 为根的子树的快乐指数总和的最大值,用dp[u][1]表示在 u 参加舞会时,以 u 为根的子树的快乐指数总和的最大值,则状态转移方程为dp[u][0]+=max(dp[v][0], dp[v][1])dp[u][1] += dp[v][0],其中 v 是 u 的子节点,最终结果为max(dp[rt][0], dp[rt][1])

时间复杂度:O(n)

struct node {
	int fa, ha;
	vector<int> adj;
}tr[N];
int dp[N][2];

void solve() {
	int n;
	cin >> n;
	for (int i = 1; i <= n; i++) cin >> tr[i].ha, tr[i].fa = -1;
	for (int i = 1; i < n; i++) {
		int u, v; cin >> u >> v;
		tr[v].adj.push_back(u);
		tr[u].fa = v;
	}
	int rt;
	for (int i = 1; i <= n; i++) if (tr[i].fa == -1) rt = i;
	function<void(int)> dfs = [&](int u) {
		dp[u][1] = tr[u].ha;
		for (auto v : tr[u].adj) {
			dfs(v);
			dp[u][0] += max(dp[v][0], dp[v][1]);
			dp[u][1] += dp[v][0];
		}
	};
	dfs(rt);
	cout << max(dp[rt][0], dp[rt][1]) << '\n';
}

H:到处都是0

题意:求1到N之间恰好包含K个非0数字的整数的数目

思路:用dp[i][j][k]表示一共有 i 位,且最高位是 j ,一共有 k 位非零的可能方案数,则状态转移方程为dp[i][k][l + (k != 0)] += dp[i][j][l],用cnt表示已经出现过的非零位置的个数,考虑最高的k个不为零的位置,对于位置 i ,分别枚举该位置可能出现的数,dp[s.size() - 1- i][j][k-cnt]即为对应位置为 j 时的总方案数,求和即为最终答案

时间复杂度:O(100nk)

string s;
int k;
int dp[N][20][10];

void solve() {
	for (int j = 0; j < 10; j++) dp[0][j][!!j] = 1;
	for (int i = 0; i <= 100; i++) {
		for (int j = 0; j < 10; j++) {
			for (int k = 0; k < 10; k++) {
				for (int l = 0; l < 4; l++) {
					dp[i + 1][k][l + (!!k)] += dp[i][j][l];
				}
			}
		}
	}
	cin >> s >> k;
	int cnt = 0, ans = 0;
	for (int i = 0; i < s.size(); i++) {
		for (int j = 0; j < s[i] - '0'; j++) ans += dp[s.size() - 1 - i][j][k - cnt];
		if (s[i] - '0') {
			if (++cnt > k) break;
		}
		if (i == s.size() - 1 && cnt == k) ans++;
	}
	cout << ans << '\n';
}

I:绿豆蛙的归宿

题意:给一个有向无环连通图,求从结点1走到结点n的所经过的路径总长度期望是多少,从每一个顶点离开时走每条路的概率相同

思路:概率dp,用dp[u]表示走到结点 u 的概率,则对于结点 u 和其子节点 v ,经过结点 v 的概率应该加上经过结点 u 的概率乘u的儿子个数的倒数,对应的期望应该增加这个概率乘边权,最终将所有点的期望相加即可。

时间复杂度:O(n+m)

int n, m;
double dp[N], e[N];

void solve() {
	cin >> n >> m;
	vector<vector<pair<int, int>>> ad(n + 1);
	for (int i = 1; i <= m; i++) {
		int a, b, c; cin >> a >> b >> c;
		ad[a].push_back({b, c});
	}
	//bfs
	queue<int> q;
	q.push(1);
	dp[1] = 1;
	vector<int> vis(n + 1, 0);
	while (q.size()) {
		auto u = q.front(); q.pop();

		if (vis[u]) continue;
		else vis[u] = 1;

		for (auto [v, len] : ad[u]) {
			double p = dp[u] * (1.0 / ad[u].size());
			dp[v] += p;
			e[v] += p * len;
			q.push(v);
		}
	}
	double res = 0;
	for (int i = 1; i <= n; i++) res += e[i];
	printf("%.2lf\n", res);
}

J:Fibonacci

题意:求斐波那契数列第n项mod1e4的值

思路:将斐波那契数列的递推公式写成矩阵形式,\begin{bmatrix} F_{n-1} &F_{n-2} \end{bmatrix} \begin{bmatrix} 1 &1 \\ 1 & 0 \end{bmatrix} = \begin{bmatrix} F_{n} & F_{n-1} \end{bmatrix},那么F_{n}就是\begin{bmatrix} 1 &1 \end{bmatrix} \begin{bmatrix} 1 &1 \\ 1 & 0 \end{bmatrix}^{n-2}第一行第一列的元素,可以用矩阵快速幂求解。

时间复杂度:O(logn)

struct mar {
	int a[3][3];
	mar operator * (const mar &s) const {
		mar res;
		memset(res.a, 0, sizeof res.a);
		for (int i = 1; i <= 2; i++) {
			for (int j = 1; j <= 2; j++) {
				for (int k = 1; k <= 2; k++) {
					res.a[i][j] = (res.a[i][j] + a[i][k] * s.a[k][j]) % mod;
				}
			}
		}
		return res;
	}
}ans, base;
int n;

void init() {
	base.a[1][1] = base.a[1][2] = base.a[2][1] = 1, base.a[2][2] = 0;
	ans.a[1][1] = ans.a[1][2] = 1, ans.a[2][1] = ans.a[2][2] = 0;
}

void qpow(int b) {
	while (b) {
		if (b & 1) ans = ans * base;
		base = base * base;
		b >>= 1;
	}
}

void solve() {
	while (cin >> n, n != -1) {
		if (n < 3) cout << !!n << '\n';
		else {
			init();
			qpow(n - 2);
			cout << ans.a[1][1] % mod << '\n';
		}
	}
}
  • 20
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ACM/ICPC(ACM International Collegiate Programming Contest, 国际大学生程序设计竞赛)是由国际计算机界历史悠久、颇具权威性的组织ACM(Association for Computing Machinery,国际计算机协会)主办的,世界上公认的规模最大、水平最高的国际大学生程序设计竞赛,其目的旨在使大学生运用计算机来充分展示自己分析问题和解决问题的能力。该项竞赛从1970举办至今已历29届,一直受到国际各知名大学的重视,并受到全世界各著名计算机公司的高度关注,在过去十几中,APPLE、AT&T、MICROSOFT和IBM等世界著名信息企业分别担任了竞赛的赞助商。可以说,ACM国际大学生程序设计竞赛已成为世界各国大学生最具影响力的国际级计算机类的赛事,是广大爱好计算机编程的大学生展示才华的舞台,是著名大学计算机教育成果的直接体现,是信息企业与世界顶尖计算机人才对话的最好机会。   该项竞赛分区域预赛和国际决赛两个阶段进行,各预赛区第一名自动获得参加世界决赛的资格,世界决赛安排在每的3~4月举行,而区域预赛安排在上一的9~12月在各大洲举行。   ACM/ICPC的区域预赛是规模很大、范围很广的赛事。仅在2003参加区域预赛的队伍就有来自75个国家(地区),1411所大学的3150支代表队,他们分别在127个赛场中进行比赛,以争夺全球总决赛的73个名额,其激烈程度可想而知。 2005第30届ACM/ICPC亚洲赛区预赛共设了北京、成都、汉城、东京等11个赛站,来自亚洲各国知名高校的各个代表队进行了激烈的角逐。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值