蓝桥杯算法训练

                                                  蓝桥杯算法训练

2的次幂表示 

前缀表达式

Anagrams问题 

出现次数最多的整数  

大小写转换  

最大的算式  

区间k大数查询

未名湖边的烦恼

最大最小公倍数

K好数

数字三角形  

寻找数组中最大值  

5-1最小公倍数  

关联矩阵

结点选择  

  算法训练 2的次幂表示  
时间限制:1.0s   内存限制:512.0MB
    
问题描述
  任何一个正整数都可以用2进制表示,例如:137的2进制表示为10001001。
  将这种2进制表示写成2的次幂的和的形式,令次幂高的排在前面,可得到如下表达式:137=2^7+2^3+2^0
  现在约定幂次用括号来表示,即a^b表示为a(b)
  此时,137可表示为:2(7)+2(3)+2(0)
  进一步:7=2^2+2+2^0 (2^1用2表示)
  3=2+2^0  
  所以最后137可表示为:2(2(2)+2+2(0))+2(2+2(0))+2(0)
  又如:1315=2^10+2^8+2^5+2+1
  所以1315最后可表示为:
  2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)
输入格式
  正整数(1<=n<=20000)
输出格式
  符合约定的n的0,2表示(在表示中不能有空格)
样例输入
137
样例输出
2(2(2)+2+2(0))+2(2+2(0))+2(0)
样例输入
1315
样例输出
2(2(2+2(0))+2)+2(2(2+2(0)))+2(2(2)+2(0))+2+2(0)
提示
  用递归实现会比较简单,可以一边递归一边输出

import java.util.Scanner;


public class Main {
	
	
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		int n;
		while(in.hasNext()){
			n= in.nextInt();
			System.out.println(f(n));
		}
	}
	
	public static String f(int n) {
		String a;
		if(n == 0){
			return "2(0)";
		}else if(n == 1){
			return "2";
		}else if(n == 2){
			return "2(2)";
		}else {
			
			a = Integer.toBinaryString(n);
			int len = a.length();
			String result = "";
			for(int i = 0; i < len; i++){
				if(a.charAt(i) == '1') {
					if(len-i-1 >2) {
						if(result == ""){
							result = result + "2(" +f(len-i-1) +")";
						}else{
							result = result + "+2("+f(len-i-1)+")";
						}
					}else if(len-i-1 <=2){
						if(result == ""){
							result = f(len-i-1);
						}else {
							result = result + "+" + f(len-i-1);
						}
					}
				}
			}
			return result;
		}
	
		
	}

}
  算法训练 前缀表达式  
时间限制:1.0s   内存限制:512.0MB
    
问题描述
  编写一个程序,以字符串方式输入一个前缀表达式,然后计算它的值。输入格式为:“运算符 对象1 对象2”,其中,运算符为“+”(加法)、“-”(减法)、“*”(乘法)或“/”(除法),运算对象为不超过10的整数,它们之间用一个空格隔开。要求:对于加、减、乘、除这四种运算,分别设计相应的函数来实现。
  输入格式:输入只有一行,即一个前缀表达式字符串。
  输出格式:输出相应的计算结果(如果是除法,直接采用c语言的“/”运算符,结果为整数)。
  输入输出样例
样例输入
+ 5 2
样例输出
7

import java.util.Scanner;


public class Main1 {
	
	
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		String n;
		while(in.hasNext()){
			n= in.nextLine();
			String[] str =n.split(" ");
			int num1 = Integer.parseInt(str[1]);
			int num2 = Integer.parseInt(str[2]);
			switch(str[0]) {
			case "+": 
				System.out.println(add(num1,num2));
				break;
			case "-":
				System.out.println(sub(num1,num2));
				break;
			case "*":
				System.out.println(mul(num1,num2));
				break;
			case "/":
				System.out.println(div(num1,num2));
				break;
			}
			
		}
	}
	
	public static int add(int a, int b){
		return a + b;
	}
	
	public static int sub(int a, int b){
		return a - b;
	}
	public static int mul(int a, int b){
		return a * b;
	}
	public static int div(int a, int b){
		return a / b;
	}

}


  算法训练 Anagrams问题  
时间限制:1.0s   内存限制:512.0MB
    
