题目名称:音乐问题 问题描述: 强迫症患者HH只有在歌曲结束时处于整点时间(即分钟和秒均为0)才能停止听歌 他初始时间为12:00:00,且一旦开始听歌就只能通过满足上述条件的方式停止 给定若干首歌曲的时长,请判断是否存在一种选歌方式(每首歌最多选一次),使得HH能在某个整点时间停止听歌。

输入描述: 第一行包含一个正整数 T(1 ≤ T ≤ 60),表示测试用例的数量。 每个测试用例包含两行:

第一行是一个整数 n(1 ≤ n < 10⁵),表示歌曲数量。

第二行包含 n 个整数 a₁, a₂, ..., aₙ(1 ≤ aᵢ ≤ 10⁶),表示每首歌曲的时长(单位:秒)。 输出描述: 对于每个测试用例,输出一行 "YES" 或 "NO",表示是否存在符合条件的选歌方式

对于1e5的数据与1e9的时长。。。。。

常规方法是用一个dp[n]来说明余数为n的有几个(有没有),能想到这个主要是因为这个很容易得出状态转移来:假设有1000,2000,3000。1000加入,dp[1000]为1,之后2000加入,dp[2000]为1,遍历有dp[1000]为1,那么dp[3000]也为1

不难看出,对于每一个我们都需要遍历一遍看原来有没有,时间复杂度就是极高的O(T * n * 3600),实际上有很多操作都是没有必要的,这里有两个很有效的优化方法:

1:鸽巢原理的使用

结论就是:对于任意个含n个正整数的数列,一定至少有一个子集有其sum%n==0

证明也很简单,设前缀和为Si = a1+a2+......+ai,对每个Si取MOD,也就是有可能为0,1,2......n-1——鸽巢原理则必然会有两个前缀和对应的余数是一样的。如果就是0,那最好。如果不是0,那么这两个前缀和相减就可以了

修补骑士不擅长数学,大家看个乐呵就可以了。这也就说明了本题只要是n>=3600的都可以直接输出YES

2:bitset的运用

botset真是个好东西吧,位运算的高速性决定了他在时空上都很有优势

结合代码讲解一下具体是怎么使用的:

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

signed main() 
{
    ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    
    int T; 
    
    cin >> T;
    
    for(int t = 0;t < T;t++)
    {        
        bitset<8888> bs;
        
        int n,x;
        
        cin>>n;
        
        vector<int> num(n,0);
        
        for(int r = 0;r <n;r++)
        {
            cin>>num[r];
        }
        
        if(n >= 3600)
        {
            cout<<"YES"<<endl;
            
            continue;
        }
        
        for(int r = 0;r < n;r++)
        {
            x = num[r];
            
            x = x % 3600;
            
            bs |= (bs << x) | (bs >> (3600 - x));
            
            bs[x] = 1;
        }
        
        if(bs[0] == 1)
        {
            cout<<"YES"<<endl;
        }
        else
        {
            cout<<"NO"<<endl;
        }
    }
    
    return 0;
}

关于位运算基础大家可以去找点视频看看,这里讲解最关键的步骤:bs |= (bs << x) | (bs >> (3600 - x));

左边的bs<<x,也就是假设有某一位bs[j] = 1(余数为j的存在),那么左移后就是bs[j + x] = 1,与我们写暴力背包的思路完全相同

另外一边的右移有些出乎意料,首先我们对于x取模,之后对于3600 - x,所有小于这个的会变成负数不用管,大于这个的,就说明他加上x后会变成超过3600的数字,我们就需要减少3600 - x来完成近似取模的操作(这个可以自己打个表,当然最重要的还是要提前取模)

不要忘记对于自己要单独标注出来,之后用|把所有1位次合并拿到就可以了

所以说bitset真是个好东西吧,在动态规划里真的很常用,大家闲的没事干可以把自己用常规方法写出来的DP又用bitset优化来写一遍,就算这不是必要的,也可以加深印象。毕竟,学算法,要看也要敲啊(笑)