【CF】Day12——Codeforces Round 910 (Div. 2) BCD + Codeforces Round 911 (Div. 2) CD

B. Milena and Admirer

题目:

思路:

想到了,但是代码细节没处理好

既然我们要将数组变成升序,并且要求最小值,那么肯定要贪心一点

我们可以从后开始枚举每一个数,同时定义一个mx代表数组的最大值,如果当数小于mx,说明符合,就不需要更改,同时将mx变为这个数,反之肯定要将这个数进行操作了,同时将mx变为操作后的数

思路是这样,那么如何实现呢?

首先我们要分解的话肯定是尽量分成较大的数,比如5分成2和3,8分成4和4,那么如何分呢?

我们可以利用mx,首先肯定是要分解成小于或等于mx的数,假设要分成k份

那么就是有 [x/k] <= mx,其中[]代表向上取整,因为比如5,分成两份是2和3,我们要保证较大值也要小于mx,那么转化一下式子,就是 [x / mx] <= k 那我们要尽量少切,也就是取等号即可,那么mx要变为什么呢?其实就是 【x / k】其中【】代表向下取整,还是5,假如我们要分成两份,那么新的最大值要取较小的值,即2,那么切的次数就是k-1次,最后答案就是所有k-1的和

至此题目解决完毕,上代码

代码:

#include <iostream>
#include <algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <stack>
#include <memory>
using namespace std;
#define int long long
#define yes cout << "YES\n"
#define no cout << "NO\n"

void solve()
{
    int n;
    cin >> n;
    vector<int> a(n);
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
    }
    int maxn = a[n - 1],res = 0;
    for (int i = n-1; i >= 0; i--)
    {
        if (a[i] <= maxn)
        {
            maxn = a[i];
        }
        else
        {
            int k = (a[i] + maxn - 1) / maxn;
            maxn = a[i] / k;
            res += k - 1;
        }
    }
    cout << res << endl;
}

signed main()
{
    cin.tie(0)->sync_with_stdio(false);
    int t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

C. Colorful Grid

题目:

思路:

小清新构造,很有意思

首先我们要考虑什么情况不存在解决方案

既然要从(1,1)走到(n,m)那么最少的步数就是 n+m-2 也就是说如果步数小于这个,那肯定不行,那么再看如果大于呢?

我们发现题目说同一个点是可以重复走的,那么我们是否可以构造出一个环呢?显然是可行的

只要我们构造出一个最小环,那么我们就能在环上一直走,其长度是4

也就是说只要 (k - (n+m-2))% 4 == 0 ,就说明这个情况也能构造出来,那么还有什么可能吗?

其实还有一种,除了走直线,我们还能走曲线,我们可以构造出一个拐弯,这样就能比走直线多走两步,也就是说 (k - (n+m-2))% 4 == 2 的情况也是可行的

可以证明,除此自外没有任何情况可以构造了,那么我们如何构造呢?

可以参考cf的官方方法,首先肯定要有一条可以走到重点的路,然后再构造出一个环,同时再构造出一个拐弯,那我们就可以像下图这样构造了

 

至此解决完毕,实现上就是打表,注意奇偶性即可 

代码:

#include <iostream>
#include <algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <stack>
#include <memory>
using namespace std;
#define int long long
#define yes cout << "YES\n"
#define no cout << "NO\n"

void solve()
{
    int n, m, k;
    cin >> n >> m >> k;
    vector<vector<char>> mpn(n-1, vector<char>(m, 'B'));
    vector<vector<char>> mpm(n, vector<char>(m-1, 'B'));
    int minn = n + m - 2;
    if (k < minn)
    {
        no;
        return;
    } 
    if ((k - minn) % 4 != 2 && (k - minn) % 4 != 0)
    {
        no;
        return;
    } 
    yes;
    for (int i = 0; i < m-1; i++)
    {
        if ((i & 1) == 0)
            mpm[0][i] = 'R';
    }
    mpm[1][0] = 'R';
    for (int i = 0; i < n - 1; i++)
    {
        if (mpm[0][m - 2] == 'R' && (i & 1))
        {
            mpn[i][m - 1] = 'R';
        }
        else if (mpm[0][m - 2] != 'R' && (i & 1)==0)
            mpn[i][m - 1] = 'R';
    }
    if (mpn[n - 3][m - 1] == 'R')
    {
        mpn[n - 2][m - 2] = 'R';
    }
    else
    {
        mpm[n - 2][m - 2] = 'R';
        mpm[n - 1][m - 2] = 'R';
    }
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < m - 1; j++)
        {
            cout << mpm[i][j] << " ";
        }
        cout << endl;
    }   
    for (int i = 0; i < n-1; i++)
    {
        for (int j = 0; j < m; j++)
        {
            cout << mpn[i][j] << " ";
        }
        cout << endl;
    }
}

