历届试题 波动数列

这篇博客探讨了一道关于数列的动态规划题目。题目要求找出所有长度为n且后一项比前一项增加a或减少b的整数数列,其和为s的方案数。博主首先给出了两段代码,第一段使用动态规划求解,第二段使用递归搜索尝试解决问题。博主指出,关键在于计算由a和b构成的不同组合的数量,然后利用动态规划计算方案数。最终,博主给出了一个经过优化的动态规划解决方案并成功通过了所有测试用例。
摘要由CSDN通过智能技术生成

@ 蓝桥杯 练习系统 历届试题 PREV-30

资源限制

时间限制:1.0s 内存限制:256.0MB


问题描述

观察这个数列:
1 3 0 2 -1 1 -2 …

这个数列中后一项总是比前一项增加2或者减少3。

栋栋对这种数列很好奇,他想知道长度为 n 和为 s 而且后一项总是比前一项增加a或者减少b的整数数列可能有多少种呢?


输入格式

输入的第一行包含四个整数 n s a b,含义如前面说述。


输出格式

输出一行,包含一个整数,表示满足条件的方案数。由于这个数很大,请输出方案数除以100000007的余数。


测试样例1

Input:
4 10 2 3

Output:
2

Explanation:
这两个数列分别是2 4 1 3和7 4 1 -2

数据规模与约定

对于10%的数据,1<=n<=5,0<=s<=5,1<=a,b<=5;
对于30%的数据,1<=n<=30,0<=s<=30,1<=a,b<=30;
对于50%的数据,1<=n<=50,0<=s<=50,1<=a,b<=50;
对于70%的数据,1<=n<=100,0<=s<=500,1<=a, b<=50;
对于100%的数据,1<=n<=1000,-1,000,000,000<=s<=1,000,000,000,1<=a, b<=1,000,000。


ACcode:

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

public class Main {

    static final int mod = 100000007;

    public static void main(String[] args) {
        InputReader in = new InputReader(System.in, 128);
        int n, a, b, sum, cnt = 0, dp[];
        long m, ab;
        n = in.nextInt(); m = in.nextInt();
        a = in.nextInt(); b = in.nextInt();
        sum = (n - 1) * n / 2;
        dp = new int[sum + 1];
        m += sum * (long)b;
        ab = a + b;
        dp[0] = 1;
        for (int i = 1; i < n; i++)
            for (int j = sum; j >= i; j--)
                dp[j] = (dp[j] + dp[j - i]) % mod;
        for (int i = 0; i <= sum; i++)
            if ((m - i * ab) % n == 0) cnt = (cnt + dp[i]) % mod;
        System.out.print(cnt);
    }

    static class InputReader {

        byte[] buff;
        int next, len;
        InputStream in;

        InputReader(InputStream in) { this(in, 1024); }

        InputReader(InputStream in, int buffSize) {
            this.buff = new byte[buffSize];
            this.in = in;
        }

        int read() {
            if (next >= len)
                try {
                    next = 0;
                    len = in.read(buff);
                    if (len == -1) return -1;
                } catch (IOException e) {}
            return buff[next++];
        }

        int nextInt() {
            int n = 0, c = read();
            while (c < '0' || c > '9') c = read();
            while (c >='0' && c <='9') {
                n = n * 10 + (c & 0xf);
                c = read();
            }
            return n;
        }
    }
}

我知道这么写必过不了,但我有了这个思路后我就知道这应该是道动态规划题,算写着玩吧,等上完课研究会儿

import java.io.*;

public class Main {

    static int n, m, a, b, cnt;

    public static void main(String[] args) {
        InputReader in = new InputReader(System.in, 128);
        n = in.nextInt();
        m = in.nextInt();
        a = in.nextInt();
        b = in.nextInt();
        dfs(1, 0, 0);
        System.out.print(cnt);
    }

    static void dfs(int d, int s, int sum) {
        if (d == n) {
            if ((sum + s + m) % n == 0) cnt++;
        } else {
            dfs(d + 1, s + a, sum + s);
            dfs(d + 1, s - b, sum + s);
        }
    }

    static class InputReader {

        byte[] buff;
        int next, len;
        InputStream in;

        InputReader(InputStream in) { this(in, 1024); }

        InputReader(InputStream in, int buffSize) {
            this.buff = new byte[buffSize];
            this.in = in;
        }

        int read() {
            if (next >= len)
                try {
                    next = 0;
                    len = in.read(buff);
                    if (len == -1) return -1;
                } catch (IOException e) {}
            return buff[next++];
        }

        int nextInt() {
            int n = 0, c = read();
            while (c < '0' || c > '9') c = read();
            while (c >='0' && c <='9') {
                n = n * 10 + (c & 0xf);
                c = read();
            }
            return n;
        }
    }
}

我回来了

可以说,任何数据如果怎么来的,就怎么放到程序里面去,那不超时或者爆堆,那就是数据太小了

写的我不知所云,但总的来说就是尽可能的总结规律压缩数据

以测试数据为例:

x = 2x = 7
x + ax - b
x + a - bx - b - b
x + a - b + ax - b - b - b

markdown表格确实跟shi一样

到这里想必你就能明白 只能也必须选择 +a,-b 的用意了

a,b 任意组合他们的数量都会出现 n * (n - 1) / 2 次

这里我们将它记为 sum

如果出现 k 次 a,那就代表出现 sum - k 次 b

但最开始的搜索代码里,a与b的能构成不同组合的数量在 n2,所以现在问题就变成了

通过 a 的数量来计算它们能构成不同组合的个数
通过 k、m、sum 的和与 n 取模,并统计结果

统计组合个数套个 01 背板模板就行

int sum = (n - 1) * n / 2;
int[] dp = new int[sum + 1];
dp[0] = 1;
for (int i = 1; i < n; i++)
	for (int j = sum; j >= i; j--)
		dp[j] = (dp[j] + dp[j - i]) % mod;

计算与统计则是

for (int i = 0, j = sum; i <= sum; i++, j--)
	if ((m + a * i - b * j) % n == 0) cnt = (cnt + dp[i]) % mod;

成品 (AC)

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

public class Main {

    static final int mod = 100000007;

    public static void main(String[] args) {
        InputReader in = new InputReader(System.in, 128);
        int n = in.nextInt(), m = in.nextInt();
        long a = in.nextInt(), b = in.nextInt();
        int sum = (n - 1) * n / 2, dp[] = new int[sum + 1], cnt = 0;
        dp[0] = 1;
        for (int i = 1; i < n; i++)
            for (int j = sum; j >= i; j--)
                dp[j] = (dp[j] + dp[j - i]) % mod;
        for (int i = 0, j = sum; i <= sum; i++, j--)
            if ((m + a * i - b * j) % n == 0) cnt = (cnt + dp[i]) % mod;
        System.out.print(cnt);
    }

    static class InputReader {

        byte[] buff;
        int next, len;
        InputStream in;

        InputReader(InputStream in) { this(in, 1024); }

        InputReader(InputStream in, int buffSize) {
            this.buff = new byte[buffSize];
            this.in = in;
        }

        int read() {
            if (next >= len)
                try {
                    next = 0;
                    len = in.read(buff);
                    if (len == -1) return -1;
                } catch (IOException e) {}
            return buff[next++];
        }

        int nextInt() {
            int n = 0, c = read();
            while (c < '0' || c > '9') c = read();
            while (c >='0' && c <='9') {
                n = n * 10 + (c & 0xf);
                c = read();
            }
            return n;
        }
    }
}

最后优化一点直接丢上面了

ps:从结果来看优化了个激素拔刀,大概是因为耗时主要在 long 取模上把

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值