K 小马哥的超级盐水 分治+暴力

链接: https://www.nowcoder.com/acm/contest/94/K
来源:牛客网

时间限制:C/C++ 5秒,其他语言10秒
空间限制:C/C++ 131072K,其他语言262144K
64bit IO Format: %lld

题目描述

小马哥有 杯盐水,第 杯有 单位的盐和 单位的水。小马哥很无聊,于是他想知道有多少种这 杯盐水的非空子集,倒在一起之后盐和水的比是

输入描述:

 
 
输入第一行包含一个整数 ,代表数据组数。
每组数据第一行包含三个整数 表示 的最大公约数。
接下来 行,第 行包含两个整数

输出描述:

每组数据输出一行,包含一个整数表示非空子集的个数。
示例1

输入

1
5 1 2
1 2
1 2
1 2
1 2
1 4

输出

15

2^35次方暴力肯定超时,但是用用分治,将问题1分为2,那么2^17和2^18次方这就不会超时了,题目给了5s。对分成的两部分进行dfs,对于一瓶盐水,都有拿和不拿两个状态,dfs正常思路就可以了 。比较麻烦的是,如何处理x/y的问题,其实这个问题就是将一棵树,分为两颗子树i,j,先对i进行操作,满足ai/bi==x/y 即 ai*y-bi*x==0,这就是满足条件的,而且不满足条件的ai*y-bi*x也要记录下来,因为减法肯定有正有负,因此我们不能用数组,要用map(映射) ,先把所有i树的所有条件都记录下来,那么接下来对j树的处理就是关键了。————大佬教我的!!!

首先我们先看,j树直接符合x/y的,假如说i树有n种,j树有m种,那么单纯符合x/y的组合就有n*m种,但是我们条件不能直接相乘,我们应该用加例如说,对于i树 ,f[0]=3,我们找到一个符合的值那么我们应该用sum+=f[0],如果在找到一个,那么继续sum+=f[0]。

接着,我们看如果两瓶盐水单独不符合,但是混合后符合的情况,i树元素和j树元素如果(ai+aj)/(bi+bj)==x/y 那么我们就有ai*y-bi*x==-aj*y+bj*x ,因此只要满足这个条件我们就可以混合起来,算一种了。也就是说,当求出j树的一个元素为-aj*y+bj*x, 那么f[-aj*y+bj*x]就是找到它对应在i树里混合后等于x/y的值,也就是f[ai*y-bi*x],那么直接sum+=f[-aj*y+bj*x]就可以得到它们总共混合后的种类数,其实也就是相当于上面的n*m。因此j树的边界操作应该是sum+=f[-aj*y+bj*x].

最后一点就是,我们这样做在第i树集合里面我们会有一种情况就是所有水都不取,j树也一样,那么这就是空集了,因此我们算出来的总个数还要-1才是所求的

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <map>
#define ll long long
using namespace std;
map<ll,ll>f;
int n,m,x,y;
ll a[40],b[40],sum;
void dfs(ll v1,ll v2,int step)
{
    if(step==m)
    {
        f[v1*y-v2*x]++;
        return;
    }
    dfs(v1+a[step],v2+b[step],step+1);
    dfs(v1,v2,step+1);
}
void DFS(ll v1,ll v2,int step)
{
    if(step==n)
    {
        sum+=f[-v1*y+v2*x];
        return;
    }
    DFS(v1+a[step],v2+b[step],step+1);
    DFS(v1,v2,step+1);
}
int main()
{
    int t,i;
    scanf("%d",&t);
    while(t--)
    {
        f.clear();
       sum=0;
        scanf("%d%d%d",&n,&x,&y);
        for(i=0; i<=n-1; i++)
            scanf("%d%d",&a[i],&b[i]);
        m=n/2;
        dfs(0,0,0);
        DFS(0,0,m);
        printf("%lld\n",sum-1);
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值