问题描述
  Anagrams指的是具有如下特性的两个单词:在这两个单词当中,每一个英文字母(不区分大小写)所出现的次数都是相同的。例如,“Unclear”和“Nuclear”、“Rimon”和“MinOR”都是Anagrams。编写一个程序,输入两个单词,然后判断一下,这两个单词是否是Anagrams。每一个单词的长度不会超过80个字符,而且是大小写无关的。
  输入格式:输入有两行,分别为两个单词。
  输出格式:输出只有一个字母Y或N,分别表示Yes和No。
  输入输出样例
样例输入
Unclear
Nuclear
样例输出
Y

import java.util.Scanner;


public class Main{
	
	
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		
		while(in.hasNext()){
			int arr1[] = new int[26];
			int arr2[] = new int[26];
			for(int i = 0; i < 26; i++){
				arr1[i] = 0;
				arr2[i] = 0;
			}
			String str1 = in.next().toLowerCase();
			String str2 = in.next().toLowerCase();
			int len1= str1.length();
			int len2 = str2.length();
			for(int i = 0; i < len1; i++){
				arr1[str1.charAt(i)- 'a'] += 1; 
			
			}
			for(int i = 0; i < len2; i++){
				arr2[str2.charAt(i)- 'a'] += 1; 
			}
			boolean check = false;
			for(int i = 0; i < 26; i++) {
				
				if(arr1[i] != arr2[i]) {
					check = true;
					break;
				}
			}
			if(check){
				System.out.println("N");
			}else{
				System.out.println("Y");
				
				
			}
		}
	}
	

}
  算法训练 出现次数最多的整数  
时间限制:1.0s   内存限制:512.0MB
    
   问题描述
  编写一个程序,读入一组整数,这组整数是按照从小到大的顺序排列的,它们的个数N也是由用户输入的,最多不会超过20。然后程序将对这个数组进行统计,把出现次数最多的那个数组元素值打印出来。如果有两个元素值出现的次数相同,即并列第一,那么只打印比较小的那个值。
  输入格式:第一行是一个整数 NN £ 20;接下来有 N行,每一行表示一个整数,并且按照从小到大的顺序排列。
  输出格式:输出只有一行,即出现次数最多的那个元素值。
输入输出样例
样例输入
5
100
150
150
200
250
样例输出
150
import java.util.Scanner;

//这代码只能得80分,不知道哪里错了
public class Main {
	
	
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		
		while(in.hasNext()){
			int n = in.nextInt();
			int arr[] = new int[n];    //存储输入数据
			int index[] = new int[n];   //记录次数
			int max = 0;    //标记最大的位置
			int temp;
			for(int i = 0; i < n; i++) {
				index[i] = 0;
			}
			int p = 0;
			arr[p] = in.nextInt();
			index[p] +=1;
			max = p;
			p++;
			for(int i = 1; i < n; i++) {
				temp = in.nextInt();
				if(temp == arr[p-1]) {
					index[p-1] += 1; 
					if(index[p-1] > index[max]){
						max = p-1;
					}
				}else{
					arr[p] = temp;
					index[p] += 1;
					if(index[p] > index[max]){
						max = p;
					}
					p++;
				}			
			}
			System.out.println(arr[max]);
			
		
		}
	}
	

}

  算法训练 大小写转换  
时间限制:1.0s   内存限制:512.0MB
    
问题描述
  编写一个程序,输入一个字符串(长度不超过20),然后把这个字符串内的每一个字符进行大小写变换,即将大写字母变成小写,小写字母变成大写,然后把这个新的字符串输出。
  输入格式:输入一个字符串,而且这个字符串当中只包含英文字母,不包含其他类型的字符,也没有空格。
  输出格式:输出经过转换后的字符串。
输入输出样例
样例输入
AeDb
样例输出
aEdB
import java.util.Scanner;


public class Main {
	
	
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		
		while(in.hasNext()){
			String str = in.next();
 		    int len = str.length();
 		    char arr[] = new char[len];
			for(int i = 0; i < len; i++) {
				arr[i] = str.charAt(i);
				if(arr[i] >= 'A' && arr[i] <= 'Z') {
					arr[i] = (char) (arr[i] + 32);
				}else {
					arr[i] =(char)(arr[i] - 32);
				}
			}
			System.out.println(new String(arr));
		}
	}
	

}

最大的算式  
时间限制:1.0s   内存限制:256.0MB
    
