USTCOJ 1271 方程X+2Y+5Z=N非负整数解计数

方程非负整数解计数:http://acm.ustc.edu.cn/ustcoj/problem.php?id=1271&contest=35
题目的大致意思,是读入一个非负整数N,然后求出X、Y、Z非负整数解。解这道题的朴素方法,自然是设置一个计数变量counter,然后利用一个三重循环(或者两重),依次对X、Y、Z赋值,当X、Y、Z的组合满足方程时,counter变量自增,最后输出counter的值。编码如下:
#include <stdio.h>

int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        int n;
        scanf("%d", &n);
        int counter = 0;
        for (int x = 0; x <= n; x++)
        {
            for (int y = 0; y <= n; y++)
            {
                for (int z = 0; z <= n; z++)
                {
                    if (x+2*y+5*z==n)
                        counter++;
                }
            }
        }
        printf("%d\n", counter);
    }
    return 0;
}
但采用这种方法编码之后提交,系统显示超时错误。看来得要改进算法才行。分析上述三重循环,我们可以利用方程中的系数减少不必要的循环。重新编码如下:
#include <stdio.h>

int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        int n;
        scanf("%d", &n);
        int counter = 0;
        for (int x = 0; x <= n; x++)
        {
            for (int y = 0; y <= (n-x)/2; y++)
                if ((n-x-2*y)%5==0)
                    counter++;
        }
        printf("%d\n", counter);
    }
    return 0;
}
提交,仍旧超时。看来算法仍待改进。分析上述代码,我们发现,最外层循环次数越多,最里层的代码执行的次数就越多(上述代码中是if ((n-x+2*y)%5==0))。那么,我们应该怎样减少循环执行的次数呢?将最外层循环的循环次数降下来是个不错的办法。怎么降?分析方程X+2Y+5Z=N发现,系数越大的变量,其可能的取值个数就越小。而在方程X+2Y+5Z=N中Z的系数最大,Y其次。我们可以将其放在循环外侧,其次是Y。这样,我们就避免了对X计数。编码如下:
#include <stdio.h>

int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        int n;
        scanf("%d", &n);
        int counter = 0;
        for (int z = 0; z <= n/5; z++)
        {
            for (int y = 0; y <= (n-5*z)/2; y++)
                if (n-5*z-2*y >= 0)
                    counter++;
        }
        printf("%d\n", counter);
    }
    return 0;
}
提交,仍旧超时。看来通过暴力枚举解的方案是行不通的了。但不要沮丧,虽然代码没能提交通过,但在不断尝试的过程中,我们已经最大限度的减少了不必要的循环,正在逐步的向目标靠近。而系统提示超时预示着程序仍有改进的空间。

休息一下,我们继续分析。由于题目只要求给出解的个数,并未要求给出所有的解。那么我们可以从这个出发点入手,分析一下解的个数的规律。朴素的想法是将方程的解进行分类计数。那应该怎么分呢?可以根据X、Y、Z的解来分。那么X、Y、Z按那一个变量分比较合适呢?用X是不现实的了,因为方程2Y+5Z=N-X=N’解的个数没有规律,要对X的每一个取值进行分类计数的话,还是得要枚举。
那么Y和Z呢?首先来看Y。令Y=i,我们可以将方程X+2Y+5Z=N转化为X+5Z=N-2i=N’。 对于X+5Z=N’(N’>=0),其X、Z满足提交的非负整数解有多少个呢? N’/5+1个。为什么?因为 对于Z=i,只要N’-5i>=0,那么就会有满足条件的非负整数解X与之形成一个可行解对。于是X+5Z=N’(N’>=0)非负整数解的个数就是满足N’-5i>=0的非负整数i的个数,即N’/5+1。
同理,利用Z进行分类,则每一个分类下可行解的个数为N’/2+1。

通过上述分析,我们就不在需要将原有代码中的内部for循环了。取而代之的,是一个简单的计算语句。代码如下:

int counter = 0;
for (int z = 0; z <= n/5; z++)
  counter += (n-5*z)/2 + 1;
当然,也可以将y放在外层循环,代码如下:

int counter = 0;
for (int y = 0; y <= n/2; y++)
  counter += (n-2*y)/5 + 1;

显然,采用Z变量进行分类,程序循环执行的次数是最少的。于是完整编码如下所示:

#include <stdio.h>

int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        int n;
        scanf("%d", &n);
        int counter = 0;
        for (int z = 0; z <= n/5; z++)
            counter += (n-5*z)/2 + 1;
        printf("%d\n", counter);
    }
    return 0;
}

提交代码,系统显示Wrong Answer。输入测试数据,结果正确。再次分析代码逻辑,没有错误。好,再来看题目,N<=1000000。那么我们测试一下当输入为1000000是程序会有怎样的输出。结果是一个负数。看来是结果溢出了。于是修改该counter变量为long类型,并修改printf语句修改为%ld,结果还是负数。于是再改,将counter声明为long long类型,并将printf语句修改为%lld。这下出来的结果是整数了。提交,Accept!


最后,再一次改写for语句为while语句,最后Accept的代码如下:
#include <stdio.h>

int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        int n;
        scanf("%d", &n);
        long long counter = 0;
        while (n >= 0)
        {
            counter += n / 2 + 1;
            n -= 5;
        }
        printf("%lld\n", counter);
    }
    return 0;
}


PS.1:printf函数中的%lld转义字符很少见,不知道是很正常的。可以利用C++的cout输出。只需在头文件处添加:
#include <iostream>
using namespace std;
然后将printf("%lld\n", counter);改写为cout << counter << endl;即可。

int counter = 0;for (int z = 0; z <= n/5; z++)  counter += (n-5*z)/2 + 1;


PS.2:在VC 6.0中,无法定义long long类型。但VC 6.0支持64位长整数。可编码如下:
#include <stdio.h>

int main()
{
    int t;
    scanf("%d", &t);
    while (t--)
    {
        int n;
        scanf("%d", &n);
        _int64 counter = 0;
        while (n >= 0)
        {
            counter += n / 2 + 1;
            n -= 5;
        }
        printf("%I64d\n", counter);
    }
    return 0;
}

更多内容,可参考:__int64 与long long 的区别(http://blog.csdn.net/shiwei408/article/details/7463476)

你可能感兴趣的:(1271,ustcoj)