ErikTse2023Codeforces思维提升赛(2)

A.Array Recovery
题意

给定一个长度为n的数组d,请你构造一个唯一的非负整数数组a,满足:

d1 = a1,di=|ai - a(i - 1)|;

思路

代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
const int N = 109;
int a[N], d[N];

void solve()
{
    int n;cin >> n;
    for(int i = 1;i <= n; ++ i)cin >> d[i];
    a[1] = d[1];
    for(int i = 2;i <= n; ++ i)
    {
        if(d[i] && a[i - 1] - d[i] >= 0)
        {
            cout << -1 << '\n';
            return;
        }
        a[i] = a[i - 1] + d[i];
    }
    for(int i = 1;i <= n; ++ i)cout << a[i] << ' ';
    cout << '\n';
}

int main()
{
    int _;cin >> _;
    while(_ --)solve();
    return 0;
}
B.Reverse Sort
题意

给定一个长度为n的0101串,每次操作可以选择一段“非递增”的子序列,并将其翻转,问是否存在一种方案使整个0101串变为升序(类似0000111100001111的串)。

思路

先把特殊情况特判,就是0101串一开始就已经是递增的了,那么无需操作,操作次数为0。

对于这种可能有若干次操作的题目,我们可以假定它“一定可以在一次(或两次)操作中完成”,于是问题就变为了如何构造出一种仅需一次的操作方法。

题目要求操作后0101串变为升序,于是我们可以知道:

1.子序列里面肯定得有11也有00,否则翻转没有意义。

2.我们要做的是将右侧的00搬到左侧,左侧的11搬到右侧。

于是我们可以构造出一种操作方法,使得对于非特殊情况,一次操作一定可以使得串变为升序。

左右跑双指针即可,对于每一个左侧的11都有一个右侧的00与之对应即可。

代码
#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;
}
C.Make It Round
题意

给定两个整数n,m,你可以对n乘上一个整数k(1≤≤)k(1≤k≤m),问选哪个k,可以使得n×k的结果的尾部的00最多。

如果多种方案尾部00的数量相等,输出结果较大的。

思路

因为这题的m范围较大,且样例数比较多,所以逐个枚举k显然是会超时的。

于是我们去分析一下"尾部0的个数"与哪些变量有关,这个思想实际上也是我第一场的A中所说过的,将一个抽象的东西,转化为具体的可量化的变量来分析。

每多一个00,说明n×k中就多一个因子1010,也就是说n×k的质因数中就多一对$(2, 5)。

再稍微分析一下,我们设f(x,i)表示x中质因子i的个数,那么:

cnt0​=min(f(nk,2),f(nk,5))

并且有一个数学规律:

f(xy,i)=f(x,i)+f(y,i)

n是一个确定的数字,所以f(n,2)和f(n,5)是确定的,可以先计算出来,我们要做的,就是构造一个数字k,它只有22和55两种质因子(因为其他质因子是没有价值的,还会让数字变大),并且保证k≤m,先让f(nk,2)和f(nk,5)相等,然后又再对k持续乘10即可。

现在还有一个问题,就是题目要求这个nk尽可能大,我们做完前面的操作后1≤k≤m,这里可能还存在一些变大的空间,于是我们可以计算出来,假设k还可以扩大p倍,于是有:

k×p≤m

p≤⌊km​⌋

最后给答案乘上一个⌊k/m​⌋即可,在这里扩大p倍是一定不会改变尾部0的个数的。

代码
#include <bits/stdc++.h>
using namespace std;
using ll = long long;


void solve()
{
	ll n, m;cin >> n >> m;
	ll k = 1, t = n;//t用于计算c2和c5
	int c2 = 0, c5 = 0;
	while(t % 2 == 0)t /= 2, c2 ++;
	while(t % 5 == 0)t /= 5, c5 ++;
	
	while(k * 2 <= m && c2 < c5)k *= 2, c2 ++;
	while(k * 5 <= m && c5 < c2)k *= 5, c5 ++;
	while(k * 10 <= m)k *= 10;
	
	
	cout << n * k * (m / k) << '\n';
}

int main()
{
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
    int _;cin >> _;
    while(_ --)solve();
    return 0;
}
D.Great Sequence
题意

给定两个整数n,x和一个长度为n数组a,问你至少新增多少个数字,可以使得数组中的数字可以两两配对,并使得每一个二元组(t1​,t2​)满足:t1​×x=t2​。

思路

代码
#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;
}
E. Playing in a Casino (数学贡献法)
核心思想

贡献法的核心思想就是,将原本难以计算的整体拆分为较小的部分,并从小部分出发,去计算每一个部分被计算过多少次,从而加速计算。

代码
#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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值