杭电Acm-母函数题目解题报告

今天是腊月29,今年没有30。所以,今晚就是除夕夜了。由于要找工作,练内功,所以,过年也不能够放松。今天做的题目是母函数相关的母函数就Generation Function,也叫生成函数,是组合数学中的一个概念。我请教了 @微博Koth 他说了一个很典型,很简答的例子,可以很好的理解母函数的概念。例如求:

x+2y=10的非负整数解的个数------①

我们直观的看(x,y)的取值有:(0,5),(2,4),(4,3)等。那么从组合数学的角度来讲,我们要怎么思考呢x可能的取值0,1,2,3...y同样的取值也是如此。根据组合数学的思想,x的所有情况乘以y的所有情况,得到的就是真个解得所有的个数。将这个思想表示为如下的公式:

(1 + x + x^2 + x^3+...)(1 + x^2 + x^4 + x^6 +...)------②

第一个括号中,1表示x取0,x表示x取1,x^2表示x取2,依此类推。第二个括号中,x^2表示y取1(2 = 1 * 2),x^4表示y取2(4 = 2 * 2),依此类推。可得。通过观察,我们可以得到,②括号中指数,就表示①中的直,比如x^4中的4就是y取2的时候,即2*y的值。那么,我们展开②式,ax^b的意义就是 x + 2y = b 的非负整数解得个数为a。

杭电ACM的1028,1398,1171都是关于母函数的。而且都是相对比较基础的题目。只要能够将母函数的表示出来,找到系数和指数的含义,就能够顺利的解出题目。稍后简单分析一下几个题目。

  • 1028题
给定一个N可以表示为m个非负整数的和。每一个数之能是大于等于1,小于等于N的。套用母函数的思路,最终的和为N,即使求ax^b中,当b为N的时候,a的值,就是我们要的答案。排列组合,每一项,都可以是1到N的任一个数。代码实现如下,代码比较简单,自己拿笔在纸上写一遍多项式相乘的过程可以了:
import java.util.Scanner;

public class P1028 {

	public static void main(String[] args) {
		int[] c1 = new int[121];
		int[] c2 = new int[121];
		Scanner in = new Scanner(System.in);
		while (in.hasNextInt()) {
			int n = in.nextInt();
			for (int i = 0; i <= n; ++i) {
				c1[i] = 1;
				c2[i] = 0;
			}
			for (int i = 2; i <= n; ++i) {
				for (int j = 0; j <= n; ++j) 
					for (int k = 0; k <= n; k += i)
						c2[k + j] += c1[j];
				for (int j = 0; j <= n; ++j) {
					c1[j] = c2[j];
					c2[j] = 0;
				}
			}
			System.out.println(c1[n]);
		}
	}
}
  • 1398题
这个题目和1028差不多,和x + 2y = 10的解法更相近,就是在遍历每一个表达式的时候,要注意指数的基准,从而弄准指数的变化。代码如下:
import java.util.Scanner;

public class P1398 {

    private static int[] coinType = new int[18];
    static {
        for (int i = 1; i < 18; ++i)
            coinType[i] = i * i;
    }
    public static void main(String[] args) {
        int[] c1 = new int[301];
        int[] c2 = new int[301];
        Scanner in = new Scanner(System.in);
        while (in.hasNextInt()) {
            int n = in.nextInt();
            if (0 == n) return;
            for (int i = 0; i <= n; ++i) {
                c1[i] = 1;
                c2[i] = 0;
            }
            for (int i = 2; i < 18; ++i) {
                for (int j = 0; j <= n; ++j) 
                    for (int k = 0; k + j <= n; k += coinType[i])
                        c2[k + j] += c1[j];
                for (int j = 0; j <= n; ++j) {
                    c1[j] = c2[j];
                    c2[j] = 0;
                }
            }
            System.out.println(c1[n]);
        }
    }
}
  • 1171题
这道题目,我们首先将题意理解透彻。一个不好理解的地方,就是要求两部分要尽可能相等,或者相差的尽可能小。这个怎么办呢?我们设总的是sum,可以将sum分成两部分,suma和sumb。两部分要满足前面说的条件。同时,每一个都得是按照输入给定的个数来划分。那么,对于sum/2为指数的时候,系数就是组合的个数,也就是可以分配多少。如果有这样的组合,则得解,则判断sum/2 - 1。直到有这样的组合位置,就可以找到满足条件的划分了。代码如下:
import java.util.Arrays;
import java.util.Scanner;

public class P1171 {

	
	public static void main(String[] args) {
		int[] c1 = new int[250001];
		int[] c2 = new int[250001];
		Scanner in = new Scanner(System.in);
		while (in.hasNextLine()) {
			int n = Integer.parseInt(in.nextLine());
			if (n < 0) return;
			int[] v = new int[n + 1];
			int[] m = new int[n + 1];
			int sum = 0;
			for (int i = 1; i <= n; ++i) {
				String[] ss = in.nextLine().split("[ ]+");
				v[i] = Integer.parseInt(ss[0]);
				m[i] = Integer.parseInt(ss[1]);
				sum += v[i] * m[i];
			}
			Arrays.fill(c1, 0);
			Arrays.fill(c2, 0);
			for (int i = 0; i <= v[1] * m[1]; i += v[1])
				c1[i] = 1;
			int len = v[1] * m[1];
			for (int i = 2; i <= n; ++i) {
				for (int j = 0; j <= len; ++j) 
					for (int k = 0; k <= v[i] * m[i]; k += v[i])
						c2[k + j] += c1[j];
				len += v[i] * m[i];
				for (int j = 0; j <= len; ++j) {
					c1[j] = c2[j];
					c2[j] = 0;
				}
			}
			for(int i= sum / 2; i >= 0; --i)
	            if(c1[i] != 0)
	            {
	                System.out.println((sum - i) + " " + i);
	                break;
	            }
		}
	}
}

 

转载于:https://www.cnblogs.com/sing1ee/archive/2012/01/22/2764994.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值