题解 | #数组分组#

数组分组

http://www.nowcoder.com/practice/9af744a3517440508dbeb297020aca86

题意

一个数组分成两组

  1. 5的倍数在一组
  2. 是3的倍数 且 不是5的倍数 在另一组
  3. 剩余数字自由分组

是否能让分成的两组数字的和相等

限制:数组长度不大于50,数值绝对值不大于500

方法

动态规划

先不考虑限制,分成两组和相等,也就是两组数字和的差为0,即是第一组的和 - 第二组的和 = 0

再看限制,不妨设,5的倍数在第一组,3的倍数 且 不是5的倍数在第二组

那么 第一组的和 - 第二组的和 = 0 变为

(5的倍数的和) - (3的倍数 且 不是5的倍数 的和) + (第一组剩余的和) - (第二组剩余的和) = 0


这里如果把和展开,实际上就是第一组的值全部 在表达式中是加法,第二组的值全部是减法

先处理掉确定部分的值,对于剩余值建立新数组

对于新数组设计状态

dp[i][j]=dp[i][j] = 前i个数的和是否能达到j

有转移方程 dp[i][j]=dp[i1][jval[i]]ordp[i1][j+val[i]]dp[i][j] = dp[i-1][j-val[i]] or dp[i-1][j+val[i]]


以题目样例数据为例

1 5 -5 1

首先我们把5的倍数的和放在第一组,是0.

然后,将 3的倍数 且 不是5的倍数 的和 放在第二组,也是0

上述的这个表达式 (5的倍数的和) - (3的倍数 且 不是5的倍数 的和) + (第一组剩余的和) - (第二组剩余的和) = 0

变为 0 - 0 + (第一组剩余的和) - (第二组剩余的和) = 0

新的数组为剩下的值1 1

第几个数 -2 -1 0 1 -2
初始化 false false true(初始化 (5的倍数的和) - (3的倍数 且 不是5的倍数 的和) 可达) false false
0 false true false true false
1 true false true false true

这样,通过转移方程处理所有值后,最后的下标为0时,为可达,所以存在方案


注意到上面的运算可能会有负值,而数组下标一般时非负数,所以考虑增加一个偏移量,让所有值都非负,因为时偏移,所以对转移方程并不影响

代码

#include<bits/stdc++.h>
using namespace std;

int main(){
    int n;
    while(~scanf("%d",&n)){
        int diff = 0;
        vector<int> arr;
        for(int i =0;i<n;i++){
            int v;
            scanf("%d",&v);
            if(v%5==0)diff+=v; // 第一组 含5
            else if(v%3==0)diff-=v; // 第二组 不含5且含3
            else arr.push_back(v);
        }
        // sum(group[1]) - sum(group[2]) == 0
        const int N = 50*500*2; // 50*500*2 = 50000
        // dp[idx][sum(group[1]) - sum(group[2]) + 50000/2] = bool
        vector<vector<bool> >dp(60,vector<bool>(N+10,false));
        dp[0][N/2 + diff] = true; // 下标 +平移25000 (偏移量)
        for(int i = 0;i<arr.size();i++){
            for(int v = 0; v <= N;v++){
                if(!dp[i][v]) continue;
                // 可达值
                if( v - arr[i] >= 0 && v - arr[i] <= N){ // 放在第二组
                    dp[i+1][v - arr[i]] = true;
                }
                if( v + arr[i] >= 0 && v + arr[i] <= N){ // 放在第一组
                    dp[i+1][v + arr[i]] = true;
                }
            }
        }
        printf("%s\n",dp[arr.size()][N/2]?"true":"false" ); // 因为加了偏移量 所以时判断 0 + N/2 = N/2
    }
    return 0;
}

复杂度分析

时间复杂度: 判断了所有位置,所有可能可达值是否可达,转移代价为常数,所以时间复杂度为O(n2val)O(n^2\cdot val)

空间复杂度: 主要消耗在,dp设计的状态储存,所以空间复杂度为O(n2val)O(n^2 \cdot val)

滚动数组+DP空间优化

注意到 状态转移只和前一轮状态相关,所以可以考虑,把上上次的状态空间进行复用, 因此采取mod 2 的方式来复用数组。

需要注意的是,每次使用前记得清空数组。

代码

#include<bits/stdc++.h>
using namespace std;

int main(){
    int n;
    while(~scanf("%d",&n)){
        int diff = 0;
        vector<int> arr;
        for(int i =0;i<n;i++){
            int v;
            scanf("%d",&v);
            if(v%5==0)diff+=v; // 第一组 含5
            else if(v%3==0)diff-=v; // 第二组 不含5且含3
            else arr.push_back(v);
        }
        // sum(group[1]) - sum(group[2]) == 0
        const int N = 50*500*2; // 50*500*2 = 50000
        // dp[idx][sum(group[1]) - sum(group[2]) + 50000/2] = bool
        vector<vector<bool> >dp(2,vector<bool>(N+10,false)); // 使用滚动数组
        dp[0][N/2 + diff] = true; // 下标 +平移25000
        for(int i = 0;i<arr.size();i++){
            for(int v = 0; v <= N;v++){ // 因为是复用,注意清空
                dp[(i+1)%2][v] = false; // 坐标取 mod 2
            }
            for(int v = 0; v <= N;v++){
                if(!dp[i%2][v]) continue; // 坐标取 mod 2
                // 可达值
                if( v - arr[i] >= 0 && v - arr[i] <= N){ // 放在第二组
                    dp[(i+1)%2][v - arr[i]] = true; // 坐标取 mod 2
                }
                if( v + arr[i] >= 0 && v + arr[i] <= N){ // 放在第一组
                    dp[(i+1)%2][v + arr[i]] = true; // 坐标取 mod 2
                }
            }
        }
        printf("%s\n",dp[arr.size()%2][N/2]?"true":"false" ); // 坐标取 mod 2
    }
    return 0;
}

复杂度分析

时间复杂度: 判断了所有位置,所有可能可达值是否可达,转移代价为常数,所以时间复杂度为O(n2val)O(n^2\cdot val)

空间复杂度: 主要消耗在,dp设计的状态储存,所以空间复杂度为O(nval)O(n \cdot val)

全部评论

相关推荐

Yushuu:你的确很厉害,但是有一个小问题:谁问你了?我的意思是,谁在意?我告诉你,根本没人问你,在我们之中0人问了你,我把所有问你的人都请来 party 了,到场人数是0个人,誰问你了?WHO ASKED?谁问汝矣?誰があなたに聞きましたか?누가 물어봤어?我爬上了珠穆朗玛峰也没找到谁问你了,我刚刚潜入了世界上最大的射电望远镜也没开到那个问你的人的盒,在找到谁问你之前我连癌症的解药都发明了出来,我开了最大距离渲染也没找到谁问你了我活在这个被辐射蹂躏了多年的破碎世界的坟墓里目睹全球核战争把人类文明毁灭也没见到谁问你了😆
点赞 评论 收藏
分享
点赞 2 评论
分享
牛客网
牛客企业服务