蓝桥杯--图及其它(二)

例5【油漆面积】(线段树)

X星球的一批考古机器人正在一片废墟上考古。
该区域的地面坚硬如石、平整如镜。
管理人员为方便,建立了标准的直角坐标系。

每个机器人都各有特长、身怀绝技。它们感兴趣的内容也不相同。
经过各种测量,每个机器人都会报告一个或多个矩形区域,作为优先考古的区域。

矩形的表示格式为(x1,y1,x2,y2),代表矩形的两个对角点坐标。

为了醒目,总部要求对所有机器人选中的矩形区域涂黄色油漆。
小明并不需要当油漆工,只是他需要计算一下,一共要耗费多少油漆。

其实这也不难,只要算出所有矩形覆盖的区域一共有多大面积就可以了。
注意,各个矩形间可能重叠。

本题的输入为若干矩形,要求输出其覆盖的总面积。

输入格式:
第一行,一个整数n,表示有多少个矩形(1<=n<10000)
接下来的n行,每行有4个整数x1 y1 x2 y2,空格分开,表示矩形的两个对角顶点坐标。
(0<= x1,y1,x2,y2 <=10000)

输出格式:
一行一个整数,表示矩形覆盖的总面积。

例如,
输入:
3
1 5 10 10
3 1 20 20
2 7 15 17

程序应该输出:
340

再例如,
输入:
3
5 2 10 6
2 7 12 10
8 1 15 15

程序应该输出:
128

方法1:暴力法,假设整个范围在1000以内,可以在1000 x 1000 的范围每块测试在不在矩形中,sum累计(效率低)

方法2:x排序,切分,每个条形累计面积;y的有效长度转化成线段树问题

线段树问题:如图,将3-6进行拆分

class Comp implements Comparator {
    public int compare(Object a,Object b){
        int []aa = (int [])a;
        int []bb = (int [])b;
        return aa[2]-bb[2];
    }
}

public class Main {
    static int solve(int[][]data){
        List list = new Vector();
        for(int i=0;i<data.length;i++){
            //取出坐标
            int x1 = data[i][0];
            int y1 = data[i][1];
            int x2 = data[i][2];
            int y2 = data[i][3];

            if(x1==x2 || y1==y2) continue;
            if(x1>x2){ int t=x1;x1=x2;x2=t;}//规范化
            if(y1>y2){ int t=y1;y1=y2;y2=t;}

            int[]a = new int[4];
            a[0] = y1;
            a[1] = y2;//y值跨度
            a[2] = x1;
            a[3] = 1;//起始边

            int[]b = new int[4];
            b[0] = y1;
            b[1] = y2;
            b[2] = x2;
            b[3] = 2;//结束边

            list.add(a);
            list.add(b);
        }
        Collections.sort(list,new Comp());//对x轴方向进行排序,list中存着沿y轴的一条条边
        return work(list);
    }

    static int work(List list){//核心方法  int[]0 1 2 3 ==>y1,y2,x,开始边1or结束边2
        if(list.size()==0)
            return 0;
        //线段树是满二叉树,所有节点数可事先计算
        final int N = 1024*16;//比10000大的最小的2次幂
        int [][] tr = new int[N*2][2];//自身覆盖数,子孙覆盖总宽度

        int []a = (int[])list.get(0);
        int from = a[2];
        add(tr,0,0,N,a[0],a[1]);

        int sum = 0;
        for(int i=0;i<list.size();i++){
            int []it=(int[])list.get(i) ;
            if(it[2]>from){
                sum += tr[0][1] * (it[2]-from);
                from = it[2];
            }
            if(it[3]==1)
                add(tr,0,0,N,it[0],it[1]);
            else
                del(tr,0,0,N,it[0],it[1]);
        }
        return sum;
    }

    //tr:堆  idx:索引位置  a、b:跨度  y1、y2:
    static void add(int[][]tr,int idx,int a,int b,int y1,int y2){//线段树操作
        if(a==y1 && b==y2){//检验是否和当前结点匹配
            tr[idx][0]++;//自身覆盖次数
            if(tr[idx][0]==1)
                cry(tr,(idx-1)/2,(b-a)-tr[idx][1]);//向父结点报告
            return;
        }
        //必要时切割后递归
        int h = (a+b)/2;
        if(y2<=h){
            add(tr,idx*2+1,a,h,y1,y2);
            return;
        }
        if(y1>=h){
            add(tr,idx*2+2,h,b,y1,y2);
            return;
        }
        add(tr,idx*2+1,a,h,y1,h);
        add(tr,idx*2+2,h,b,h,y2);
    }

    static void cry(int[][]tr,int idx,int delta){
        if(delta==0) return;
        tr[idx][1] +=delta;
        if(tr[idx][0]==0 && idx>0)
            cry(tr,(idx-1)/2,delta);
    }

    static void del(int[][]tr,int idx,int a,int b,int y1,int y2){
        if(a==y1 && b==y2 && tr[idx][0]>0){
            tr[idx][0]--;
            if(tr[idx][0]==0)
                cry(tr,(idx-1)/2,tr[idx][1]-(b-a));
            return;
        }
        //必要时切割后递归
        int h = (a+b)/2;
        if(y2<=h){
            del(tr,idx*2+1,a,h,y1,y2);
            return;
        }
        if(y1>=h){
            del(tr,idx*2+2,h,b,y1,y2);
            return;
        }
        add(tr,idx*2+1,a,h,y1,h);
        add(tr,idx*2+2,h,b,h,y2);
    }

