CF1394A(枚举case贪心,倒着dp)
CF1394A(枚举case贪心,倒着dp)
题意
你在群里开 \(n\) 个玩笑,每个玩笑可以获得 \(a_i\) 快乐值,但是如果 \(a_i > m\) ,狗管理就会给你一发 \(d\) 天的口球。在禁言期间无法发言。
现在找到玩笑方案使得快乐值的和最大。
(每个玩笑仅可以说一次)
思路一(贪心)
如果给定 \(x\) 次说被禁言的玩笑,可以很简单构造出贪心解。因此只要枚举 \(x\) 对每种情况取 \(max\) 就行。
感觉和平时贪心套路有点差别,一般贪心都是找到一个贪心策略+边界case讨论或者二分+贪心check。这里则是因为直接贪会陷入分类讨论的漩涡。虽然不能二分答案,但我们可以 枚举case !
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<random>
#include<iomanip>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
#define int long long
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;
void solve()
{
int n,m,d; cin >> n >> d >> m;
vector<int> a,b;
for(int i = 0;i < n;i += 1) {
int t; cin >> t;
if(t > m) a.push_back(t);
else b.push_back(t);
}
if(a.size() == 0) {
cout << accumulate(b.begin(),b.end(),0ll);
return;
}
sort(a.begin(),a.end(),greater<int>());
sort(b.begin(),b.end(),greater<int>());
int sza = a.size(),szb = b.size();
vector<ll> sa(sza + 1),sb(szb + 1);
for(int i = 1;i <= sza;i += 1) sa[i] = sa[i - 1] + a[i - 1];
for(int i = 1;i <= szb;i += 1) sb[i] = sb[i - 1] + b[i - 1];
ll ans = 0;
for(int i = (sza + d) / (d + 1);i <= sza ; i += 1) {
if(n - (i - 1) * (d + 1) - max(1ll,sza - (i - 1) * (d + 1)) >= 0) {
// cout << i << " " << n - (i - 1) * (d + 1) - max(1ll,sza - (i - 1) * (d + 1)) << endl;;
ll res = sa[i] + sb[n - (i - 1) * (d + 1) - max(1ll,sza - (i - 1) * (d + 1))];
ans = max(res,ans);
}
}
cout << ans;
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
//int T;cin>>T;
//while(T--)
solve();
return 0;
}
思路二(dp)
做dp的基础一定要知道初始状态解,这里的初始状态在最后一次玩笑,最后一次一定选择快乐值最大的玩笑,因为之后被禁言也无所谓,所以这里是无代价的。这样就有了倒着dp的基础。进而的我们可以把序列翻转,变成一个前缀的问题,就是一个很顺眼的dp了。
对这个问题其实就是从两个集合选数的问题,我们考虑朴素定义 \(dp[i][j][k]\) 表示考虑前 \(i\) 个玩笑用了 \(j\) 个禁言玩笑和 \(k\) 个非禁言玩笑的答案。这显然是可做的,但会时空都不可以接受
这里的优化技巧是考虑进行多个dp
我们修改定义为:\(dp[i][0/1/2]\) 分别表示答案,用了前 \(dp[i][1]\) 的禁言玩笑和 \(dp[i][2]\) 的非禁言玩笑。
考虑转移
对位置 \(i\) 分三类
- 开一个禁言玩笑,则它会从 \(i - d - 1\) 转移过来并且保证此时有玩笑可开
- 开一个非禁言玩笑,它会从 \(i - 1\) 转移并且保证此时有玩笑可开
- 不开玩笑,或者认为做完dp后这里随便填一个,反正贡献为0
按上面三类做如下dp即可
f[i][0] = f[i - 1][0];
f[i][1] = f[i - 1][1];
f[i][2] = f[i - 1][2];
if(f[i - 1][2] < szb ) {
f[i][0] = f[i - 1][0] + b[f[i - 1][2]];
f[i][1] = f[i - 1][1];
f[i][2] = f[i - 1][2] + 1;
}
if(f[i - 1][1] < sza and i - d - 1 > 0) {
if(f[i][0] < f[i - d - 1][0] + a[f[i - d - 1][1]]) {
f[i][0] = f[i - d - 1][0] + a[f[i - d - 1][1]];
f[i][1] = f[i - d - 1][1] + 1;
f[i][2] = f[i - d - 1][2];
}
}
完整代码:
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<vector>
#include<set>
#include<queue>
#include<map>
#include<stack>
#include<string>
#include<random>
#include<iomanip>
#define yes puts("yes");
#define inf 0x3f3f3f3f
#define ll long long
#define linf 0x3f3f3f3f3f3f3f3f
#define ull unsigned long long
#define endl '\n'
#define int long long
#define rep(i,a,n) for(int i = a;i <= n;i++)
#define per(i,n,a) for(int i = n;i >= a;i--)
using namespace std;
mt19937 mrand(random_device{}());
int rnd(int x) { return mrand() % x;}
typedef pair<int,int> PII;
const int MAXN =10 + 2e5 ,mod=1e9 + 7;
ll f[MAXN][3];
void solve()
{
int n,m,d; cin >> n >> d >> m;
vector<int> a,b;
for(int i = 0;i < n;i += 1) {
int t; cin >> t;
if(t > m) a.push_back(t);
else b.push_back(t);
}
if(a.size() == 0) {
cout << accumulate(b.begin(),b.end(),0ll);
return;
}
sort(a.begin(),a.end(),greater<int>());
sort(b.begin(),b.end(),greater<int>());
int sza = a.size(),szb = b.size();
f[1][0] = a[0];
f[1][1] = 1;
f[1][2] = 0;
for(int i = 2;i <= n;i += 1) {
f[i][0] = f[i - 1][0];
f[i][1] = f[i - 1][1];
f[i][2] = f[i - 1][2];
if(f[i - 1][2] < szb ) {
f[i][0] = f[i - 1][0] + b[f[i - 1][2]];
f[i][1] = f[i - 1][1];
f[i][2] = f[i - 1][2] + 1;
}
if(f[i - 1][1] < sza and i - d - 1 > 0) {
if(f[i][0] < f[i - d - 1][0] + a[f[i - d - 1][1]]) {
f[i][0] = f[i - d - 1][0] + a[f[i - d - 1][1]];
f[i][1] = f[i - d - 1][1] + 1;
f[i][2] = f[i - d - 1][2];
}
}
}
cout << f[n][0];
}
signed main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
//int T;cin>>T;
//while(T--)
solve();
return 0;
}
感觉把这个题意改成,有两个集合,第一个集合选数和上一个距离不小于 \(d\) 第二个集合则没有限制,一共 \(n\) 个空可以选。这样的题意可能就能一眼dp了。
这题把初始状态翻了一下而且原题说的是安排一个最优排列,反而有点迷惑性没反应过来是dp