华为OD 数字游戏

1. 题意

题目描述
小明玩一个游戏:
系统发 1+n 张牌,每张牌上有一个整数。

第一张给小明(数字为 m);
后 n 张按发牌顺序排成连续一行。

需要判断:后 n 张牌中,是否存在连续的若干张牌,其和能整除小明手中牌的数字 m。
输入输出
输入
数据有多组,每组占两行,输入至文件结尾:
第一行:两个整数 n 和 m(空格分隔,m 是小明手中牌的数字);
第二行:n 个整数(空格分隔,代表后 n 张牌的数字)。
输出
对每组输入,若存在符合条件的连续子数组,输出 1;否则输出 0。
备注
数据范围:
1 ≤ n ≤ 1000;
牌上的整数 ≤ 400000;
输入组数 ≤ 1000;
输入格式保证正确,无需处理非法情况。

2. 题解

2.1 动态规划

自己想到的解法。定义 d p [ i ] [ j ] dp[i][j] dp[i][j]表示以 i i i个数为结尾的所有子串中是否存在和模 m m m j j j的子串。

状态转移方程
d p [ i + 1 ] [ ( j + a [ i ] )   m o d   m ] = d p [ i ] [ j ] dp[i+1][(j+a[i])\bmod m] =dp[i][j] dp[i+1][(j+a[i])modm]=dp[i][j]

  • 代码一
int n,m;
int ans;

int dp[1001][1001];
void solve1_1()
{
    memset(dp, 0, sizeof(dp));
    ans = 0;

    vector<int> a( n + 1, 0 );
    for (int i = 1;i <= n; ++i) {
        cin >> a[i];
    }

    dp[0][0] = 1;
    for (int i = 1; i <= n; ++i) {

        int vmod = a[i] % m;
        dp[i][vmod] = 1;
        for (int j = 1; j < m; ++j) {
            if (dp[i - 1][j]) {
                dp[i][ (vmod + j) % m] = dp[i - 1][j];
            }
        }
        if (dp[i][0]) {
            ans = 1;
            break;
        }
    }
}

时间复杂度 O ( n m ) O(nm) O(nm), 空间复杂度 O ( n m ) O(nm) O(nm)

由于我们只需要知道上一个状态,因此可以优化空间复杂度

  • 代码二
void solve1_2()
{
    vector<int> dp(m,0);
    vector<int> ndp(m, 0);

    ans = 0;

    vector<int> a( n + 1, 0 );
    for (int i = 1;i <= n; ++i) {
        cin >> a[i];
    }

    dp[0] = 1;
    for (int i = 1; i <= n; ++i) {

        int vmod = a[i] % m;
        ndp[vmod] = 1;

        for (int j = 0; j < m; ++j) {
            if (dp[j]) {
                ndp[(j + vmod) % m] = dp[j];
            }
        }
        dp = ndp;

        if (dp[0]) {
            ans = 1;
            break;
        }
    }
}

时间复杂度 O ( n m ) O(nm) O(nm), 空间复杂度 O ( m ) O(m) O(m)

2.2 前缀和

其实看了前缀和,也没有想到哈希表继续优化。

因此一开始只是写了前缀和加暴力枚举子串。

  • 代码三
void solve2_1()
{

    ans = 0;
    vector<int> a(n, 0);
    vector<int> pre(n + 1, 0);
    int sum = 0;

    for (int i = 0;i < n; ++i) {
        
        cin >> a[ i ];
        sum  = ( sum + a[i] ) % m;
        pre[ i + 1] = sum;
    }
    for (int i = 0; i < n; ++i) {
        for (int j = i + 1;j <= n; ++j) {
            int dif = (pre[j] + m - pre[i]) % m ;
            if ( 0 == dif) {
                ans = 1;
                break;
            }
        }
        if (ans) break;
    }
}

这道题目找到的最优解,是前缀和加哈希表。

我们定义前缀和pre

p r e [ i ] = ∑ j = 0 i − 1 a [ i ] ∑ k = i j a [ i ] = p r e [ j + 1 ] − p r e [ i ] pre[i]=\sum_{j=0}^{i-1}a[i]\\ \sum_{k=i}^j a[i] =pre[j+1] -pre[i] pre[i]=j=0i1a[i]k=ija[i]=pre[j+1]pre[i]

其实是利用了同余的性质,如果存在区间 [ i , j ] [i,j] [i,j]满足其和是 m m m的倍数,

那么一定有 p r e [ j + 1 ] − p r e [ i ] = k m pre[j+1] -pre[i]= km pre[j+1]pre[i]=km,

因此 p r e [ j + 1 ] ≡ p r e [ i ] (     m o d     m ) pre[j+1] \equiv pre[i]\quad (\ \bmod \ m) pre[j+1]pre[i]( mod m)

我们用哈希表记录下 p r e [ k ]   m o d   m pre[k] \bmod m pre[k]modm的值,如果后续出现了的相同的值 p r e [ k ′ ]   m o d   m pre[k'] \bmod m pre[k]modm。那么说明区间存在。

利用同余性质将找区间转换成了,找相同的模值。

p r e [ k ]   m o d   m = p r e [ k ′ ]   m o d   m pre[k] \bmod m =pre[k'] \bmod m pre[k]modm=pre[k]modm

  • 代码四
void solve2_2()
{

    vector<int> a(n, 0);
    // vector<int> pre(n + 1, 0);
    int sum = 0;
    unordered_set<int> pos;
    pos.insert(0);


    for (int i = 0;i < n; ++i) {
        cin >> a[ i ];
        sum  = ( sum + a[i] ) % m;
        if ( pos.count(sum)) {
            ans = 1;
        }
        else {
            pos.insert( sum );
        }
    }
}

时间复杂度 O ( n ) O(n) O(n), 空间复杂度 O ( m ) O(m) O(m)

3. 参考

KJ.JK

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值