牛客小白月赛D~F

文章讨论了如何在递归算法中优化空间使用,避免数组开辟过多空间,以及处理区间和问题,如计算满足条件的区间和、区间加值操作,并提到使用差分和前缀和等技巧来简化计算。
摘要由CSDN通过智能技术生成

题目链接


D.剪纸游戏

题目大意

  • 找图案中,存在多少个标准方形

解题思路

  • 对于一个未访问的点,递归找它所能到点,记录点个数cntmin\ i,j=(mii,mij)\ \ max\ i,j=(mxi,mxj)
  • cnt==(mxi-mii+1)*(mxj-mij+1),ans++

注意 

  • 递归中能不定义数组就不定义,因为递归只有在返回时会回收,所以相当于每层都开辟一次空间,空间会出问题
  • 递归中调用数组同样耗费时间,像探查方向这类的简短操作,直接分着写要比for合着遍历数组写要快很多

 

import java.io.*;
import java.util.Scanner;



public class Main{
    static int n;
    static int m;
	static char[][] map;
    static boolean[][] us;
    static int mxi,mxj,mii,mij,cnt;
	public static void main(String[] args) throws IOException{
		Scanner input=new Scanner(System.in);
        n=input.nextInt();
        m=input.nextInt();
        map=new char[n][m];
	    for(int i=0;i<n;++i) {
	    	
	    	map[i]=input.next().toCharArray();
	    }
		int ans=0;
		us=new boolean[n][m];

		for(int i=0;i<n;++i) {
			for(int j=0;j<m;++j) {
				if(map[i][j]=='.'&&us[i][j]==false) {
					mii=1001;mij=1001;cnt=0;
                    mxi=0;mxj=0;
					getFang(i, j);
                    int num=(mxi-mii+1)*(mxj-mij+1);
					if(num==cnt)ans++;
				}
			}
		}
		System.out.println(ans);

	}
//     static int[] nxti= {1,0,-1,0};
//     static int[] nxtj= {0,1,0,-1};//移到递归里试试?
	static void getFang(int x,int y) {
        if(x<0||y<0||x>=n||y>=m||us[x][y]||map[x][y]!='.')return;
        us[x][y]=true;
        cnt++;		
		if(mii>x)mii=x;
        if(mij>y)mij=y;
        if(mxi<x)mxi=x;
        if(mxj<y)mxj=y;
		getFang(x+1,y);
        getFang(x,y+1);
        getFang(x-1,y);
        getFang(x,y-1);//for 下试试?
				
	}
	
}


E.可口蛋糕

题目大意

  • 每一点id_i,w_i,求对于所有满足W\leq \sum_{i=l}^{r}w_i的区间[i,j],能得到的最大\sum_{i=l}^{r}d_i

解题思路

  • 区间和,考虑前缀和
  • 选择定右端点j,找到能满足限制的最大的i,则小于i的也是满足限制的可选的左端点
  • 在找的过程中,记录最小的前缀和mn=min_{t=1}^{i-1} \ Sumd
  • 则以j为右端点的ans=Sumd_j-mn
  • 然后j++,去计算以j+1为右端点的ans,最后取max\ ans
  • 由于w\geq 0,所以[i,j]满足限制,则[i,j+1]也满足限制,自然i之前的也满足
  • 所以i,j均不减
  • O(n)
  • 若选择定左端点i,则i,j均要从n开始往前走
  • i,j从1开始,则
  • 无法保证[i+1,j]也满足限制
  • [j,n]的区间变小无法维护最小值

import java.io.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Scanner;
import java.util.StringTokenizer;
/*
 * 函数中要使用数组。将其定义为全局
 * 递归中能分开写的尽量不用数组
 */

 public class Main{
	
	public static void main(String[] args) throws IOException{
		
		AReader input=new AReader();
	    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	    int n=input.nextInt();
	    long W=input.nextLong();
	    long[] w=new long[n+1];
	    long[] d=new long[n+1];
	    for(int i=1;i<=n;++i) {
	    	w[i]=input.nextLong();
	    	w[i]+=w[i-1];
	    }
	    for(int i=1;i<=n;++i) {
	    	d[i]=input.nextLong();
	    	d[i]+=d[i-1];
	    }
	    int i=0;//i-1
	    int j=1;
	    long mi=Long.MAX_VALUE/10;
	    long ans=Long.MIN_VALUE;
	    while(j<=n&&i<=j) {
	    	if(w[j]-w[i]>=W) {//i+1,j
	    		mi=Math.min(mi, d[i]);
	    		i++;
	    	}else {
	    		ans=Math.max(ans, d[j]-mi);
	    		j++;
	    	}
	    }
	    out.println(ans);
		out.flush();
		out.close();
	}
	
	static
    class AReader {
        private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        private StringTokenizer tokenizer = new StringTokenizer("");
        private String innerNextLine() {
            try {
                return reader.readLine();
            } catch (IOException ex) {
                return null;
            }
        }
        public boolean hasNext() {
            while (!tokenizer.hasMoreTokens()) {
                String nextLine = innerNextLine();
                if (nextLine == null) {
                    return false;
                }
                tokenizer = new StringTokenizer(nextLine);
            }
            return true;
        }
        public String nextLine() {
            tokenizer = new StringTokenizer("");
            return innerNextLine();
        }
        public String next() {
            hasNext();
            return tokenizer.nextToken();
        }
        public int nextInt() {
            return Integer.parseInt(next());
        }
 
        public long nextLong() {
            return Long.parseLong(next());
        }
 
    }
}

 


