hdu 4334 Trouble 题解(记忆化,单调性,思维)

原题链接:
HDU

题意简述

多组数据。给定一个 n ( n &lt; = 200 ) n(n&lt;=200) n(n<=200),接下来 5 5 5行,每行有 n n n个数(每个数的绝对值 &lt; = 1 0 15 &lt;=10^{15} <=1015),表示 5 5 5个集合。你要求出,能否在 5 5 5个集合中都选一个数,使得和为 0 0 0

数据

输入

第一行是一个正整数 T T T表示有 T T T组数据。接下来给定 T T T组数据,每个数据有 6 6 6行,先是一行一个 n n n,然后是 5 5 5行,每行 n n n个数。

输出

输出 T T T行。对于每组数据,输出答案。能凑出 0 0 0输出 Y e s Yes Yes,不能输出 N o No No

样例

输入
2
2
1 -1
1 -1
1 -1
1 -1
1 -1
3
1 2 3
-1 -2 -3
4 5 6
-1 3 2
-4 -10 -1
输出
No
Yes

思路

显然有一个暴力的想法: O ( n 5 ) O(n^5) O(n5)枚举。显然,这个是过不去的。。。

那么,如果我们能枚举一半,然后用记录的方法直接求得另一半呢?

如果是第一次做这种题应该很难能想到有记忆化的方法。不过,能做出来这种题,要么就是你天才,第一眼看到就能做出来;要么就是你努力,做过类似的题,然后也能像天才一样一眼看出来怎么做。

我们有这样一个记忆化的思路:枚举第 2 , 3 2,3 2,3个集合中所有的和,记录到 v 1 v1 v1。枚举第 4 , 5 4,5 4,5个集合中所有的和,记录到 v 2 v2 v2。然后,我们只要枚举第一个集合,再枚举 v 1 , v 2 v1,v2 v1,v2中的元素,判断是否和为 0 0 0

珂是, v 1 , v 2 v1,v2 v1,v2总各有 n 2 n^2 n2个元素,这样枚举,不还是 O ( n ∗ n 2 ∗ n 2 ) = O ( n 5 ) O(n*n^2*n^2)=O(n^5) O(nn2n2)=O(n5)的么?

我们会发现, v 1 , v 2 v1,v2 v1,v2中的元素,顺序不重要,怎么排列都不会影响。所以,我们令 v 1 , v 2 v1,v2 v1,v2都升序排列。设枚举的第一个元素中的数是 a a a v 1 v1 v1中枚举的是 b 1 b1 b1 v 2 v2 v2中枚举的是 b 2 b_2 b2。当我们不断向后枚举 b 1 b1 b1的时候, b 1 b1 b1是递增的,而且此时 a a a没有变,那么不难证明,如果有满足条件的 b 2 b2 b2,那么位置一定不会更后,一定会往前。

所以我们只要记录好我们枚举到 v 2 v2 v2的哪个位置,每次 − − -- 即珂,而不是重新找。这样就 O ( n 2 l o g n + n 3 ) O(n^2logn+n^3) O(n2logn+n3)了。

代码:

#include<bits/stdc++.h>
using namespace std;
namespace Flandle_Scarlet
{
    #define int long long
    #define N 40100
    int v1[N];int p1=0;
    int v2[N];int p2=0;//v1和v2,p1和p2表示用到了哪个位置

    int n;
    int a[6][N];//5个集合
    void Input()
    {
        scanf("%lld",&n);
        for(int i=1;i<=5;++i)
        {
            for(int j=1;j<=n;++j)
            {
                scanf("%lld",&a[i][j]);
            }
        }
    }

    void Build()
    {
        p1=p2=0;
        for(int i=1;i<=n;++i)
        {
            for(int j=1;j<=n;++j)
            {
                ++p1;
                v1[p1]=a[1][i]+a[2][j];

                ++p2;
                v2[p2]=a[3][i]+a[4][j];
            }
        }
        sort(v1+1,v1+p1+1);
        sort(v2+1,v2+p2+1);//记得排序
    }

    void Solve()
    {
        int ans=0;
        for(int i=1;i<=n;++i)
        {
            int pos=p2;//记录v2用到了哪个位置
            for(int j=1;j<=p1 and pos>=1;++j)
            {
                while(a[5][i]+v1[j]+v2[pos]>0) --pos;
                //不断往前找,因为显然pos往后+是没有答案的
                if (a[5][i]+v1[j]+v2[pos]==0)
                {
                    ans=1;//如果找到了就记录答案
                }
            }
        }
        printf("%s\n",ans==1?"Yes":"No");
    }
    void Main()
    {
        if (0)
        {
            freopen("","r",stdin);
            freopen("","w",stdout);
        }
        int t;scanf("%lld",&t);
        while(t--)
        {
            Input();
            Build();
            Solve();
        }
    }
};
main()
{
    Flandle_Scarlet::Main();
    return 0;
}

回到总题解界面

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值