[CF988E] Divisibility by 25 - 贪心
Description
给出一个从 1 到 10^18 的整数 n,不包含前导零。在一次移动中,可以交换任意两个相邻数字,使得结果数字不包含前导零。获取可被 25 整除的数字所需的最小移动次数是多少?
Solution
就那么几种情况
- 把 5 冒泡到最后,把 2 冒泡到倒数第二位
- 把 5 冒泡到最后,把 7 冒泡到倒数第二位
- 把 0 冒泡到最后,把 5 冒泡到倒数第二位
- 把 0 冒泡到最后,把另一个 0 冒泡到倒数第二位
我们可以先尝试性地做一下,如果换完以后有前导零,那么再强行找一个最左边的非零数字换到前面来,如果换完以后炸了那么这种情况就无效
可以证明先把非零数字换到前面,和先把有效数字换到后面,得到的答案是一样的
找到最靠右的两个我们要找的数字,下标为 i,j,假设我们的目标是把 j 换到最后,i 换到倒数第二个位置
如果只有 i,j 两个数字,皆大欢喜(这时 i,j 不可能都是 0)
如果除了 i,j 外的第一个数字是 0,我们要找到第一个非零的数字,如果找到了记录它的下标 k,如果没找到直接结束
现在我们是把 j 换到最后,i 换到倒数第二个位置,如果有的话,把 k 换到第一个位置
这个过程中,会出现的基础贡献为 n-j+n-1-i+k-1
考虑相互作用导致的附加贡献,如果 (k>i) 则附加贡献,如果 (k>j) 则附加贡献,如果 (i>j) 则附加贡献
#include <bits/stdc++.h>
using namespace std;
#define int long long
int solve(string s, int ii, int jj)
{
int n = s.length();
int i = -1, j = -1, k = -1;
for (int t = s.length() - 1; t >= 0; t--)
if (s[t] == '0' + jj)
{
j = t;
break;
}
for (int t = s.length() - 1; t >= 0; t--)
if (s[t] == '0' + ii && t != j)
{
i = t;
break;
}
for (int t = 0; t < s.length(); t++)
if (s[t] != '0' && t != i && t != j)
{
k = t;
break;
}
++i;
++j;
++k;
if (i == 0 || j == 0)
{
return 1e9;
}
if (s.length() > 2)
{
int pin = 1;
if (pin == i)
++pin;
if (pin == j)
++pin;
if (pin == i)
++pin;
if (pin == j)
++pin;
--pin;
if (pin < s.length() && s[pin] == '0')
{
if (k)
{
int ans = n - j + n - 1 - i + k - 1 - (k > i) - (k > j) + (i > j);
return ans;
}
else
{
return 1e9;
}
}
}
int ans = 0;
ans = n - j + n - 1 - i + (i > j);
return ans;
}
signed main()
{
ios::sync_with_stdio(false);
int ans = 1e9;
string s;
cin >> s;
ans = min(ans, solve(s, 0, 0));
ans = min(ans, solve(s, 2, 5));
ans = min(ans, solve(s, 5, 0));
ans = min(ans, solve(s, 7, 5));
if (ans < 1e8)
cout << ans << endl;
else
cout << -1 << endl;
}