F.喜欢序列

题目大意

  • 将一个数组分段,每段段内满足x,x+1,x+2,\cdots
  • 进行m次操作,每次对[l,r]的数值加w
  • 若设分完段后,每段长度为s_i,则输出每次操作后的\sum s_i

解题思路

  • 区间加值,考虑差分
  • 由于最后结果与数值大小无关,所以只用记录数值与它前一个数的相对大小
  • 由于不需要知道实际大小,所以差分不用求前缀和得出数值,也就不需要树状数组等数据结构)
  • 对于一次修改,区间内的无影响,区间外的只可能会影响到l-1所在的段和r+1
  • 而影响只有两种
  • l+1l在一个区间,即pre[l]=w+1 (此时差分已经修改过,原先pre=1),则断开
  • l+1l不在一个区间,但修改后pre[l]=1w=0不算修改,不考虑),则拼接
  • rr+1同理,但由于维护的是向前差分,所以对r+1的相对值-w,看作判断r+1所在的区间是拼接还是断开(l=1,r=n,无意义不考虑)
  • (将一次修改看作两次拼接/断开操作,先进行l,产生新的一轮分段,在此基础上,再进行r+1,产生新的分段)
  • 例:1\ 2\ 3|5\ 6\ 7|9\ 10\ 11,(4,4,-1)\Rightarrow \\1\ 2\ 3\ 4\ 6\ 7|9\ 10\ 11\Rightarrow \\1\ 2\ 3\ 4| 6\ 7|9\ 10\ 11
  • 怎么记录每个点所在的区间,并且避免产生新的区间后,需要重新对区间内的点进行映射更新呢?
  • 由于若一个点在某一区间内,则该区间的左端点一定是所有区间中比它小且离它最近的
  • 所以不直接记录每个点所在的区间,而是知道点的位置去找区间
  • 怎么找?这里引入神奇的Treemap
  • Treemap内部自动按照键值由小到大进行排序,其内置找小于x的最大值Treemap.lowerKey(x)
  • 完美
import java.io.*;
import java.util.StringTokenizer;
import java.util.TreeMap;


/*
 * 函数中要使用数组。将其定义为全局,写在主函数上面
 * 递归中能分开写的尽量不用数组
 */

 public class Main{
	static long ans;
	static long[] pre;
	static TreeMap<Integer, Integer> qu;
	public static void main(String[] args) throws IOException{
		
		AReader input=new AReader();
//		Scanner input=new Scanner(System.in);
	    PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
	    int n=input.nextInt();
	    int m=input.nextInt();
	    long[] a=new long[n+1];
	    pre=new long[n+2];
	    a[1]=input.nextLong();
	    for(int i=2;i<=n;++i) {
	    	a[i]=input.nextLong();
	    	pre[i]=a[i]-a[i-1];
	    }
	    ans=0;
	    qu=new TreeMap<Integer, Integer>();
	    for(int i=2,l=1,r=1;i<=n;++i) {
	    	if(a[i]==a[i-1]+1) r++;
	    	else {
	    		qu.put(l, r);
	    		ans+=(long)(r-l+1)*(r-l+1);
	    		l=i;r=i;
	    	}
	    	if(i==n) {
	    		qu.put(l, r);
	    		ans+=(long)(r-l+1)*(r-l+1);
	    	}
	    }
	    
	    while(m>0) {
	    	int l=input.nextInt();
	    	int r=input.nextInt();
	    	long w=input.nextLong();
	    	if(w!=0) {
	    		if(l!=1)update(l, w);
	    		if(r!=n)update(r+1, -w);
	    	}
	    	out.println(ans);
	    	out.flush();
	    	m--;
	    }
		out.flush();
		out.close();
	}
	
	static void update(int x,long w) {
		
		pre[x]+=w;
		if(pre[x]==1) {//拼
			int l=qu.lowerKey(x);//x-1与x原先是断开的,l<x,l即x-1的左端点
			int r=qu.get(x);//即x的右端点
			qu.remove(x);//删去x区间
			ans-=(long)(x-l)*(x-l);//x-1-l+1
			ans-=(long)(r-x+1)*(r-x+1);
			ans+=(long)(r-l+1)*(r-l+1);
			qu.put(l, r);//替换x-1区间

			
		}else if(pre[x]==w+1) {//断
			int l=qu.lowerKey(x);
			int r=qu.get(l);
			//qu.remove(l);反正接着要替换
			ans-=(long)(r-l+1)*(r-l+1);
			ans+=(long)(x-l)*(x-l);//x-1-l+1
			ans+=(long)(r-x+1)*(r-x+1);
			qu.put(l, x-1);
			qu.put(x,r);
		}
	}
	static
    class AReader {
        private BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        private StringTokenizer tokenizer = new StringTokenizer("");
        private String innerNextLine() {
            try {
                return reader.readLine();
            } catch (IOException ex) {
                return null;
            }
        }
        public boolean hasNext() {
            while (!tokenizer.hasMoreTokens()) {
                String nextLine = innerNextLine();
                if (nextLine == null) {
                    return false;
                }
                tokenizer = new StringTokenizer(nextLine);
            }
            return true;
        }
        public String nextLine() {
            tokenizer = new StringTokenizer("");
            return innerNextLine();
        }
        public String next() {
            hasNext();
            return tokenizer.nextToken();
        }
        public int nextInt() {
            return Integer.parseInt(next());
        }
 
        public long nextLong() {
            return Long.parseLong(next());
        }
 
    }
}

  • 23
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值