[LOJ 6254] 最优卡组

博主旧题重做有新体会,介绍了一道题的解法。先提出自己错误但有启发的做法,用优先队列爆搜需满足两个条件,此方法会超时。正解是对原方法第二部操作优化,按最大值 - 次大值从小到大排序,改动转移方式,保证复杂度为 O(nlogn),还会贴错解和正解代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以前是写过的,但现在旧题重做又有了新的体会!

一、题目

点此看题

二、解法

先讲一种我自己 y y \tt yy yy 的做法,虽然是错的,但是有启发意义。

爆搜是不可能爆搜的, ∏ c i \prod c_i ci 是不可能爆搜的,这种题一般需要用优先队列爆搜。但这种方式很苛刻,必须要同时满足两个条件:不会算重,权值总和从大到小,下文主要解决这两个问题。

我们定义三元组表示 ( x , y , s , z ) (x,y,s,z) (x,y,s,z) 搜到第几个卡组的第几张牌, s s s 表示卡牌权值总和, z z z 表示是否能计入答案(因为我的做法会算重),初始状态时全部都选最大值,然后就涉及到两种转移方式:选当前卡组的下一张卡牌,可以计入答案;进入下一张卡组的第一张卡牌,不可计入答案。

看上这种方法满足上述两个条件,但是会超时,因为第二种操作相当于向后跳到一个新的卡组开始选择,所以由于权值从大到小的性质会导致第二种操作批量执行,这样不计入答案的状态就会很大 T L E \tt TLE TLE


那么正解其实是对我们方法第二部操作的优化,我们必须要让访问到的每一个状态计入答案。而二操作的第一个计入答案的状态是把卡组的第一个卡牌换成第二个卡牌,那我们能不能跳过二操作而直接执行这个有效的换牌操作呢?

不行,因为无法保证这样产生的结果是最大的,因为该卡组的 最大值 − - 次大值 不是最小的。等等,要求差量最小,那不是排序可以解决的问题吗,所以我们要按 最大值 − - 次大值 从小到大排序呀。

那么排序就帮我们解决了计入答案的问题,转移要略做改动,因为我做法的操作二是可以选择进入后面所有的卡组之一,所以转移变成了这样:

  • 去掉当前卡组的这张卡而选择下一张卡。
  • 进入下一个卡组,从次大值开始选。
  • 如果上一次是进入下一个卡组的操作,那么把上一个卡组重新调回最大值,这一个卡组从最大值开始选。

这样就满足了两个条件且保证了复杂度是 O ( n log ⁡ n ) O(n\log n) O(nlogn) 的。


为了好理解,我会贴两份代码,请注意,第一份是错解

#include <cstdio>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
const int M = 100005;
#define ll long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k;ll sum;vector<int> v[M];
struct node
{
	int x,y;ll s;int z;//这个状态是第几个卡组,第几张卡,总和
	bool operator < (const node &b) const
	{
		return s<b.s;
	}
};priority_queue<node> q;
signed main()
{
	n=read();k=read();
	for(int i=1;i<=n;i++)
	{
		int x=read();
		while(x--) v[i].push_back(read());
		sort(v[i].begin(),v[i].end());
		sum+=v[i][v[i].size()-1];
	}
	q.push(node{1,v[1].size()-1,sum,0});
	while(k>0)
	{
		node t=q.top();q.pop();
		if(t.x<n)//能转移过去
			q.push(node{t.x+1,v[t.x+1].size()-1,t.s,1});
		if(t.y>0)
			q.push(node{t.x,t.y-1,t.s-v[t.x][t.y]+v[t.x][t.y-1],0});
		if(!t.z) printf("%lld ",t.s),k--; 
	}
}
#include <cstdio>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
const int M = 100005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k,sum,l[M],p[M];vector<int> v[M];
struct node
{
	int x,y,s,z;//这个状态是第几个卡组,第几张卡,总和
	bool operator < (const node &b) const
	{
		return s<b.s;
	}
};priority_queue<node> q;
bool cmp(int x,int y)
{
	return v[x][l[x]]-v[x][l[x]-1]<v[y][l[y]]-v[y][l[y]-1];
}
signed main()
{
	n=read();k=read();
	for(int i=1;i<=n;i++)
	{
		int x=read();
		while(x--) v[i].push_back(read());
		sort(v[i].begin(),v[i].end());
		l[i]=v[i].size()-1;p[i]=i;
		sum+=v[i][l[i]];
		if(l[i]==0)
		{
			v[i].clear();i--;n--;
			continue; 
		}
	}
	sort(p+1,p+1+n,cmp);
	q.push(node{1,l[p[1]],sum,0});
	while(k--)
	{
		node t=q.top();q.pop();
		int x=p[t.x],y=t.y,to=p[t.x+1];
		printf("%lld ",t.s);
		if(t.y>0)
			q.push(node{t.x,y-1,t.s-v[x][y]+v[x][y-1],0});
		if(t.x<n)
			q.push(node{t.x+1,l[to]-1,t.s-v[to][l[to]]+v[to][l[to]-1],1});
		if(t.x<n && t.z==1)//可以撤销
			q.push(node{t.x+1,l[to]-1,t.s-v[x][y]+v[x][y+1]-v[to][l[to]]+v[to][l[to]-1],1}); 
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值