关于我想了很久才想出这题咋做这档事 - 4

#0.0 目录


目录中以下题目皆可点击查看原题

#1.0 P3420 [POI2005]SKA-Piggy Banks

#1.1 简单分析

我们可以简单看出,每一个储存罐都有一个依赖关系,如下面这张图

很显然,里面的 \(7\) 个储存罐共形成了 \(3\) 个小团体,经过合理的猜想及模拟可发现,打开一个团体总存在一种方法仅敲碎一个罐子即可打开该团体的所有罐子

简单说明

假设有罐子 \(a,b\) 存在于团体 \(T\),对于 \(a\) 有且仅有以下两种情况:

  • \(a\to a\)
  • $a\to b $

对于情况1,\(a\) 为该团体的“头”,因为 \(T\) 中罐子数量 \(> 1\),故必然存在 \(b\to a\),否则 \(a,b\) 不为同一个团体

对于情况2,对于 \(b\) 一定是该团体的“头”

如果出现了环,即 \(a\to b,b\to a\),这两个点显然可以看成一个点,且必须要敲一次。

以上两种情况,显然敲 \(1\) 次即可,那么一个有许多罐子的团体 \(Q\),一定也是由以上的关系构成,因为方向单一,且每个罐子最多有 \(1\) 出度,所以必然存在一个打开后其他的都可以打开的“头”,这里的头一定是环(类似 \(a\to a\) 的是自环),但如我们上面说的,环可以看成一个必敲的点。

假设,有一个团体至少要敲两次,就意味着它有两个环,自己画一画可以简单看出两个环是一定无法连成一个团体的

#1.2 解决思想

“团体”,“连接关系”是什么?——并查集

我们显然只需要根据输入建立并查集,然后找存在自环的点的个数。

你可能会问:为什么找自环的点?像 \(a\to b,b\to a\) 就不存在自环啊?

我们来想想,并查集是怎么连起两个团体的?是不是将代表元素相连,也就是说,在进行 \(b\to a\) 时,实际进行的是 \(b\to f(a)\),即 \(b\to b\),出现了自环

不过要注意一点:要用以上操作实现,一定要路径压缩,不仅是时间复杂度上的问题,还有程序正确性的问题,留给各位独立思考

#1.3 代码实现

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define N 1000100
using namespace std;

int n;
int f[N];

inline int get_f(int a){
    while (a != f[a])
      a = f[a] = f[f[a]];
    return a;
}

inline void unionn(int a,int b){
    int fa = get_f(a);
    int fb = get_f(b);
    f[fa] = fb;
}

int main(){
    scanf("%d",&n);
    for (int i = 1;i <= n;i ++)
      f[i] = i;
    for (int i = 1;i <= n;i ++){
        int x;
        scanf("%d",&x);
        unionn(i,x);
    }
    int cnt = 0;
    for (int i = 1;i <= n;i ++)
      if (f[i] == i)
        cnt ++;
    cout << cnt;
    return 0;
}

#2.0 P1006 [NOIP2008 提高组] 传纸条

#2.1 简单分析

数据范围是典型的四维dp,,我们只需使用类似数字三角形的操作,注意特判即可

#2.2 代码实现

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
#define N 61
using namespace std;

int n,M;
int m[N][N],f[N][N][N][N];

int main(){
    scanf("%d%d",&n,&M);
    for (int i = 1;i <= n;i ++)
      for (int j = 1;j <= M;j ++)
        scanf("%d",&m[i][j]);
    for (int i = 1;i <= n;i ++)
      for (int j = 1;j <= M;j ++)
        for (int o = 1;o <= n;o ++)
          for (int k = 1;k <= M;k ++){
              f[i][j][o][k] = max(max(f[i - 1][j][o - 1][k],f[i - 1][j][o][k - 1]),max(f[i][j - 1][o - 1][k],f[i][j - 1][o][k - 1])) + m[i][j] + m[o][k];
              if (i == o && j == k) //注意特判位置
                f[i][j][o][k] -= m[i][j];
          }
    printf("%d",f[n][M][n][M]);
    return 0;
}

更新日志及说明

更新

  • 初次完成编辑 - \(2021.2.15\)

个人主页

欢迎到以下地址支持作者!
Github戳这里
Bilibili戳这里
Luogu戳这里

posted @ 2021-02-15 14:27  Dfkuaid  阅读(68)  评论(0编辑  收藏  举报