signed main()
{
    cin.tie(0)->sync_with_stdio(false);
    int t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

D. Absolute Beauty

题目:

思路:

思路远大于实现,数形结合

对于这种绝对值的题目,我们一般考虑的是变化的量,即如何让交换后的改变量最大,那我们来分析一下题目

我们可以画出下图

我们将数字在数轴上表示,那么奉献就是线段长度,其中红色是没交换前的奉献,蓝色是交换后的奉献(这里只画了几种情况)

我们其实可以发现,只有当两个区间不相交时,交换bi和bj才会增加奉献,那么我们只需要储存最远的不相交的两个区间即可,那么储存什么值呢?

可以发现,增加的奉献就是左区间的右端点 r,和右区间的左端点 l 的差的两倍,即 2 * (l - r)

所以我们遍历整个数组,每次都加上其原来的奉献,同时储存所有区间的最小右端点,和所有区间的最大左端点既可,最后看看到底要不要交换

具体看代码

代码:

#include <iostream>
#include <algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <stack>
#include <memory>
using namespace std;
#define int long long
#define yes cout << "YES\n"
#define no cout << "NO\n"

void solve()
{
    int n;
    cin >> n;
    vector<int> a(n), b(n);
    for (int i = 0; i < n; i++)
    {
        cin >> a[i];
    }
    for (int i = 0; i < n; i++)
    {
        cin >> b[i];
    }
    int ans = 0;
    int r = 1e9, l = 0;
    for (int i = 0; i < n; i++)
    {
        r = min(r, max(a[i], b[i]));
        l = max(l, min(a[i], b[i]));
        ans += abs(a[i] - b[i]);
    }
    cout << max(ans,ans + 2 * (l - r)) << endl;
}

signed main()
{
    cin.tie(0)->sync_with_stdio(false);
    int t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

C. Anji's Binary Tree

题目:

思路:

一道很简单的题,想复杂了,还是对dfs的用法不够理解

翻译一下题目:给定一颗有根树(根为1),每个节点都有一个操作,分别为U,L,R,分别代表往父节点走,往左子节点走,往右子节点走,现在我们从根节点出发,每次都按节点的操作行走,问最少要多少次 改变节点的操作(如将L改为R) 才能走到叶子节点

一开始我是直接统计走到每一个叶子节点需要什么操作,然后将这个操作与原树的操作对比,需要改变的次数就是不同的地方,但是这样的话最后内存超了,所以不行,那么我们按照这个思路重新写一遍看看呢

还是一样的,首先肯定要找到叶子节点的路,还是要用dfs,那么对于任意一个节点,只要他有子孩子,那我们就要往下走,既然我们要求改变操作的次数,那我们直接走过每一条路,然后看那条路的改变次数最小就行了,那么如何计算需要改变的次数呢?

看下图

如果我们按其节点的操作走,肯定是走不到叶子节点的,一个很显然的最小操作数是将1的操作改为R,这样只需要一次操作就能走到节点3,那我们如何写呢? 

很简单,对于节点s,如果我们往左走,如果string[s]不为L,那么就要加一次改变次数,如果是L,那么就不用加,以此类推,这样就变成递归的形式了,然后每次对左右两次操作取最小值即可

具体实现看代码

代码:

#include <iostream>
#include <algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <stack>
#include <memory>
using namespace std;
#define int long long
#define yes cout << "YES\n"
#define no cout << "NO\n"

struct Node
{
    int l = -1, r = -1;
};
int n;
string s;
int dfs(const vector<Node>& Tree, int self)
{
    if (!self)
        return 1e9;
    if (Tree[self].l == 0 && Tree[self].r == 0)
        return 0;
    return min((s[self] != 'L') + dfs(Tree, Tree[self].l), (s[self] != 'R') + dfs(Tree, Tree[self].r));
}

void solve()
{
    cin >> n;
    cin >> s;
    s = '$' + s;
    vector<Node> tree(n + 1);
    for (int i = 1; i <= n; i++)
    {
        int l, r;
        cin >> l >> r;
        tree[i].l = l;
        tree[i].r = r;
    }
    cout << dfs(tree, 1) << endl;
}

signed main()
{
    cin.tie(0)->sync_with_stdio(false);
    int t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

D. Small GCD

题目:

思路:

好题,好题,好题

首先分析题目,我们要找出一个三元组a,b,c,然后求两个较小值的gcd,也就是最大值是什么无所谓,我们只考虑最小值

那么这题就变成求所有 gcd(a,b) 的奉献和,任意一个 gcd(a,b)的奉献和就是 gcd(a,b) * 数组中大于max(a,b)的数的数量

如1 2 3 5,如果要求gcd(1,2)的奉献,显然就是 gcd(1,2) * 2,因为2后有两种选择,即3 5

所以我们就不需要考虑原数组到底啥样了,我们直接一个排序即可

但是如果这样一个一个算的话肯定会超时,那么我们如何优化呢?

遇到这种题我们可以考虑每个数的奉献,由于是求gcd,那么我们可以转化为每个因子在答案中的奉献

观察到a的数据只有1e5,那我们就可以预处理1e5中每个数的因子有多少以及分别是什么

我们定义一个 f[i] ,其代表的是以因子 i 为gcd能组成的对数,即 f[i] += cnt[i] * (n - pos),其中pos为因子i对于的数a在数组中的位置,cnt[i] 代表之前有出现过多少次 i 

让我们来理解一下为什么是这样子,比如对于数组 2 4 8 ... 遇到 2 时,其因子有 1 2

那么f[1] = 0 * (n - 1),f[2] = 0 * ( n- 1),因为此前我们没有遇到过1 2,所以都是0,当我们处理完因子的奉献后,再将cnt[1]++,cnt[2]++,顺序很重要

之后遇到4,还是一样的操作,不过由于此时 1 2 出现过,所以1 2的奉献就要增加了,以此类推,最后所有数都被找过一遍了

我们来分析一下为什么要选所有因子,难道不是选最大因子就行了吗?

因为我们要求所有的 gcd(a,b),如上面,对于2,我们要求 gcd(2,4) + gcd(2,8) + ... ,如果只考虑最大因子,那么如果后面出现了一个数使得其 gcd(2,x) == 1 的话,那么因子1的奉献应该要增加,但是我们没考虑,所以答案就会出错了

通俗的讲,我们每一种因子都有可能在后面遇到,所以我们所有因子都要考虑

那么还有一点显而易见,就是会重复计算,比如 2 4 8,其中因子2被重复计算了,如4 8,他们的gcd是4,但是我们算的时候会把 2 也算进去,因为他们都有因子2,所以我们要考虑去重

这里根据容斥原理,一个因数的奉献 = 现在的奉献减去其所有倍数的奉献 = 其真正的奉献

如 2 4 8,因数 2 的奉献就是 gcd(2 4) 和 gcd(2 8),但是计算过程中 2 却有3个,其原因就是4和8的gcd应该是4,但是此时2也被算过一遍了,所以要减去所有2的倍数的贡献,因为此时2的贡献其实是0,或者可以这样理解,我们要选 4,但是我们把 2 也都选了,所以 2 的奉献就多了,而这多的部分都在 4 里,所以减去 4 的奉献即可,这样就只剩 2 的奉献了

具体细节看代码

代码:

#include <iostream>
#include <algorithm>
#include<cstring>
#include<cctype>
#include<string>
#include <set>
#include <vector>
#include <cmath>
#include <queue>
#include <unordered_set>
#include <map>
#include <unordered_map>
#include <stack>
#include <memory>
using namespace std;
#define int long long
#define yes cout << "YES\n"
#define no cout << "NO\n"

const int MAXN = 1e5;
vector<vector<int>> factor(MAXN + 5);

void solve()
{
    int n;
    cin >> n;
    vector<int> a(n+1);
    vector<int> f(MAXN + 5, 0);
    vector<int> cnt(MAXN + 5, 0);
    for (int i = 1; i <= n; i++)
    {
        cin >> a[i];
    }
    sort(a.begin()+1, a.end());
    for (int i = 1; i <= n; i++)
    {
        for (auto fac : factor[a[i]])
        {
            f[fac] += cnt[fac] * (n - i);
            cnt[fac]++;
        }
    }
    int res = 0;
    for (int i = MAXN; i; i--)
    {
        for (int j = i*2; j <= MAXN; j+=i)
        {
            f[i] -= f[j];
        }
        res += f[i] * i;
    }
    cout << res << endl;
}

signed main()
{
    for (int i = 1; i <= MAXN; i++)
    {
        for (int j = i; j <= MAXN; j+=i)
        {
            factor[j].push_back(i);
        }
    }
    cin.tie(0)->sync_with_stdio(false);
    int t = 1;
    cin >> t;
    while (t--)
    {
        solve();
    }
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值