ErikTse2023Codeforces新手思维提升集训营第二场题解

第一题 A. Array Recovery - 链接Problem - A - Codeforces

        1.题目描述

                构造一个非负整数数组a, 使得a满足 a_{1} = b_{1} \quad b_{i} = |a_{i} - a_{i - 1}| 即可, 同时要保证有唯一解。

        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)具体思路

                看样例很像前缀和对吧, 那我们怎么判定是不是唯一解呢,我们注意到b_{i}实际上是绝对值之差。例如b_{i} = 3 \quad a_{i - 1} = 5 那么我们 a_{i} = 2 or8 都是没问题的对吧。这就是失去了唯一解,所以我们只要在做前缀合的路上看一看有没有这种情况就可以啦

        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 找到a_{i} = a_{?} * 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.题目描述

                   求            \sum_{k=1}^n\sum_{i=1}^m\sum_{j=i+1}^m|a[i] - a[j]|

        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;
}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值