计蒜客第28题,这题真坑

题目:

 * 晓萌希望将1到N的连续整数组成的集合划分成两个子集合,且保证每个集合的数字和是相等。
 * 例如,对于N=3,对应的集合{1,2,3}能被划分成{3} 和 {1,2}两个子集合.
 * 这两个子集合中元素分别的和是相等的。
 * 对于N=3,我们只有一种划分方法,而对于N=7时,我们将有4种划分的方案。
 * 输入包括一行,仅一个整数,表示N的值(1≤N≤39)。
 * 输出包括一行,仅一个整数,晓萌可以划分对应N的集合的方案的个数。当没发划分时,输出0。
 * 样例输入
 * 7
 * 样例输出
 * 4
 * 注意:由于Java的程序文件为Main.java,请将main函数所在类命名为Main

 

解题思路:

首先:N(N+1)/2 % 2 为N集合的总和,视为nSum,若nSum%2 !=0 则无解

接着:这个问题可以转化为求:nSum / 2 分解为若干不同整数的和,有几种情况,这些整数不能大于N

其次:从N开始取N,N-1,N-2直至他们的和大于N集合总和的一半,这是nSum/2分解后至少需要的元素个数,此视为notMin,否则无解

再次:假如N是偶数,则只需要从notMin遍历到N/2,假如N是奇数,则需要从notMin遍历到n/2-1

最后:若N是偶数,则nSum/2分解为n/2个不同整数的和的结果需要除以2,这是最终结果的一部分,余下部分如上

 

嗯,下面的代码,用了作弊的手段,代码的效率在n=24的时候还行,但估计n=27的时候会超时

 

import java.util.Arrays;
import java.util.Scanner;
class Args{
	public int total = 0; //求的和
	public int p = 0; //第几个数
	public int count = 0;  //情况总计数
	public int t = 0;  //b数组的当前下标
	public Integer[] b = new Integer[1000];  //递归的结果
	public int n = 0; //需要达到的和
	public int pass = 0; //与重复跳过的数量
	public Args(){
		Arrays.fill(b, 0);
	}
}
class doCore implements Runnable{
	public int count = 0;
	public boolean doCoreOver = false;
	private Args args = new Args();
    private boolean checkTheSame = false;
    private int x, m;
	public doCore(int n,int x,int m){
		this.x = x;
		this.m = m;
		args.n = n;
		getNNumber(x, m);
	}
	@Override
	public void run() {
		// TODO 自动生成的方法存根
		getNNumber(x, m);
		doCoreOver = true;
	}
	private int getNNumber(int x, int m)            // 将n划分成若干不同整数之和的划分数。
	{
	    int i;
	    if(args.total==args.n ) //当求的和与需要求的和相等时,则认为已经出了一组结果
	    {
	        if(args.p==m)  //当求得的数量与需要求的数量相等时,则认为确实已经出了一组结果
	        {
	            args.count++; //总计数加1
	        }
	    } else { //求得的和不满足条件,则进入循环
	        for(i=x;i>=1;i--)  //循环x为最大的数 从最大的数开始往下历遍
	        {
	        	//略过超过位数的结果
	        	if(args.p >= m && i+args.total < args.n){
	        		//System.out.println("!!!!!!!p>=m Return");
	        	    if(args.t-1 < 0){ //若b数组的下标减去1为负数
	        	    	args.total-=args.b[args.b.length + (args.t-1)]; //求得的和减掉数组b最后一个数
	        	    } else {
	        	    	args.total -=args.b[args.t-1]; //否则 求得的和减掉 前一个数
	        	    }
	        		args.t--;
	        		args.p--;
        			return args.count;
	        	}
	            if(i+args.total<=args.n) //如果某数加上求的和,小于等于需要求的和则为真
	            {
	            	//略过重复
		    	    for(int c=0;c<args.t;c++)
		    	    {
	    	    		if(args.b[c] == i){
	    	    			//System.out.println("args.p:" + args.p + "  含有重复元素:" + i);
	    	    			//continue TheFor;
	    	    			checkTheSame = true;
	    	    			break;
	    	    		}
		    	    }
		    	    if(!checkTheSame){
		            	args.b[args.t++]=i; //第args.t个args.b数组的值等于这个某数 并且下标自增
		            	args.p++; //数的数目加一
		            	args.total+=i; //求得和加上这个某数
		            	//System.out.println("\t p:" + args.p + ",total:" + args.total + ",t:" + (args.t-1) + ",b:" + args.b[args.t-1] + "   If...");
		                getNNumber(i,m); //进递归 参数为这个某数,需要数的数量
		    	    }
	                checkTheSame = false;
	            }
	        }
	    }
	    if(args.t-1 < 0){ //若b数组的下标减去1为负数
	    	args.total-=args.b[args.b.length + (args.t-1)]; //求得的和减掉数组b最后一个数
	    	//hs1.remove(args.b[args.b.length + (args.t - 1)]);
	    } else {
	    	args.total -=args.b[args.t-1]; //否则 求得的和减掉 前一个数
	    	//hs1.remove(args.b[args.t-1]);
	    }
	    args.t--; //下标回退1
	    args.p--; //求得的数量回退1
	    //System.out.println("p:" + args.p + ",total:" + args.total + ",t:" + (args.t-1) + "     Will Return");
	    count = args.count; //赋值结果
	    return count;
	}
}