    public static void main(String[] args) {
      Scanner sc = new Scanner(System.in);
      int n = Integer.parseInt(sc.nextLine().trim());
      int [][]data = new int[n][4];
      for(int i=0;i<n;i++){
          String []ss = sc.nextLine().trim().split(" +");
          data[i] = new int[4];
          for(int j=0;j<4;j++){
              data[i][j] = Integer.parseInt(ss[j]);
          }
      }
        System.out.println(solve(data));
    }
}

例6【合根植物】(并查集)

w星球的一个种植园,被分成 m * n 个小格子(东西方向m行,南北方向n列)。每个格子里种了一株合根植物。
这种植物有个特点,它的根可能会沿着南北或东西方向伸展,从而与另一个格子的植物合成为一体。


如果我们告诉你哪些小格子间出现了连根现象,你能说出这个园中一共有多少株合根植物吗?

输入格式:
第一行,两个整数m,n,用空格分开,表示格子的行数、列数(1<m,n<1000)。
接下来一行,一个整数k,表示下面还有k行数据(0<k<100000)
接下来k行,第行两个整数a,b,表示编号为a的小格子和编号为b的小格子合根了。

格子的编号一行一行,从上到下,从左到右编号。
比如:5 * 4 的小格子,编号:
1  2  3  4
5  6  7  8
9  10 11 12
13 14 15 16
17 18 19 20

样例输入:
5 4
16
2 3
1 5
5 9
4 8
7 8
9 10
10 11
11 12
10 14
12 16
14 18
17 18
15 19
19 20
9 13
13 17


样例输出:
5

解析:一个图中,因为连通性分成多个集团,快速求两个节点间的连通性:dfs建生成树,从没访问的点中继续建生成树,将是否连通问题转化为生成树根结点是否相同。在查询的过程中,不断地改变父节点(并查集),使得纵深的树变成了扁平的树,,从而提高效率

public class Main {
    static int []tb;
    static int num = 0;

    static void f(int a,int b){
        int ra = find(a);
        int rb = find(b);
        if(ra==rb) return;
        tb[ra]=rb;//树合并
        num++;
    }

    static int find(int a){
        if(tb[a]==0)
            return a;
        return tb[a] = find(tb[a]);//重新指定自己的祖先!!!变为扁平的树
    }

    public static void main(String[] args) {
      Scanner sc = new Scanner(System.in);
      String [] ss = sc.nextLine().trim().split(" +");

      int M = Integer.parseInt(ss[0]);
      int N = Integer.parseInt(ss[1]);

      tb = new int [M*N+1];//标号从1开始

      int k =Integer.parseInt((sc.nextLine().trim()));
      for(int i=0;i<k;i++){
          ss = sc.nextLine().trim().split(" +");
          f(Integer.parseInt(ss[0]),Integer.parseInt(ss[1]));
      }
        System.out.println(M*N-num);
    }
}

例7【青蛙跳杯子】

X星球的流行宠物是青蛙,一般有两种颜色:白色和黑色。
X星球的居民喜欢把它们放在一排茶杯里,这样可以观察它们跳来跳去。
如下图,有一排杯子,左边的一个是空着的,右边的杯子,每个里边有一只青蛙。

*WWWBBB

  其中,W字母表示白色青蛙,B表示黑色青蛙,*表示空杯子。

  X星的青蛙很有些癖好,它们只做3个动作之一:
  1. 跳到相邻的空杯子里。
  2. 隔着1只其它的青蛙(随便什么颜色)跳到空杯子里。
  3. 隔着2只其它的青蛙(随便什么颜色)跳到空杯子里。

  对于上图的局面,只要1步,就可跳成下图局面:

WWW*BBB

本题的任务就是已知初始局面,询问至少需要几步,才能跳成另一个目标局面。

输入为2行,2个串,表示初始局面和目标局面。
输出要求为一个整数,表示至少需要多少步的青蛙跳。

例如:
输入:
*WWBB
WWBB*

则程序应该输出:
2

再例如,
输入:
WWW*BBB
BBB*WWW

则程序应该输出:
10

我们约定,输入的串的长度不超过15

----------------------------

笨笨有话说:
    我梦见自己是一棵大树,
    青蛙跳跃,
    我就发出新的枝条,
    春风拂动那第 5 层的新枝,
    哦,我已是枝繁叶茂。

解析:与分酒问题类似

public class Main {
    static Set jump(String s){
        Set<String> set = new HashSet();
        char [] c = s.toCharArray();
        for(int i=0;i<c.length;i++)
            if(c[i]=='*')
                for(int j=-3;j<4;j++){
                    if(j==0) continue;
                    if(i+j>=0 && i+j<c.length && c[i+j]!='*'){
                        try{
                            c[i] = c[i+j];c[i+j] = '*';set.add(new String(c));//试探
                        }
                        finally{
                            c[i+j] = c[i];c[i] = '*';//回溯
                        }
                    }
                }
        return set;
    }

    static int f(Set<String> begin,Set<String> his,String end){
        if(begin.contains(end))
            return 0;
        Set<String> ns = new HashSet();
        for(String s:begin){
            ns.addAll(jump(s));
        }
        ns.removeAll(his);
        if(ns.isEmpty()) return -1;
        his.addAll(ns);
        int r = f(ns,his,end);
        if(r<0) return -1;
        return r+1;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String begin = sc.nextLine().trim();
        String end = sc.nextLine().trim();
        Set<String> set = new HashSet();
        Set<String> his = new HashSet();
        set.add(begin);
        his.addAll(set);
        System.out.println(f(set,his,end));
    }
}

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值