问题描述
  题目很简单,给出N个数字,不改变它们的相对位置,在中间加入K个乘号和N-K-1个加号,(括号随便加)使最终结果尽量大。因为乘号和加号一共就是N-1个了,所以恰好每两个相邻数字之间都有一个符号。例如:
  N=5,K=2,5个数字分别为1、2、3、4、5,可以加成:
  1*2*(3+4+5)=24
  1*(2+3)*(4+5)=45
  (1*2+3)*(4+5)=45
  ……
输入格式
  输入文件共有二行,第一行为两个有空格隔开的整数,表示N和K,其中(2<=N<=15, 0<=K<=N-1)。第二行为 N个用空格隔开的数字(每个数字在0到9之间)。
输出格式
  输出文件仅一行包含一个整数,表示要求的最大的结果
样例输入
5 2
1 2 3 4 5
样例输出
120
样例说明
  (1+2+3)*4*5=120

思路:提示说按照dp处理,就往这边想了,先确定了dp[i][j]表示前i个数中使用了j个乘号的所得到最大值。

纠结于插入的位置,所以只好再设一重循环,表示可以插入的位置,范围从2到n。

动态转移方程是根据01背包的思想转化得来的,选定插入位置,前面的最大值乘后边从插入位置到i之前的总和,原来想的是只乘一个数,但是一起乘总比一个去乘要大。

所以状态转移方程:dp[i][j]=max(dp[i][j],dp[p-1][j-1]*(p到i之间的总和));

最后就是初始状态的考虑,dp[i][0]表示没有用到乘号,所以dop[i][0]就是前i项的和。


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		
		while(in.hasNext()) {
			int n = in.nextInt();
			int m = in.nextInt();
			Long arr[]  = new Long[16];
			Long dp[][] = new Long[16][16];
			for(int i = 0; i < 16; i++) {
				for(int j = 0; j < 16; j++) {
					dp[i][j] = 0L;
				}
			}
			arr[0] = 0L;
			for(int i = 1; i <= n; i++) {
				
				arr[i] = in.nextLong();
	
				dp[i][0] = dp[i-1][0] + arr[i];		//在没有使用乘号的情况全部使用加法  
			}
			for(int i = 1; i <= n; i++) {
				for(int j = 1; j <= i - 1; j++) {
					for(int p = 2; p <= i; p++) { //第j个乘号插入的位置,如果和前面的乘号位置重叠了,也不影响,还是原来的dp[i][j]
						dp[i][j] = Math.max(dp[i][j], dp[p-1][j-1] * (dp[i][0] - dp[p-1][0])); //从车如位置到i的和,一起乘总比一个乘要大
					}
				}
			}
			System.out.println(dp[n][m]);
		}
		
		
	}
	
	
}


区间k大数查询  
时间限制:1.0s   内存限制:256.0MB
      
问题描述

给定一个序列,每次询问序列中第l个数到第r个数中第K大的数是哪个。

输入格式

第一行包含一个数n,表示序列长度。

第二行包含n个正整数,表示给定的序列。

第三个包含一个正整数m,表示询问个数。

接下来m行,每行三个数l,r,K,表示询问序列从左往右第l个数到第r个数中,从大往小第K大的数是哪个。序列元素从1开始标号。

输出格式
总共输出m行,每行一个数,表示询问的答案。
样例输入
5
1 2 3 4 5
2
1 5 2
2 3 2
样例输出
4
2
数据规模与约定

对于30%的数据,n,m<=100;

对于100%的数据,n,m<=1000;

保证k<=(r-l+1),序列中的数<=106


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Scanner;
public class Main {
	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		
		while(in.hasNext()) {
			int n = in.nextInt();
			Integer arr[] = new Integer[n+1];
			int l = 0,c = 0,r = 0;
			PriorityQueue pq = null;      //排序队列
			arr[0] = 1;
			for(int i = 1; i <= n; i++) {
				arr[i] = in.nextInt();
			}
			int m = in.nextInt();
			for(int i = 0; i < m; i++) {
				l = in.nextInt();
				c = in.nextInt();
				r = in.nextInt();
				
				pq = new PriorityQueue();
				for(int j = l; j <= c ; j++){
					pq.add(arr[j]);     //添加自动实现排序(从小到大)
				}
				int len = pq.size() - r;
				for(int k = 1; k <= len; k++){
					pq.poll();		  //弹出队头并删除
					
				}
				
				System.out.println(pq.peek());			//弹出队头不删除
			}
		
		}
		
		
	}
	
	
}


