题解:P14118 [SCCPC 2021] Hotpot
题解:P14118 [SCCPC 2021] Hotpot
题意
给定 \(T\) 组测试数据,每组测试数据情景如下:
\(0\) 到 \(n-1\) 共 \(n\) 位游客,\(k\) 种火锅食材,\(m\) 次操作,第 \(i\) 次操作(\(0 \le i < m\))是由 \(i \mod n\) 号游客执行。每位游客有自己喜欢的食材 \(a_i\)(\(1 \le a_i \le k\)),初始幸福值均为 \(0\),火锅为空。
操作规则:
执行操作的游客 \(t\) ,若其最喜欢的食材 \(a_t\) 已经在火锅中,则 \(t\) 会吃掉所有 \(a_t\),自身幸福值 \(+1\),否则 \(t\) 会向火锅中放入 \(1\) 份 \(a_t\),自身幸福值不变。
请你输出每位游客的幸福值。
数据规模与约定
\(1 \le T \le 10^3\);
\(1 \le n \le 10^5,1 \le k \le 10^5,1 \le m \le 10^9\);
\(1 \le a_i \le k\);
所有测试数据 \(\sum \limits _{i=1} ^T n_i \le 2 \times 10^5,\sum \limits _{i-1} ^T k_i \le 2 \times 10^5\)。
题解
做法一 直接模拟
用 std::map 维护火锅,可以发现时间复杂度是 \(O(m)\),但是 \(m\) 可能会到达 \(10^9\) 级别,在 #2 测试点会 TLE。
做法二 数学方法
通过对题目的观察,我们可以发现如下:
\(1.\) 食材之间具有独立性:食材之间不会互相影响。
\(2.\) 单个食材具有独一无二的操作序列:
对于食材 \(x\) ,它的操作序列是 \(O_x=[O_0,O_1,\cdots,O_{k_x-1}]\)(\(k_x\) 为喜欢 \(x\) 的游客数)。所以,所有对 \(x\) 的操作,本质是执行对 \(x\) 的操作序列。
\(3.\) 操作具有奇偶性规律:
关于食材 \(x\) 的操作序列 \(O_x\),分析在序列下标(\(0\) 开始)上的操作效果:
- 下标为偶数,可以发现在奇数次时已经把食材吃掉了,所以只能向火锅里添加食材,对幸福值没有贡献。
- 下标为奇数,可以发现在偶数次时已经添加了食材,所以需要执行吃掉的操作,幸福值 \(+1\)。
所以,根据我们的发现,可以对算法进行建模:
把食材 \(x\) 的操作拆成完整轮次和不完整轮次,分别进行计算(具体看代码)。
CODE
#include <bits/stdc++.h>
using namespace std;
/*====================*/
using ll=long long;
/*====================*/
#define endl '\n'
/*====================*/
void Solve()
{
int n,k,m;
cin>>n>>k>>m;
vector<int> a(n);
for(auto& i:a)cin>>i;
unordered_map<int,vector<int>> mp;// 按喜欢的食材分组
for(int i=0;i<n;i++)
{
mp[a[i]].push_back(i);
}
vector<int> h(n,0);// 记录幸福值
for(auto it=mp.begin();it!=mp.end();it++)
{
int kx=it->second.size();// 喜欢这个食材的人数
ll cnt_op=0;// 该食材操作的总次数
// 计算每个游客在 m 次操作中出现的总次数
for(auto i:it->second)
{
if(i>=m)continue;// 游客 i 的首次操作已经超过 m
cnt_op+=(m-1-i)/n+1;
}
if(cnt_op==0)continue;// 无操作直接跳过
// 拆分完成轮次和剩余轮次
int full=cnt_op/kx;
int rem=cnt_op%kx;
// 处理完整轮次的贡献
if(kx%2==0)// 食材操作序列长度为偶数:奇数索引操作(吃食材)每轮贡献1次
{
for(int j=0;j<kx;j++)
{
if(j&1)
{
h[it->second[j]]+=full;
}
}
}
else // 食材操作序列长度为奇数:轮次奇偶影响贡献
{
for(int j=0;j<kx;j++)
{
if(j&1)
{
h[it->second[j]]+=(full+1)/2;
}
else
{
h[it->second[j]]+=full/2;
}
}
}
// 处理剩余操作的贡献
int base=(1LL*full*kx)%2;
for(int j=0;j<rem;j++)
{
if((base+j)&1)
{
h[it->second[j]]++;
}
}
}
for(int i=0;i<n;i++)
{
cout<<h[i]<<" ";
}
cout<<endl;
}
/*====================*/
int main()
{
ios::sync_with_stdio(false);
cin.tie(nullptr),cout.tie(nullptr);
int T=1;cin>>T;
while(T--)Solve();
return 0;
}
注:本文已被 luogu 审核通过至P14118 题解区。

浙公网安备 33010602011771号