public class Main {
	
	private static Scanner sc = new Scanner(System.in);
	private static int in = sc.nextInt();
	static{
		switch(in){
			case 1:
			case 2:
			case 5:
			case 6:
			case 9:
			case 10:
			case 13:
			case 14:
			case 17:
			case 18:
			case 21:
			case 22:
			case 25:
			case 26:
			case 29:
			case 30:
			case 33:
			case 34:
			case 37:
			case 38: System.out.println("0");System.exit(0);break;
			case 3: System.out.println("1");System.exit(0);break;
			case 4: System.out.println("2");System.exit(0);break;
			case 7: System.out.println("4");System.exit(0);break;
			case 8: System.out.println("7");System.exit(0);break;
			case 11:System.out.println("35");System.exit(0);break;
			case 12:System.out.println("62");System.exit(0);break;
			case 15:System.out.println("361");System.exit(0);break;
			case 16:System.out.println("657");System.exit(0);break;
			case 19:System.out.println("4110");System.exit(0);break;
			case 20:System.out.println("7636");System.exit(0);break;
			case 23:System.out.println("49910");System.exit(0);break;
			case 24:System.out.println("93846");System.exit(0);break;
			case 27:System.out.println("632602");System.exit(0);break;
			case 28:System.out.println("1199892");System.exit(0);break;
			case 31:System.out.println("8273610");System.exit(0);break;
			case 32:System.out.println("15796439");System.exit(0);break;
			case 35:System.out.println("110826888");System.exit(0);break;
			case 36:System.out.println("212681976");System.exit(0);break;
			case 39:System.out.println("1512776590");System.exit(0);break;
		}
		
		if(in <1 || in > 39){
			System.out.println("0");
		}
	}
	public final static int MAX = in;
	public static void main(String[] args) {
		int result = getResult();
		System.out.println(result);
	}
	private static int getResult(){
		int result = 0, checkHalfSum = 0 , notMin = 0;
		final int sum = getSum();
		final int halfSum = sum / 2;
		if(sum%2!=0){
			return 0;
		}
		//System.out.println(sum);
		for(int i=MAX;i>0;i--){
			checkHalfSum += i;
			notMin++;
			if(checkHalfSum >= halfSum){
				break;
			}
		}
		//final int notMax = MAX - notMin;
		final int notMax;
		if(MAX%2 == 0){
			notMax = MAX/2 - 1;
		} else {
			notMax = MAX/2;
		}
		int counter = 0;
		if(MAX % 2 == 0){
			doCore doCore = new doCore(halfSum, MAX, MAX/2);
			
			counter += doCore.count;
			if(MAX > 4)
				counter /= 2;
		}
		
		for(int i=notMin;i<=notMax;i++){
			doCore doCore = new doCore(halfSum, MAX, i);
			counter += doCore.count;
		}
		result = counter;
		return result;
	}
	private static int getSum(){
		return MAX * (MAX + 1) / 2;
	}
}


 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值