未名湖边的烦恼  
时间限制:1.0s   内存限制:256.0MB
    
问题描述
  每年冬天,北大未名湖上都是滑冰的好地方。北大体育组准备了许多冰鞋,可是人太多了,每天下午收工后,常常一双冰鞋都不剩。
  每天早上,租鞋窗口都会排起长龙,假设有还鞋的m个,有需要租鞋的n个。现在的问题是,这些人有多少种排法,可以避免出现体育组没有冰鞋可租的尴尬场面。(两个同样需求的人(比如都是租鞋或都是还鞋)交换位置是同一种排法)
输入格式
  两个整数,表示m和n
输出格式
  一个整数,表示队伍的排法的方案数。
样例输入
3 2
样例输出
5
数据规模和约定
  m,n∈[0,18]
  问题分析

递归法

共有m个人还鞋,n个人借鞋,记最终排列数为f(m, n)

现在求mn的排队情况,具体理解如下:

起始,要去一人还鞋(PS:此时,m = m - 1),还完后,可以选一人还鞋(PSm = m - 1)或者一人借鞋(PS:n = n - 1)

那么,f(m , n) = f(m - 1, n) + f(m, n - 1)。这就是求取f(m, n)的递推公式,那么轻易可知当m < n时,f(m, n) = 0;当n = 0时,f(m, 0) = 1

import java.util.Scanner;  

public class Main {  
 
    public static void main(String[] args) {  
    	Scanner in = new Scanner(System.in);
    	int m = in.nextInt();
    	int n = in.nextInt();
    	System.out.println(fun(m,n));
    }  
  
    public static int fun(int m, int n) {
    	//System.out.println("fun("+ (m-1) + "," + n +")+fun(" + m + "," + (n-1) + ");");
    	if(m < n){
    		return 0;
    	}
    	if(n == 0) {
    		return 1;
    	}
    	return fun(m-1,n) + fun(m, n-1);
    }
    
}  

递推法

import java.util.Scanner;

public class Main{
    //m代表还鞋的人数,n代表借鞋的人数
    public int getArrange1(int m, int n) {
        int[][] result = new int[m + 1][n + 1];   //初始化元素全为0
       
        for(int i = 1;i <= m;i++) {
            result[i][0] = 1;     //当借鞋的人数为0时,排列只有一种情况
            for(int j = 1;j <= n;j++) {
                if(i >= j)   //当i小于j时,排列总数为0
                    result[i][j] = result[i - 1][j] + result[i][j - 1];
            }
        }
        return result[m][n];
    }
    
    
    public static void main(String[] args){
        Main test = new Main(); 
        Scanner in = new Scanner(System.in);
    //    System.out.println("请分别输入还鞋人数m和和借鞋人数n:");
        int m = in.nextInt();
        int n = in.nextInt();
        System.out.println(test.getArrange1(m, n));
        
    }
}

  算法训练 最大最小公倍数  
时间限制:1.0s   内存限制:256.0MB
问题描述

已知一个正整数N,问从1~N中任选出三个数,他们的最小公倍数最大可以为多少。

输入格式

输入一个正整数N。

输出格式
输出一个整数,表示你找到的最小公倍数。
样例输入
9
样例输出
504
数据规模与约定

1 <= N <= 106

题意分析:在n个数中找任意三个数的最小公倍数,并且求得最大的最小公倍数(重点在于最大)。

思路分析:最大 最小公倍数,联想到两个数的求最大最小公倍数,即两个数的乘积(注:连续的两个自然数是互斥的)。

                   同样,我们可以拿最后三个数来做考虑。

                   1.当n为奇数时,n,n-1,n-2为奇偶奇,里面只有一个偶数,所以不会有2这个因子。这三个数相差不到3,所以也不会有因子3,故符合题意。

                   2.当n为偶数时,n,n-1,n-2为偶奇偶,此时n,n-2肯定含有因子2,所以除于2不值得。所以考虑将n-2 换成n-3,变成奇偶奇,此时也有一个问题,

                   n和n-3,如果n%3==0,则除于3更不值得。仍根据奇偶奇的原则,变动偶数n为n-2,此时换成n-1,n-2,n-3和1情况一样。故此时符合题意。

