2019上海网络赛 Stone Game (背包dp)

传送门
题意:n个石头,每个石头有一个重量ai, 所有石头重量之和为S, 要求从中选一些石头,被选石头重量和为S’, 满足S’ >=S-S’ 且 从S’中去掉任何一个石头,上式不成立,也就是选的石头的重量和刚好>=总重量的一半。问共有多少种选择方案。
思路: 把序列通过贪心法进行预处理,即由大至小排序,然后设计状态dp[i][j]表示前i个石头里面选取若干个,使总和为j的方案个数。这样的状态转移方程dp[i][j] = dp[i-1][j] + dp[i-1][j-a[i]]。这种题型我是第一次做,因为不能单单的只是通过最终的结果来得出答案,因为你并不知道是否满足题目所给的条件,所以很巧妙的方法就是在过程中收集答案。对于一个状态dp[i][j]而言,为了不重复计算,我们只考虑一定选第i个的情况,这样如果j >= sum-j && j-a[i] <= sum-j, 则一定选i的方案数可以包含在答案中。
这种题目还是靠经验比较多,因为一般在设计状态的时候都是想怎么直接得出答案,但却可以设计一个较为简单的状态,然后得出答案

#include
#include
#include
#include
#include
using namespace std;
const long long MOD = 1e9+7;
long long dp[150000]; //dp[i][tot]表示考虑前i个石子,总数为tot,满足的条件数
long long a[305];
long long prefix[305];
bool cmp(long long a, long long b){
     
    return a > b;
}
int main(){
     
    int t;
    cin >> t;
    while(t--){
     
        memset(dp, 0, sizeof(dp));
        int n;
        cin >> n;

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

        long long ans = 0;
        dp[0] = 1;
        for(int i = 1; i <= n; ++i){
     
            for(int tot = sum; tot >= a[i]; --tot){
     
                dp[tot] = (dp[tot]+dp[tot-a[i]]) % MOD;
                if(tot >= sum-tot && tot-a[i] <= sum-tot)
                    ans = (ans + dp[tot-a[i]]) % MOD;
            }
        }

        cout << ans << endl;
    }
    return 0;
}

你可能感兴趣的:(2019上海网络赛 Stone Game (背包dp))