JZOJ2935. 【USACO Open 2012 Gold Division】Balanced Cow Subsets

该博客介绍了USACO Open 2012 Gold Division的一道竞赛题,题目要求从n个数中选择若干数,使得它们能被分成和相等的两组。博主分享了利用meet in the middle策略(折半搜索)来解决这道问题的方法,通过枚举每半部分数的系数,并在满足和相等时进行组合,从而减少计算复杂度并实现解题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目大意

n个数,从中任意选出一些数,使这些数能分成和相等的两组。
求有多少种选数的方案。

Data Constraint
n20

题解

这题用到了 meet in the middle 的思想,或者说是折半搜索。
先将n分为两半,每一半O(3n2)枚举每一个数的系数1/0/1。然后把式子写出来可以发现,当左边的和=右边的和的时候,就是用左边1与右边1分在一组。然后暴力维护一下,去重。

SRC

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std ;

#define N 20 + 10
#define M 2000 + 10
#define K 60000 + 10
typedef long long ll ;
struct Stype {
    ll x ;
    ll s ;
} s1[K] , s2[K] ;

bool vis[M][M] ;
ll a[N] ;
ll c[K] , d[K] ;
int n , tot1 , tot2 , Sign = 0 ;
ll ans ;

bool cmp( Stype a , Stype b ) { return a.s < b.s ; }

void DFS( int k , int m , ll sum , ll sta ) {
    if ( k > m ) {
        if ( !Sign ) s1[++tot1].x = sta , s1[tot1].s = sum ;
        else s2[++tot2].x = sta , s2[tot2].s = sum ;
        return ;
    }
    for (int c = -1 ; c <= 1 ; c ++ )
        DFS( k + 1 , m , sum + a[k] * c , sta | ((ll)(c != 0) << (k - Sign - 1)) ) ;
}

void Calc() {
    for (int i = 1 ; i <= c[0] ; i ++ ) {
        for (int j = 1 ; j <= d[0] ; j ++ ) {
            if ( vis[c[i]][d[j]] ) continue ;
            vis[c[i]][d[j]] = 1 ;
            ans ++ ;
        }
    }
}

int main() {
    scanf( "%d" , &n ) ;
    for (int i = 1 ; i <= n ; i ++ ) scanf( "%lld" , &a[i] ) ;
    DFS( 1 , n / 2 , 0 , 0 ) ;
    Sign = n / 2 ;
    DFS( n / 2 + 1 , n , 0 , 0 ) ;
    sort( s1 + 1 , s1 + tot1 + 1 , cmp ) ;
    sort( s2 + 1 , s2 + tot2 + 1 , cmp ) ;
    if ( s1[1].s < s2[1].s ) swap( s1 , s2 ) , swap( tot1 , tot2 ) ;
    int j = 1 ;
    vis[0][0] = 1 ;
    for (int i = 1 ; i <= tot1 && j <= tot2 ; ) {
        while ( s1[i].s > s2[j].s && j <= tot2 ) j ++ ;
        if ( j > tot2 ) break ;
        if ( s1[i].s < s2[j].s ) { i ++ ; continue ; }
        c[0] = d[0] = 1 ;
        c[1] = s1[i].x ;
        d[1] = s2[j].x ;
        int k ;
        for (k = i + 1 ; k <= tot1 ; k ++ ) {
            if ( s1[k].s != s1[i].s ) { k -- ; break ; }
            c[++c[0]] = s1[k].x ;
        }
        i = k + 1 ;
        for (k = j + 1 ; k <= tot2 ; k ++ ) {
            if ( s2[k].s != s2[j].s ) { k -- ; break ; }
            d[++d[0]] = s2[k].x ;
        }
        j = k + 1 ;
        Calc() ;
    }
    printf( "%lld\n" , ans ) ;
    return 0 ;
}

以上.

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值