粗心:忘记了结果的long处理,数据处理超过了int的大小。
import java.math.*;  
import java.util.Scanner;


public class Main {  
    public static void main(String[] args) throws Exception {  
       Scanner in = new Scanner(System.in);
       while(in.hasNext()) {
    	   long n = in.nextLong();
    	   long sum;
    	   if(n%2 == 1) {
    		   System.out.println(n*(n-1)*(n-2));
    	   }else {
    		   if(n%3 == 0) {
    			   System.out.println((n-1)*(n-2)*(n-3));
    		   }else {
    			   sum = n*(n-1)*(n-3);
    			   System.out.println(sum);
    		   }
    	   }
    	 
       }
    }  
    
   
}  


  算法训练 K好数  
时间限制:1.0s   内存限制:256.0MB
问题描述

如果一个自然数N的K进制表示中任意的相邻的两位都不是相邻的数字,那么我们就说这个数是K好数。求L位K进制数中K好数的数目。例如K = 4,L = 2的时候,所有K好数为11、13、20、22、30、31、33 共7个。由于这个数目很大,请你输出它对1000000007取模后的值。

输入格式

输入包含两个正整数,K和L。

输出格式
输出一个整数,表示答案对1000000007取模后的值。
样例输入
4 2
样例输出
7
数据规模与约定

对于30%的数据,KL <= 106

对于50%的数据,K <= 16, L <= 10;

对于100%的数据,1 <= K,L <= 100。


典型的动态规划题:一位一位的去填充,用一个二维数组arr[i][j]表示i位数以j结尾一共有多少种,接下来要放下一位的话,只要判断要放的这位数字是否是j的邻位即可。

