写这类题目前,首先需要了解一下并查集是什么:并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题(即所谓的并、查)。主要由一个pre数组和find,join两个操作构成。
pre数组用于存储每个结点的上级是谁,find用于查找某个结点的上级(根结点),join用于合并两个结点。
find函数:
int find(int f) {
if (pre[f] == f || pre[f] == 0)return pre[f] = f;
else return pre[f] = find(pre[f]);
}
join函数
void join(int x,int y)
{
int fx=find(x), fy=find(y);
if(fx != fy)
pre[fx]=fy;
}
并查集的主要作用是求连通分支数(如果一个图中所有点都存在可达关系(直接或间接相连),则此图的连通分支数为1;如果此图有两大子图各自全部可达,则此图的连通分支数为2……)。
P3367 【模板】并查集
这是一道板子题,上代码:
#include
using namespace std;
//P3367 【模板】并查集
int pre[10005];
int find(int f) {
if (pre[f] == f || pre[f] == 0)return pre[f] = f;
else return pre[f] = find(pre[f]);
}
int main() {
ios::sync_with_stdio(false);
int n, m;
cin >> n >> m;
int z, x, y;
for (int i = 0; i < m; i++) {
cin >> z >> x >> y;
int f1 = find(x); //查找两个结点的根结点
int f2 = find(y);
if (z == 1) { //合并两个结点
pre[f2] = f1;
}
else { //查找两个结点是否在同一个连通块中
if (f1 == f2) {
cout << "Y\n";
}
else cout << "N\n";
}
}
return 0;
}
并查集加01背包。
使用并查集,搭配购买的云可视为一个连通块,把连通块上所有的云的价格和价值都累加到最上级的云上,最后使用01背包,注意:只需要操作每一个连通块中最上级的云。代码如下:
#include
#include
using namespace std;
const int MAX = 1e4 + 5;
int dp[MAX];
typedef struct { //存储云的价格和价值
int mom, jj;
}yun;
yun y[MAX];
int pre[MAX];
int find(int f) {
if (pre[f] == f || pre[f] == 0)return pre[f] = f;
else return pre[f] = find(pre[f]);
}
int main() {
ios::sync_with_stdio(false);
int n, m, w;
cin >> n >> m >> w;
for (int i = 1; i <= n; i++)cin >> y[i].mom >> y[i].jj;
int a, b;
for (int i = 0; i < m; i++) {
cin >> a >> b;
int f1 = find(a);
int f2 = find(b);
if (f1 != f2) {
pre[f2] = f1;
y[f1].mom += y[f2].mom;//把所有连通的云的价值的价格都累加到最上级的云上
y[f1].jj += y[f2].jj;
}
}
for (int i = 1; i <= n; i++) { //dp
if (find(i) == i) { //同一个连通块上的云只选最上级的云(避免重复)
for (int j = w; j >= y[i].mom; j--) {
dp[j] = max(dp[j], dp[j - y[i].mom] + y[i].jj);
}
}
}
cout << dp[w];
return 0;
}
题目意思就是要求我们找到与小明认识的男生和与小红认识的女生分别有多少人,然后取较小值。也就是求两个最大连通块。
建立两个并查集,分别存储男生和女生每个人的根结点,通过输入的朋友关系,构建连通分支,填充两个并查集,最后访问每一个人的根结点是否为小明和小红。
#include
using namespace std;
//P2078 朋友
#include
const int MAX = 1e4 + 5;
//两个并查集
int pre1[MAX]; //男
int pre2[MAX]; //女
int find1(int f) { //查找根结点(男
if (pre1[f] == f || pre1[f] == 0)return pre1[f] = f;
else return pre1[f] = find1(pre1[f]);
}
int find2(int f) { //(女
if (pre2[f] == f || pre2[f] == 0)return pre2[f] = f;
else return pre2[f] = find2(pre2[f]);
}
int main() {
ios::sync_with_stdio(false);
int n, m, p, q;
cin >> n >> m >> p >> q;
int man = 1, girl = 1; //小明和小红可以组成一对
int n1, n2;
for (int i = 0; i < p + q; i++) {
cin >> n1 >> n2;
if (n1 > 0) { //查找与合并男生
int f1 = find1(n1);
int f2 = find1(n2);
if (f1 != f2) {
if (f1 < f2)
pre1[f2] = f1;
else pre1[f1] = f2;
}
}
else { //女生
int f1 = find2(-n1);
int f2 = find2(-n2);
if (f1 != f2) {
if (f1 < f2)
pre2[f2] = f1;
else pre2[f1] = f2;
}
}
}
//跳过小明和小红,从2开始,累计与小明认识的男生和与小红认识的女生有多少人
for (int i = 2; i <= n; i++) {
if (find1(pre1[i]) == 1)man++;
}
for (int i = 2; i <= m; i++) {
if (find2(pre2[i]) == 1)girl++;
}
cout <
有点最小生成树的意思,把修复时间看作每条边的权值,我们需要找到一条权值之和最小的连通分支。
使用并查集构建连通分支,从权值最小的边开始,判断两个顶点是否连通,若不连通,则合并,最后再找出这一条连通分支的最大权值,这便是我们要的答案。
#include
#include
using namespace std;
int parent[1005]; //用于存储每个顶点的上级
typedef struct {
int x, y, t;
}egde; //边
egde Egde[100005]; //存储所有边
int find(int f) { //找顶点f的上级
while (parent[f] > 0)
{
f = parent[f];
}
return f;
}
bool cmp(egde a, egde b) { //比较函数,因为我们要优先找修复时间最短的路
return a.t < b.t;
}
int main() {
ios::sync_with_stdio(false);
int maxn = 0;
int n, m;
cin >> n >> m;
for (int i = 0; i < m; i++) {
cin >> Egde[i].x >> Egde[i].y >> Egde[i].t;
}
sort(Egde, Egde + m, cmp);
int flag = 0; //选取边的条数
for (int i = 0; i < m; i++) { //判断两个顶点的上级是否相等
int f1 = find(Egde[i].x);
int f2 = find(Egde[i].y);
if (f1 != f2) {
parent[f2] = f1; //不相同,任选一个顶点作为上级
flag++; //边数累加
if (Egde[i].t > maxn) { //找到连通分支中修复时间最久的路
maxn = Egde[i].t;
}
}
}
if (flag == n - 1) //判断最终是否能通路
cout << maxn;
else cout << "-1\n";
return 0;
}
什么是动态规划,我到现在还是一脸懵逼。。。
递推,,由子状态推出父状态,,由最优子得到最优父。。。
确定边界,找出初始值。
根据题意写出状态转移方程。。。
吾甚愚,多刷题。。。
这道题非常简单,小卒只能往右或下走,被马控制的地方过不了,所以我们要算一个点的路径数的话,只需要把该点的上面的点和左边的点的路径数加起来就行了(处于最左和最上的点只能从上和左边到达,需要初始化),易得dp[i][j] = dp[i - 1][j] + dp[i][j - 1]。完整代码如下:
#include
using namespace std;
//P1002 [NOIP2002 普及组] 过河卒
int f[25][25]; //存储被马控制的点
long long dp[25][25]; //
int dx[8] = { -2,1,2 ,-1,2,1,-2,-1 }; //马走的方向
int dy[8] = { 1,-2, -1,2,1,2,-1,-2 };
int main() {
ios::sync_with_stdio(false);
int n, m, x, y;
cin >> n >> m >> x >> y;
f[x][y] = 1;
for (int i = 0; i < 8; i++) { //标记马可以控制的点
int x1 = x + dx[i];
int y1 = y + dy[i];
if (x1<0 || y1<0 || x1>n || y1>m) {
continue;
}
f[x1][y1] = 1;
}
for (int i = 0; i <= n; i++) { //初始化边界
if (!f[i][0])
dp[i][0] = 1;
else break;
}
for (int i = 0; i <= m; i++){
if (!f[0][i])
dp[0][i] = 1;
else break;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (!f[i][j])
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
cout << dp[n][m];
return 0;
}
逆向求解,从r-1行开始,每一个数选择左下角与右下角中较大的数进行累加,所以f[1][1]就是最终答案。
#include
using namespace std;
//P1216 [USACO1.5][IOI1994]数字三角形 Number Triangles
#include
long long f[1005][1005];
int main() {
ios::sync_with_stdio(false);
int r;
cin >> r;
for (int i = 1; i <= r; i++) {
for (int j = 1; j <= i; j++)cin >> f[i][j];
}
for (int i = r - 1; i >= 1; i--) {
for (int j = 1; j <= i; j++) {
f[i][j] += max(f[i + 1][j], f[i + 1][j + 1]);
}
}
cout << f[1][1];
return 0;
}
首先得判断有没有足够的药,1.有:两种操作,失败或成功,判断哪个更划得来;2.没有:只能失败。注意:只要选择失败,就不用药,避免浪费。代码如下:
#include
using namespace std;
#include
long long l[1005]; //失败经验
long long w[1005]; //成功经验
long long u[1005]; //用药量
long long f[1005];
int main() {
int n, x;
cin >> n >> x;
for (int i = 1; i <= n; i++) {
cin >> l[i] >> w[i] >> u[i];
}
for (int i = 1; i <= n; i++) {
for (int j = x; j >= 0; j--) {
//有足够的药,判断输好还是赢好
if (j >= u[i])
f[j] = max(f[j] + l[i], f[j - u[i]] + w[i]);
else f[j] += l[i]; //没有就直接认输,不浪费药
}
}
cout << f[x] * 5;
return 0;
}
动态规划这块本周学得很不理想,题目换个招式,我就不会了,后面还需抽时间补补,接着刷题接着蒙。。。
本周完结!!!