第一题 A. Array Recovery - 链接Problem - A - Codeforces
1.题目描述
构造一个非负整数数组a, 使得a满足 即可, 同时要保证有唯一解。
2.思路
(1)模拟样例
d : 1 0 2 5
a : 1 1 + 0 (1 + 0) + 2 ((1 + 0) + 2) + 5
d : 2 6 3
a : 2 8 (11 or 5) <- 出现不唯一的解
(2)具体思路
看样例很像前缀和对吧, 那我们怎么判定是不是唯一解呢,我们注意到实际上是绝对值之差。例如 那么我们 都是没问题的对吧。这就是失去了唯一解,所以我们只要在做前缀合的路上看一看有没有这种情况就可以啦
3.AC代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
int n; cin >> n;
vector<int> a(n);
for(int i = 0; i < n; i ++) cin >> a[i];
vector<int> ans;
ans.push_back(a[0]);
for(int i = 1; i < n; i ++)
{
if(ans[i - 1] - a[i] < 0 || a[i] == 0) //判定是否有唯一解
{
ans.push_back(a[i] + ans[i - 1]); // 如果没有那就做前缀和
}
else
{
cout << -1 << endl;//有了就寄了
return;
}
}
for(int i = 0; i < n; i ++)
{
cout << ans[i] << " ";//然后输出一下我们的前缀和
}
cout << endl; // 记得回车
return;
}
signed main()
{
int _;
cin >> _;
while(_ --)
{
//cout << "--" << endl;
solve();
//cout << "---" << endl;
}
return 0;
}
4.注意事项
题目来讲是一个模拟,需要注意特判。
第二题 B. Reverse Sort 链接 - Problem - B - Codeforces
1.题目描述
给一个01组成的串, 可以选定下标并将其翻转(注意下标的选择可以不连续)。判断经过多次操作后 是否能变成一个升序的串(如从 101010 变成 000111)
2.思路
(1)e哥的标准做法
1.如果01串本来就是递增的, 如000111, 那么无需操作, 答案是0
2.题目要求操作后01串变为升序,于是我们可以知道:子序列里面肯定得有1也有
0,否则翻转没有意义。我们要做的是将右侧的0搬到左侧,左侧的1搬到右侧。
于是我们可以构造出一种操作方法,使得对于非特殊情况,一次操作一定可以使得串变为升序。
左右跑双指针即可,对于每一个左侧的1都有一个右侧的0与之对应即可。
(2)我的guesscodes做法
首先我们知道,一个01串一定可以被排成升序,那我们比较一下排序的前后有什么不同吧 如:00011010 -> 00000111
那么最显而易见的就是我们的0和1个数没变(好, 废话是吧),然后就是0一定在前面,1一定在后面(好,又是废话),那么把我们的废话综合一下也就是说,我们最后要让所有的0在前面所有的1在后面。同时,位置不对的0和1一定是成对出现的(我们例子里面的4,5,6,7)就是如此。只要统计出0有多少,那么前多少就是应该0(本题前五个应该是0)之后再统计前五个哪里不是零(如4, 5)那么统计出来的就是答案啦。
3.AC代码
(1)e哥代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 1e6 + 9;
char s[N];
void solve()
{
int n;cin >> n;
cin >> s + 1;
bool tag = true;//tag表示01串为升序
for(int i = 2;i <= n; ++ i)if(s[i] < s[i - 1])tag = false;
if(tag)cout << 0 << '\n';
else
{
vector<int> v;
int l = 1, r = n;
while(l < r)
{
while(l < r && s[l] == '0')l ++;
while(l < r && s[r] == '1')r --;
if(l < r)//注意一定要保证l < r才能加到答案里
{
v.push_back(l), v.push_back(r);
l ++, r --;
}
}
//输出前记得排序
sort(v.begin(), v.end());
cout << 1 << '\n' << v.size() << ' ';
for(const auto &i : v)cout << i << ' ';
cout << '\n';
}
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int _;cin >> _;
while(_ --)solve();
return 0;
}
(2)我的代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
int n; cin >> n;
string st; cin >> st;
int a = 0, b = 0;
for(int i = 0; i < n; i ++)//统计
{
if(st[i] == '0') a ++;
else b ++;
}
vector<int> ans;
for(int i = 0; i < a; i ++)//看不合事宜的(doge)
{
if(st[i] != '0')
{
ans.push_back(i + 1);
}
//cout << "i = " << i << endl;
}
for(int i = a; i < n; i ++)
{
if(st[i] != '1')
{
ans.push_back(i + 1);
}
//cout << "i = " << i << endl;
}
//cout << ans.size() << endl;
if(ans.size() == 0)
{
cout << 0 << endl;
return;
}
cout << 1 << endl;
cout << ans.size() << " ";
for(int i = 0; i < ans.size(); i ++)
{
cout << ans[i] << " ";
}
cout << endl;
}
signed main()
{
int _; cin >> _;
while(_ --)
{
solve();
}
return 0;
}
4.注意事项
(其实是一点心得)e哥的思维是一种规范的,明了的,通用的思维。而显然我并没有(哭)
第三题 C. Make It Round 链接 - Problem - C - Codeforces
1.题目描述
给两个数n, m 在 k <= m的条件下求 n * k 之后后面零最多的里面最大的那个
2.思路
这个先说思路在模拟,便于理解
(0)质因数分解介绍
把合数表示为质因数乘积的形式叫做“分解质因数”
(1)边说思路边模拟
首先想的一定是暴力枚举,但我们注意到在一个1e9的范围暴力显然是不现实的
那么我们做一个转换的思维,我们不是需要更多的 0 吗,0是什么呢,是 10的多少次方, 那10是由什么组成的呢 2 * 5。
1000 -> 10³ -> 2³ * 5³ // 推理过程
pow(10, n) -> pow(2, x) * pow(5, y) * p; // 一般规律, p为分解质因数剩下的
//模拟
20 -> 2² * 5 x = 2 y = 1 p = 1 此时 k = 1; k = min(x, y)
这需要我们想方设法让min(x , y)变大 也就是在k <= m时,先尽量让 x == y, 然后再 x和y同时增大,即 k *= 10。
那让我们模拟一下以便思考
n = 4 m = 16
//分解质因数
n = pow(2, 2) * pow(5, 0) * 1 此时 x = 2 y = 0 位数0的个数为0
//让x, y尽量相等 我们定义 k 为扩大的倍数 int k = 1;
n * k= pow(2, 2) * pow(5, 1) * 1 此时 x = 2 y = 1 k = 5 位数0的个数为 1
//虽然x != y 但是 k = 5 的情况下 k 没办法再扩大5倍(这样 k = 25 > m)
//所以我们目前的答案是 n * k = 20
难道20就是答案了吗,20是最大值吗,当然不是 我们的40,60都比20更好,显然我们还没完事。 首先因为我们尽可能的扩大k(为了凑 0),所以 m / k一定小于10,小于10的数一定不会影响0的数量。那我们还能扩大 [m / k] 倍
20 * (m / k) -> 20 * (16 / 5) -> 20 * 3 = 60
所以此题答案为 60
3.AC代码
#include<bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
int n, m; cin >> n >> m;
int k = 1;
int c2 = 0, c5 = 0;
int nn = n;
while(n % 2 == 0) c2 ++, n /= 2;
while(n % 5 == 0) c5 ++, n /= 5;
while(c2 < c5 && k * 2 <= m) c2 ++, k *= 2;
while(c5 < c2 && k * 5 <= m) c5 ++, k *= 5;
while(k * 10 <= m) k *= 10;
cout << nn * k * (m / k) << endl;
}
signed main()
{
int _; cin >> _;
while( _ --)
{
solve();
}
return 0;
}
4.注意事项
还是要考虑全面吧,我做题的时候一度忽略了最后的 * (m / k)
第四题 D. Great Sequence 链接-Problem - D - Codeforces
1.题目描述
提供n, m 找到即为配对,问有多少不能配对的。
2.思路
(1)大体思路
大体思路很容易想到将元素存在map中,进行查找。但我们要注意查找的顺序,如:
1 16 4 4
//如果直接按顺序查找
1 -> 4
16 -> ?
我们知道是4去对应16,而不是16去对应4。所以应先sort一下
(2)模拟一下
9 10
10 10 10 20 1 100 200 2000 3
//sort
1 3 10 10 10 20 100 200 2000
//对应
1 -> 10 // 3 10 10 20 100 200 2000
3 -> ? ans ++; // 10 10 20 100 200 2000
10 -> 100 // 10 20 200 2000
10 -> ? ans ++;// 20 200 2000
20 -> 200 // 2000
2000 -> ? ans ++;
so ans = 3;
3.AC代码
我的map版本
#include<bits/stdc++.h>
using namespace std;
#define int long long
void solve()
{
int n, p; cin >> n >> p;
vector<int> a(n);
map<int , int> mp;
for(int i = 0; i < n; i ++)
{
cin >> a[i];
mp[a[i]] ++;
}
int ans = 0;
sort(a.begin(), a.end());
for(int i = 0; i < n; i ++)
{
if(mp[a[i]] != 0)
{
if(mp[a[i] * p] != 0)
{
mp[a[i] * p] --;
mp[a[i]] --;
}
else
{
mp[a[i]] --;
ans ++;
//cout << a[i] << endl;
}
}
}
cout << ans << endl;
}
signed main()
{
int _; cin >> _;
while(_ --)
{
solve();
}
return 0;
}
e哥的
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
void solve()
{
ll n, x;cin >> n >> x;
multiset<ll> st;
for(int i = 1;i <= n; ++ i)
{
ll y;cin >> y;
st.insert(y);
}
int ans = 0;
while(st.size())
{
ll y = *st.begin();
if(st.find(y * x) == st.end())//如果x * y不存在
{
st.erase(st.begin());
ans ++;
}else
{
//注意这里不能写st.erase(x * y),这会把所有x * y都删除
//但是我们只想删除一个
st.erase(st.find(x * y));
st.erase(st.begin());
}
}
cout << ans << '\n';
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int _;cin >> _;
while(_ --)solve();
return 0;
}
4.注意事项
没有啥hhh
第五题 E. Playing in a Casino 链接-Problem - E - Codeforces
1.题目描述
求
2.思路
首先我们可以发现,这个式子的每一列都相互独立,且对于每一列来说,它的结果是与顺序无关的,所以我们可以对于每一列都单独排序,然后计算每一段对于答案的贡献。
那我们怎么算贡献呢 ↓
2,3,1,5
|2 - 3| + |2 - 1| + |2 - 5| + |3 - 1| + |3 - 5| + |1 - 5|
= 1 + 1 + 3 + 2 + 2 + 4 = 13
但这样是不是太慢了,所以e哥教了区间模型↓
1 2 3 5
↑ ↑
j-1 j
//他们所有的区间和可用公式计算 (v[j] - v[j - 1]) * j * (n - j)
3.AC代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const ll N = 1e6 + 9;
vector<vector<ll> > v;
void solve()
{
int n, m;cin >> n >> m;
//v有m列
v.clear();
v.resize(m + 1);
for(int i = 1;i <= n; ++ i)
{
for(int j = 1;j <= m; ++ j)
{
int x;cin >> x;
v[j].push_back(x);
}
}
ll ans = 0;
for(int i = 1;i <= m; ++ i)
{
//引用类型t = v[i]
vector<ll>& t = v[i];
sort(t.begin(), t.end());
for(int j = 1;j < n; ++ j)
{
//j是左端点的个数,n - j为右端点的个数
ans += 1ll * j * (n - j) * (t[j] - t[j - 1]);
}
}
cout << ans << '\n';
}
int main()
{
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
int _;cin >> _;
while(_ --)solve();
return 0;
}