个人思路,仅作记录,可以参考,欢迎交流。
比赛地址:传送门
A. Digit Minimization
【题意】给定一个十进制不含0的正整数n和一种操作:先任意选择n的两位调换位置,再删去n的最后一位。求不断进行该操作直到n只剩一位数时这一位数最小是多少
【思路】通过调换尽量将所有位数字中的最小数字留下。可以证明当n的位数≥3时最小数字一定能留下,因为只要在n还剩3位时将最小数字换到第二位,在n还剩2位时将最小数字换到第一位即可。而当n=2时,留下的数一定是初始时的第一位。
【代码】
void solve()
{
int n;
cin >> n;
vector<int> v;
while (n)
{
v.push_back(n % 10);
n /= 10;
}
if (v.size() == 2)
{
cout << v[0] << '\n';
return;
}
cout << *min_element(v.begin(), v.end()) << '\n';
return;
}
B. Z mod X = C
【题意】给定满足a<b<c的正整数a,b,c,求一组正整数x,y,z,使得x%y=a, y%z=b, z%x=c
【思路】x,y,z在三个关系式中互相制约,环环相扣,难以确定。分析各数间的大小关系,发现需要y>a, z>b, x>c。
设想x>y>z=c>b>a的情况:此时x只受到第一个等式的限制,这样就可以轻易推得一组满足要求的x和y了,即y=z+b,x=y+a。
【代码】
void solve()
{
long long a, b, c;
cin >> a >> b >> c;
long long z = c;
long long y = z + b;
long long x = y + a;
cout << x << " " << y << " " << z << " \n";
return;
}
C. Column Swapping
【题意】给定一个n行m列的矩阵,求在最多交换一次矩阵任意两列的情况下,交换哪两列后矩阵可以满足每一行都为非减序列。
【思路】如果答案存在,矩阵的每一行一定只有0或2个数与矩阵排序后的样子不同。其中,若每一行都是排好序的,则答案直接为”1 1”;而若某一行有2个数不同,则这两个数所在的列就是唯一的可能的答案。
所以可以将每一行与其排序好的样子进行对比,找到一组不同的2列,并检查将这两列交换后整个矩阵是否是排好序的。
【代码】
vector<int> grid[200005];
vector<int> comp;
int n, m;
void solve()
{
cin >> n >> m;
for (int i = 1; i <= n; ++i)
{
grid[i].clear();
}
int t;
for (int i = 1; i <= n; ++i)
{
for (int j = 1; j <= m; ++j)
{
cin >> t;
grid[i].push_back(t);
}
}
if (m == 1)
{
cout << "1 1\n";
return;
}
bool sorted = 1;
int lef = -1, rig = -1, cnt = 0;
for (int i = 1; i <= n; ++i)
{
comp = grid[i];
sort(comp.begin(), comp.end());
for (int j = 0; j < m; ++j)
{
if (grid[i][j] != comp[j])
{
if (cnt == 0)
{
lef = j;
cnt++;
}
else if (cnt == 1)
{
rig = j;
cnt++;
}
else
{
cnt++;
break;
}
}
}
if (cnt)
{
sorted = 0;
}
if (cnt == 2)
{
break;
}
lef = rig = -1;
cnt = 0;
}
if (sorted)
{
cout << "1 1\n";
}
else if (lef >= 0 && rig >= 0)
{
for (int i = 1; i <= n; ++i)
{
swap(grid[i][lef], grid[i][rig]);
if (is_sorted(grid[i].begin(), grid[i].end()) == 0)
{
cout << "-1\n";
return;
}
}
cout << lef + 1 << " " << rig + 1 << '\n';
}
else
{
cout << "-1\n";
}
return;
}
D. Traps
【题意】你需要经过n个陷阱,已知经过每个陷阱会受到a[i]伤害。你最多能跳过k个陷阱,但每跳过一次会使后续的陷阱伤害都+1。求你全程受到的伤害的最小值。
【思路】先不考虑后续陷阱中也有陷阱被跳过的情况,此时跳过每个陷阱能获得的收益为a[i]-(n-i);再考虑跳过每个陷阱对跳过其他陷阱时伤害增加的缓解:在已经跳过m个陷阱的情况下,再跳过一个陷阱会让所有陷阱造成的伤害增加减少m,即有额外收益m。
所以做法为:先算出每个陷阱的基本收益a[i]-(n-i)并从大到小排序,然后逐个判断a[i]-(n-i)+m≥0是否成立,只要成立就选择跳过,这样就可以算得答案。
【代码】
long long a[200005];
void solve()
{
long long sum = 0;
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
sum += a[i];
a[i] -= n - i;
}
sort(a + 1, a + 1 + n, greater<long long>());
for (int i = 1; i <= k; ++i)
{
if (a[i] > -i)
{
sum -= a[i] + i - 1;
}
else
{
break;
}
}
cout << sum << '\n';
return;
}
E. MEX vs DIFF
【题意】给定长度为n的数组a,定义DIFF为数组中数值的种数,MEX为数组中最小的不存在的数。在最多能将a中k个数变为任意数的情况下,求DIFF-MEX的最小值
【思路】要想DIFF-MEX最小,就要DIFF尽量小,MEX尽量大。也就是要使a中数值多样性尽量降低,同时要尽量补全较小的不存在的数。分类讨论每种情况下的操作,发现只要是使MEX增加的操作对答案的贡献一定是非负面的,且增加MEX的代价一定不高于减少DIFF。所以只要算出k次操作能获得的最大MEX,并求此时最小的DIFF。
具体做法为:将a排序后从小到大遍历,找到填补k次能得到的最大MEX,然后统计比MEX大的数,找出这些数中出现频率最低的数,算出将k次操作用在它们身上最多让DIFF减小多少。最后答案正好为删去k个数后这些数中的种类数的最小值。细节见代码(虽然可读性有点差但我懒得再打文字了)。
【代码】
int n, k, a[100005] = { -1 };//a[0]设定为-1很重要
map<int, int> M;
vector<int> v;
void AddMap(int num)
{
if (M.find(num) == M.end())
{
M.insert({ num, 1 });
}
else
{
M[num]++;
}
return;
}
void solve()
{
M.clear();
v.clear();
cin >> n >> k;
for (int i = 1; i <= n; ++i)
{
cin >> a[i];
}
sort(a + 1, a + 1 + n);
//k次全花完一定没错
int quota = k;
int m = 0;
for (int i = 1; i <= n; ++i)
{
if (quota == 0 && a[i] > m)
{
AddMap(a[i]);
}
else if (a[i] > a[i - 1])
{
if (quota >= a[i] - a[i - 1] - 1)
{
quota -= a[i] - a[i - 1] - 1;
m = a[i] + 1;
}
else
{
m += quota;
quota = 0;
AddMap(a[i]);
}
}
}
for (auto it = M.begin(); it != M.end(); ++it)
{
v.push_back(it->second);
}
sort(v.begin(), v.end());
quota = k;
int cnt = v.size();
for (auto it = v.begin(); it != v.end(); ++it)
{
if (quota >= *it)
{
quota -= *it;
cnt--;
}
else
{
break;
}
}
cout << cnt << '\n';
return;
}
...