牛客周赛 Round 99 EF题解
E、小宇
题意:
对于一个序列,选择一个x,定义将所有a[i]=x变为i为一次操作,问将序列变为严格递增序列的最少操作次数。
思路:
首先可以发现,若一个数字在序列中出现两次及以上,一定需要选择此数字(否则这些位置的值相等,永远不可能严格递增)。然后考虑仅出现一次的数字,每一位都有两种状态:
①操作这个数字
②不操作这个数字
故考虑dp,用prechange.cost表示更改第i个元素,得到严格递增序列的最少操作次数。用prekeep.cost表示不操作第i个元素,得到严格递增序列的最少操作次数
得到状态转移方程:
\[nowkeep.cost =
\begin{cases}
min(prechange.cost, prekeep.cost)
\end{cases}
\]
\[nowchange.cost =
\begin{cases}
min(prechange.cost, prekeep.cost) + extra
\end{cases}
\]
(由前一个数两种状态转移而来)
对于extra,若此元素出现两次及以上,在第一步已经统计过,此处置0。否则通过更改cost加1。
注意只有仅出现一次的元素才能够保留,以及初始状态的考虑。
代码。
#include<bits/stdc++.h>
#define ll long long
#define ce cerr
#define ull unsigned long long
#define lll __int128
using namespace std;
const int inf = 0x3f3f3f3f;
const ll iinf = 1e18;
//cin.ignore(std::numeric_limits< streamsize >::max(), '\n');
int t;
struct node {
int now, cost;
};
void solve() {
int n;
cin >> n;
vector<int> a (n + 1);
map<int, int> mp;
int res = 0;
for (int i = 1; i <= n; ++i) {
cin >> a[i];
mp[a[i]] ++;
if (mp[a[i]] == 2) res ++;
}
node prekeep = {a[1], 0} , prechange = {1, mp[a[1]] > 1 ? 0 : 1};
for (int i = 2; i <= n; ++i) {
node nowkeep, nowchange;
nowkeep = {a[i], inf};
nowchange = {a[i], inf};
int extra = mp[a[i]] > 1 ? 0 : 1;
if (mp[a[i]] == 1) {
if (a[i] > prekeep.now) nowkeep.cost = min(nowkeep.cost, prekeep.cost);
if (a[i] > prechange.now) nowkeep.cost = min (nowkeep.cost, prechange.cost);
if (nowkeep.cost != inf) nowkeep.now = a[i];
}
if (i > prekeep.now) nowchange.cost = min (nowchange.cost, prekeep.cost + extra);
if (i > prechange.now) nowchange.cost = min (nowchange.cost, prechange.cost + extra);
if (nowchange.cost != inf) nowchange.now = i;
prekeep = nowkeep;
prechange = nowchange;
}
cout << res + min (prechange.cost, prekeep.cost) << "\n";
return ;
}
int main() {
ios::sync_with_stdio (false);
cin.tie(NULL);
cout.tie(NULL);
t = 1;
cin >> t;
while (t --) {
solve();
}
return 0;
}
F、汉堡猪猪分糖果
题意:
n颗糖果,分给m个小朋友。
每个小朋友至少要分到一颗糖果,如何分配才能让所有小朋友分到的糖果数量的按位与最大?
思路:
考虑逐位决策:
设答案为 ans。若 ans 的第 i 位想为 1,则 每个 小朋友都必须至少再得到 2^i 颗糖;否则这位必然为 0。
把二进制高位先确定下来,对已确定的高位保证“以后再怎么分剩下的糖,也不会把这些位冲掉”,就能得到最优解。
分三种情况讨论:
\[\begin{cases}
n \geq x \cdot m & \text{(第一种情况)} \\
(x - 1) \cdot m < n < x \cdot m & \text{(第二种情况)} \\
n \leq (x - 1) \cdot m & \text{(第三种情况)}
\end{cases}
\]
对于第一种情况,所有人再发 x 颗,能保证第 i 位全为 1,于是 ans|=x。
对于第二种情况,必须“削减”一部分剩余,把 n 降到 < m·(x-1),保证不变量,为之后低位决策扫清隐患。
对于第三种情况,人均剩余本来就不到 x-1,继续向低位判定(不操作)。
TLE代码
void solve() {
int n, m;
cin >> n >> m;
int res = 0;
for (int i = 30; i >= 0; --i) {
int x = 1 << i;
if (n >= x * m) {
res |= x;
n -= x * m;
}else if (n > (x - 1) * m) {
ll q = (x - 1) * m;
while (n > q) {
n -= x;
}
}
}
cout << res << "\n";
}
其实不难看出:
要减去的整块数量 = ceil((n - (x-1)*m) / x)
n -= 要减去的整块数量 * 整块的大小(x)
正确代码
#include<bits/stdc++.h>
#define ll long long
#define ce cerr
#define ull unsigned long long
#define lll __int128
using namespace std;
#define int long long
const int inf = 0x3f3f3f3f;
const ll iinf = 1e18;
//cin.ignore(std::numeric_limits< streamsize >::max(), '\n');
int t;
void solve() {
int n, m;
cin >> n >> m;
int res = 0;
for (int i = 30; i >= 0; --i) {
int x = 1 << i;
if (n >= x * m) { // 情况一,每人还能至少分到 x 颗, 先把这 x 颗固定发下去
res |= x;
n -= x * m;
}else if (n > (x - 1) * m) {// 情况二,削掉若干个整 x,使剩余 < (x-1)*m
ll q = (x - 1) * m;
while (n > q) {
n -= ((n - (x - 1) * m) + x - 1) / x * x;
}
}
// 情况三,否则什么也不用做
}
cout << res << "\n";
}
signed main() {
ios::sync_with_stdio (false);
cin.tie(NULL);
cout.tie(NULL);
t = 1;
cin >> t;
while (t --) {
solve();
}
return 0;
}