【洛谷】P1111 修复公路(学习记录)

今天主要是解决题目。重点的问题就是修复公路,将里面要用到的关键代码部分和思路总结一下。

题目如下:

这道题的整体思路是利用并查集。我一开始写题时审题有问题,以为要用到并查集和动态规划,后面看题解发现了正确的思路。

这道题,注意一个地方,t 的含义是什么?是指在 t 时能够修复完某条公路,而不是指修复某条公路需要花 t 个小时。

那么基于这点,得到一个求得最早修好一条连接所有村庄的公路的方法---->按照每条公路要修完要到 t 时的时间大小排序,升序排列。(排列的是结构体,为了方便操作,创建一个结构体)

以下是结构体组成:

struct new {
	int x;
	int y;
	int t;
}load[100001];

然后是一个升序排列结构体,用到了qsort函数

(这个函数我还没学透,指个路复习:【C语言】qsort()函数详解:能给万物排序的神奇函数-优快云博客

下面是排列结构体的代码:

int compe(const void* e, const void* f) {

	struct new* road1 = (struct new*)e;
	struct new* road2 = (struct new*)f;

	return road1->t - road2->t;
}

//这里要注意,比较方法是要自己写的,qsort会按你比较后返回的值进行排序处理
qsort(load+1, M, sizeof(struct new), compe);

然后我们按顺序修路就好,将路的根节点都指向同一个就代表修好了,每次修完一条路都要判断是否已经成功修完一条连接所有村庄的路了。

修路就用了经典的并查集,利用一个combine函数让根节点一致,find函数查找根节点。

判断部分用了一个test函数,这里我目前知道有两种判断方法。

一种是找一个路的根节点为样例,for循环一下看看是不是所有根节点都和样例一样,如果有不同,就代表还没全部连通。

第二种是,将pre[ ]传入test函数,如果有pre[ i ] = i的情况就代表找到了一个根节点,记录一下,num++,但是num一旦大于1,那就说明根节点不止一个了,那就说明路还没通全部村庄。

(代码中也有比较详细的注释,记录一下,方便复习理解)

以下是ac代码:

#include<stdio.h>
#include<stdlib.h>

int pre[1001];
int num = 0;
int N = 0;
int M = 0;

struct new {
	int x;
	int y;
	int t;
}load[100001];

//下面两个函数是基本的并查集模型
int find(int a) {
	if (pre[a] == a) {
		return a;
	}
	return pre[a] = find(pre[a]);
}

void combine(int x, int y) {
	int fx = find(x);
	int fy = find(y);
	if (fx != fy) {
		pre[fx] = y;
		
	}
}

//检查是否每个村庄都已经连通
//这里其实有两种方法,第一种如下,判断是否只有一种根节点就是自己本身的情况。
// 第二种是举例一个根节点,看其他的pre[i]的根节点是不是都与其一致,有不一致的就代表还没连通

int test(int pre[]) {
	int num = 0;
	for (int i = 1; i <= N; i++) {

		//代表只有一个根节点
		if (pre[i] == i) {
			num++;
		}
		if (num == 2) {
			return 0;
		}
	}
	return 1;
}



//比较结构体(要学会使用qsort函数,各种数据类型的比较函数怎么写要清楚)
int compe(const void* e, const void* f) {

	struct new* road1 = (struct new*)e;
	struct new* road2 = (struct new*)f;

	return road1->t - road2->t;
}

int main() {
	scanf("%d%d", &N, &M);
	
	for (int i = 1; i <= N; i++) {
		
		pre[i] = i;
	}
	for (int i = 1; i <= M; i++) {
		scanf("%d%d%d", &load[i].x, &load[i].y, &load[i].t);
		
	}
	qsort(load+1, M, sizeof(struct new), compe);
	for (int i = 1; i <= M; i++) {
		//只有当路的两端都指向同一个村庄,才代表通路
		combine(load[i].x, load[i].y);
		if (test(pre)) {
			printf("%d", load[i].t);
			return 0;
		}
	}
	printf("-1");
	return 0;
}

03-21
### 关于动态规划 (Dynamic Programming, DP) 的解决方案 在解决洛谷平台上的编程问题时,尤其是涉及动态规划的题目,可以采用以下方法来构建解决方案: #### 动态规划的核心思想 动态规划是一种通过把原问题分解为相对简单的子问题的方式来求解复杂问题的方法。其核心在于存储重复计算的结果以减少冗余运算。通常情况下,动态规划适用于具有重叠子问题和最优子结构性质的问题。 对于动态规划问题,常见的思路包括定义状态、转移方程以及边界条件的设计[^1]。 --- #### 题目分析与实现案例 ##### **P1421 小玉买文具** 此题是一个典型的简单模拟问题,可以通过循环结构轻松完成。以下是该问题的一个可能实现方式: ```cpp #include <iostream> using namespace std; int main() { int n; cin >> n; // 输入购买数量n double p, m, c; cin >> p >> m >> c; // 输入单价p,总金额m,优惠券c // 计算总价并判断是否满足条件 if ((double)n * p <= m && (double)(n - 1) * p >= c) { cout << "Yes"; } else { cout << "No"; } return 0; } ``` 上述代码实现了基本逻辑:先读取输入数据,再根据给定约束条件进行验证,并输出最终结果[^2]。 --- ##### **UOJ104 序列分割** 这是一道经典的区间动态规划问题。我们需要设计一个二维数组 `f[i][j]` 表示前 i 次操作后得到的最大价值,其中 j 是最后一次切割的位置。具体实现如下所示: ```cpp #include <bits/stdc++.h> using namespace std; const int MAXN = 5e3 + 5; long long f[MAXN], sumv[MAXN]; int a[MAXN]; int main(){ ios::sync_with_stdio(false); cin.tie(0); int n,k; cin>>n>>k; for(int i=1;i<=n;i++)cin>>a[i]; for(int i=1;i<=n;i++)sumv[i]=sumv[i-1]+a[i]; memset(f,-0x3f,sizeof(f)); f[0]=0; for(int t=1;t<=k;t++){ vector<long long> g(n+1,LLONG_MIN); for(int l=t;l<=n;l++)g[l]=max(g[l-1],f[t-1][l-1]); for(int r=t;r<=n;r++)f[r]=max(f[r],g[r]+sumv[r]*t); } cout<<f[n]<<'\n'; return 0; } ``` 这段程序利用了滚动数组优化空间复杂度,同时保持时间效率不变[^3]。 --- ##### **其他常见问题** 针对更复杂的路径覆盖类问题(如 PXXXX),我们往往需要结合一维或多维动态规划模型加以处理。例如,在某些场景下,我们可以设定 dp 数组记录到达某一点所需最小代价或者最大收益等指标[^4]。 --- ### 总结 以上展示了如何运用动态规划技巧去应对不同类型的算法挑战。无论是基础还是高级应用场合,合理选取合适的数据结构配合清晰的状态转换关系都是成功解决问题的关键所在。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值