以前是写过的,但现在旧题重做又有了新的体会!
一、题目
二、解法
先讲一种我自己 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});
}
}