Music Problem 背包dp +(抽屉原理 || 二进制优化)

本文针对MusicProblem问题,提出三种高效算法解决方案。首先介绍了一种基于抽屉原理的数学优化方法,适用于n大于等于3600的情况;其次,提出了利用二进制优化思路来处理一般情况,有效提升了求解效率;最后,介绍了使用betset进行状态压缩优化的方法,进一步减少了计算资源消耗。

Music Problem
思路:
常规思路:
模数取 3600 3600 3600,很容易想到 T ∗ n ∗ 3600 T*n*3600 Tn3600 的做法,空间上优化第一维,但时间复杂度太大
数学优化:
题解有人讲到抽屉原理:一个由 n n n 个数组成的数列 一定能找出若干个连续的数使它们之和能被 n n n 整除。特判掉 n > = 3600 n>=3600 n>=3600 的情况,常规思路就可以通过了,我直呼 N B NB NB,不愧是清华的大佬
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 3600;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
int f[2][4000];

void work()
{
	cin >> n;
	if(n >= 3600){
        for(int i = 1, x; i <= n; ++i) cin >> x;// 这里要读完再输出,不然会wa
        cout << "YES" << endl;return;
    }
	for(int i = 0; i <= mod; ++i)
		for(int j = 0; j < 2; ++j)
			f[j][i] = 0;
	int op = 1;
	for(int i = 1, x; i <= n; ++i, op ^= 1){
		cin >> x;
		x %= mod;
		f[op][x] = 1;
		for(int j = mod - 1; j >= 0; --j){
			f[op][j] = max(f[op][j], f[op^1][j]);
			int k = (x + j) % mod;
			f[op][k] = max(f[op][k], f[op^1][j]);
		}
	}
	cout << (f[op^1][0] ? "YES" : "NO") << endl;
}

int main()
{
	ios::sync_with_stdio(0);
	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

二进制优化思路:
a i   %   m o d a_i \ \% \ mod ai % mod 分成 m o d mod mod 组,然后一组有 c i c_i ci 个数,然后类似多组背包二进制优化的方法,可以很快的通过,很好用的优化思路,可以直接莽这个方法
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 3600;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
int f[2][4000];
int cnt[4000];
void work()
{
	cin >> n;
	for(int i = 0; i < mod; ++i)
		for(int j = 0; j < 2; ++j)
			f[j][i] = 0;
	for(int i = 0; i < mod; ++i) cnt[i] = 0;
	for(int i = 1, x; i <= n; ++i) cin >> x, cnt[x%mod]++;
	if(cnt[0]){// 速度大幅提升主要是因为这个
		cout << "YES\n";return;
	}
	int op = 1;
	for(int i = 1; i < mod; ++i){
		if(!cnt[i]) continue;
		vector <int> v;
		int c = cnt[i];
		for(int j = 1; j <= c; j <<= 1)
		{
			v.push_back(i * j % mod);
			c -= j;
		}
		if(c) v.push_back(i * c % mod);
		for(auto d : v)// 把c拆成 v.size() 个数,用这几个数进行dp
		{
			f[op][d] = 1;
			for(int j = mod - 1; j >= 0; --j)
			{
				f[op][j] = max(f[op][j], f[op^1][j]);
				int k = (d + j) % mod;
				f[op][k] = max(f[op][k], f[op^1][j]);
			}
			op ^= 1;// 因此 op 在这
		}
	}
	cout << (f[op^1][0] ? "YES" : "NO") << endl;
}

int main()
{
	ios::sync_with_stdio(0);
	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

betset优化:
因为只有 3600 3600 3600 个数,可以用 b i t s e t bitset bitset 实现状压
因为要 + j +j +j,所以把上一层状态所有数都左移 j j j 位,原来位置是 i i i 1 1 1,或一下就可以转移到 i + j i+j i+j
因为加的过程中会取模
在这里插入图片描述
因此 a a a 这段 + x +x +x 取模会到 b b b 这段,所以右移 m o d − x mod-x modx 位就可以把 a a a 这段转移到 b b b
code:

#include<bits/stdc++.h>
#define endl '\n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 4e3 + 9;
const int mod = 3600;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;

void work()
{
	cin >> n;
	bitset <maxn * 2> f;// 一般开个乘2就行
	for(int i = 1, x; i <= n; ++i){
		cin >> x; x %= mod;
		f |= f << x | f >> (mod - x);
		f[x] = 1;
	}
	cout << (f[0] ? "YES" : "NO") << endl;
}

int main()
{
	ios::sync_with_stdio(0);
	int TT;cin>>TT;while(TT--)
	work();
	return 0;
}

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值