dp[i][j],其中 i 表示总共有多少位(i<=L),j 表示最后面那个数字(j我们是这样动态规划的:
总共1位:  全部初始化为1,方便arr[2][j]  规划。
总共2位:......  (还是直接看下面的数组吧)。
当输入:>> 4 2  时,如下:(行i

  0 1 2 3
1 1 1 1 1
2 3 2 2 3
所以:7 = 2 + 2 + 3
import java.math.*;  
import java.util.Scanner;

public class Main {  
    public static void main(String[] args) throws Exception {  
       Scanner in = new Scanner(System.in);
       while(in.hasNext()) {
    	   int k = in.nextInt();
    	   int L = in.nextInt();
    	   long sum = 0;
    	   long arr[][] = new long[505][105];
    	   for(int i = 0; i < k; i++) {      //如果是一位数,那么每个小于k的数就是1
    		   arr[1][i] = 1;
    	   }
    	   
    	   for(int i = 2; i <= L; i++) {
    		   for(int j = 0; j < k; j++) {
    			   for(int x = 0; x < k; x++) {
    				   if(x != j-1 && x !=  j+1) {     //左右不相邻
    					   arr[i][j] += arr[i-1][x];
    					   arr[i][j] %= 1000000007;
    				   }
    			   }
    		   }
    	   }
    	   for (int i = 1; i < k; i++) {     //将最后一行累加,第一列0,不统计
    		   sum += arr[L][i];
    		   sum %= 1000000007;
    	   }
    	 System.out.println(sum);
       }
    }  
    
   
}  
  算法训练 数字三角形  
时间限制:1.0s   内存限制:256.0MB   
问题描述
  (图3.1-1)示出了一个数字三角形。 请编一个程序计算从顶至底的某处的一条路
  径,使该路径所经过的数字的总和最大。
  ●每一步可沿左斜线向下或右斜线向下走;
  ●1<三角形行数≤100;
  ●三角形中的数字为整数0,1,…99;


   .
  (图3.1-1)
输入格式
  文件中首先读到的是三角形的行数。

  接下来描述整个三角形
输出格式
  最大总和(整数)
样例输入
5
7
3 8
8 1 0
2 7 4 4
4 5 2 6 5
样例输出
30

  普通的dp问题,自底向上求最大就可以了
code:

import java.math.*;  
import java.util.Scanner;

public class Main {  
    public static void main(String[] args) throws Exception {  
       Scanner in = new Scanner(System.in);
       int dp[][] = new int[101][101];
       while(in.hasNext()) {
    	  int n = in.nextInt();
    	  for(int i = 1; i <= n;i++) {
    		  for(int j = 1; j <= i; j++) {
    			  dp[i][j] = in.nextInt();
    		  }
    	  }
    	  int max = 0;
    	  int temp;
    	  for(int i = n - 1; i >= 1; i--) {
    		  for(int j = 1; j <= i; j++) {
    			 temp =  Math.max(dp[i+1][j],dp[i+1][j+1]);
    			 dp[i][j] += temp;
    			 if(dp[i][j] > max) {
    				 max = dp[i][j];
    			 }
    		  }
    	  }
    	  System.out.println(max);
       }
    }  
    
   
}  
  算法训练 寻找数组中最大值  
时间限制:1.0s   内存限制:512.0MB
问题描述
  对于给定整数数组a[],寻找其中最大值,并返回下标。
输入格式
  整数数组a[],数组元素个数小于1等于100。输出数据分作两行:第一行只有一个数,表示数组元素个数;第二行为数组的各个元素。
输出格式
  输出最大值,及其下标
样例输入
3
3 2 1

样例输出

3 0
import java.math.*;  
import java.util.Scanner;

public class Main {  
    public static void main(String[] args) throws Exception {  
       Scanner in = new Scanner(System.in);
       while(in.hasNext()) {
    	  int n = in.nextInt();
    	  int max = 0;
    	  int index = 0;
    	  int number;
    	  for(int i= 0; i < n; i++ ) {
    		 number =  in.nextInt();
    		 if(number > max) {
    			 max = number;
    			 index = i;
    		 }
    	  }
    	  System.out.println(max + " " + index);
       }
    }  
    
   
}  

  算法训练 5-1最小公倍数  
时间限制:1.0s   内存限制:256.0MB
问题描述
  编写一函数lcm,求两个正整数的最小公倍数。
样例输入
一个满足题目要求的输入范例。
例:

3 5
样例输出
与上面的样例输入对应的输出。
例:
数据规模和约定
  输入数据中每一个数的范围。
  例:两个数都小于65536。

import java.math.*;  
import java.util.Scanner;

public class Main {  
    public static void main(String[] args) throws Exception {  
       Scanner in = new Scanner(System.in);
       while(in.hasNext()) {
    	  int n = in.nextInt();
    	  int m = in.nextInt();
    	  System.out.println(n*m/dp(n, m));
       }
    }  
    public static int dp(int a, int b) {
    	return b == 0? a: dp(b, a%b);
    }
   
}  

  算法训练 关联矩阵    
问题描述
  有一个n个结点m条边的有向图,请输出他的关联矩阵。
输入格式
  第一行两个整数n、m,表示图中结点和边的数目。n<=100,m<=1000。
  接下来m行,每行两个整数a、b,表示图中有(a,b)边。
  注意图中可能含有重边,但不会有自环。
输出格式
  输出该图的关联矩阵,注意请勿改变边和结点的顺序。
样例输入
5 9
1 2
3 1
1 5
2 5
2 3
2 3
3 2
4 3
5 4
样例输出
1 -1 1 0 0 0 0 0 0
-1 0 0 1 1 1 -1 0 0
0 1 0 0 -1 -1 1 -1 0
0 0 0 0 0 0 0 1 -1
0 0 -1 -1 0 0 0 0 1
对于一个无向图G,pxq, p为顶点的个数,q为边数。 bij 表示在关联矩阵中点i和边j之间的关系。若点i和边j之间是连着的,则 bij = 1. 反之,则 bij = 0;

对于有向图,若bij = 1,表示边j离开点i。 若bij = -1, 表示边j进入点i。 若bij = 0,表示边j和点i不相关联。

很明显题意是有向图

import java.util.Scanner;


public class Main {
   public static void main(String[] args) {
	Scanner in  = new Scanner(System.in);
	int n, m,a,b;
	int arr[][] = new int[105][1005];
	while(in.hasNext()) {
		n = in.nextInt();
		m = in.nextInt();
		for(int i = 1; i <= m; i++) {
			a = in.nextInt();
			b = in.nextInt();
			arr[a][i] = 1;
			arr[b][i] = -1;
		}
		for(int i = 1; i <= n; i++) {
			for(int j = 1; j <= m; j++){
				System.out.print(arr[i][j]);
				if(j != m) {
					System.out.print(" ");
				}else {
					System.out.println();
				}
			}
		}
	}
}
}

  算法训练 结点选择  
时间限制:1.0s   内存限制:256.0MB
问题描述

有一棵 n 个节点的树,树上每个节点都有一个正整数权值。如果一个点被选择了,那么在树上和它相邻的点都不能被选择。求选出的点的权值和最大是多少?

输入格式

第一行包含一个整数 n 。

接下来的一行包含 n 个正整数,第 i 个正整数代表点 i 的权值。

接下来一共 n-1 行,每行描述树上的一条边。

输出格式
输出一个整数,代表选出的点的权值和的最大值。
样例输入
5
1 2 3 4 5
1 2
1 3
2 4
2 5
样例输出
12
样例说明
选择3、4、5号点,权值和为 3+4+5 = 12 。
数据规模与约定

对于20%的数据, n <= 20。

对于50%的数据, n <= 1000。

对于100%的数据, n <= 100000。

权值均为不超过1000的正整数。

百度找的题解,能力不足,还是看不懂,先扑上去,有时间再看看。

看过这道题,我们大概可以知道,这是一道和树有关的问题,那么我们需要考虑的就是如何去遍历树?很明显,我们需要用到DFS。仔细读这道题,然后我们就可以得到这还需要用到动态规划,由此,我们做这道题,解题方案也就是树形动态规划了!

既然要用动态规划,构造状态转移方程是必不可少了! 
对于叶子结点: 
dp[k][0] = 0; 
dp[k][1] = k点权值; 
对于非叶子结点: 
dp[i][0] = max(dp[j][0], dp[j][1]); (j是i的儿子) 
dp[i][1] = i点权值 + dp[j][0]; (j是i的儿子) 
最大权值即为: 
max(dp[0][0], dp[0][1])。(要么不包括根结点,要么包括根结点)

代码如下:


#include 
#include 
#define _Max 100010
#define max(a, b) a > b ? a : b

struct point
{
    int v, next;   //v指向这条边的另一个结点(父结点),next指向子结点
} edge[_Max * 2];  //一条边记录两次,分别以一个点做记录

int head[_Max];
int M;
int dp[_Max][2];

//添加一个边
void addEdge(int from, int to)
{
    //from结点
    edge[M].v = to;
    edge[M].next = head[from];    //为-1则定位叶结点,否则,指向另外一条边
    head[from] = M++;             //指向他的一条边,增加结点
    //to结点
    edge[M].v = from;
    edge[M].next = head[to];      //为-1则定位叶结点,否则,指向另外一条边
    head[to] = M++;               //指向他的一条边,增加结点
    return ;
}

//深度遍历,先深入到叶子结点,然后一层一层往上回升,一直到根结点,即第一个结点(初始pre为-1是因为根结点没有父结点,用-1表示)
void dfs(int x, int pre)
{
    int i = head[x], v;
    for (; i != -1; i = edge[i].next)  //i != -1说明有子结点,则遍历子结点,否则为叶子结点
    {
        v = edge[i].v;
        if (pre == v)  //如果指向的子结点和父结点重合,则说明这个结点是叶子结点,不需要进一步dp
        {
            continue;
        }
        dfs(v, x);     //x可以理解为父结点
        //深度遍历到最里面的叶子结点的父结点   如果父结点选择,则子结点不选择,否则子结点可能选择或者不选择,但是要比较两者哪个大选择哪个
        dp[x][1] += dp[v][0];                   //   父结点(选) += 子结点(不选)
        dp[x][0] += max(dp[v][0], dp[v][1]);    //   父结点(不选) += max(子结点(不选),子结点(选))
    }
    return ;
}
int main(int argc, const char * argv[])
{
    int i, n, s, t, tmp;
    scanf("%d", &n);
    M = 0;
    memset(head, -1, sizeof(head));   //初始化每个结点都是独立的没有子结点
    memset(dp, 0, sizeof(dp));
    //输入权值,并且记录在dp[i][1]上,i表示第i个结点,1代表取了这个结点
    for (i = 1; i <= n; i++)
    {
        scanf("%d", &dp[i][1]);
    }
    //输入边,并且添加edge,一个边添加两个edge
    for (i = 1; i < n; i++)
    {
        scanf("%d %d", &s, &t);
        addEdge(s, t);
    }
    dfs(1, -1);   //深度优先遍历,从第一个结点开始遍历
    tmp = max(dp[1][0], dp[1][1]);    //求出最大的权值和
    printf("%d\n", tmp);
    return 0;
}

你可能感兴趣的:(蓝桥之战)