Codeforces Round #432 (Div. 2)C,D,E题目详解

越到后来就发现,CDE题对博主的提升更大,而且博主纵观CSDN,几乎没人写E题题解,写A到D的居多,为了提高阅读量以及博客水平,以后就只更新CDE题目的题解了,并且会陆续把之前大约50场div2的题目都更新。。
C题题意:在一个5维坐标系中,如果一个点与其他任意两个点组成的两个向量夹角小于90度,则该点被称为bad,给你n个点的坐标,问有几个good点。
思路:直接三层for循环,如果夹角小于90度,则直接break,如果大于等于90度,则另外两个点不需要考虑了。

#include<iostream>
using namespace std;
const int maxn = 1005;
struct node
{
    int a, b, c, d, e;
}nodes[maxn];
bool vis[maxn];
int main()
{
    int n;
    cin >> n;
    for (int i = 1;i <= n;i++)
    {
        scanf("%d%d%d%d%d", &nodes[i].a, &nodes[i].b, &nodes[i].c, &nodes[i].d, &nodes[i].e);
    }
    long long temp;
    int cnt = 0;
    int ans[maxn];
    for (int i = 1;i <= n;i++)if (!vis[i])
    {
        bool flag = true;
        for(int j=1;j<=n;j++)if(j!=i)
            for (int k = 1;k <= n;k++)if(k!=i&&k!=j)
            {
                temp = (nodes[j].a - nodes[i].a)*(nodes[k].a - nodes[i].a) + (nodes[j].b - nodes[i].b)*(nodes[k].b - nodes[i].b) + (nodes[j].c - nodes[i].c)*(nodes[k].c - nodes[i].c) + (nodes[j].d - nodes[i].d)*(nodes[k].d - nodes[i].d) + (nodes[j].e - nodes[i].e)*(nodes[k].e - nodes[i].e);
                if (temp >0)
                {
                    k = n + 1;
                    j = n + 1;
                    flag = false;
                }
                else
                {
                    vis[j] = 1;
                    vis[k] = 1;
                }
            }
        if (flag)
        {
            cnt++;
            ans[cnt] = i;
        }
    }
    cout << cnt << endl;
    for (int i = 1;i <= cnt;i++)
        cout << ans[i] << endl;
    return 0;
}

这题其实有一个结论,即n如果大于11个,则没有good点,即输出0.稍后会带来证明。。
D题题意:给你一个序列如果这个序列是非空并且gcd为1则该序列被称为bad,现在有两个操作,1是将序列中的一个元素删掉,需花费x,2是将一个元素值加1,花费y,可以对同一个元素操作多次。
问最小花费是多少可以使序列变成good。
思路:枚举序列的gcd,然后将所有不能整除它的元素进行1操作或者2操作(贪心),这个思路没错,但是元素个数有5*10^5,枚举gcd的范围也达到了10^6,可想而知,肯定TLE。这里就要进行一点脑筋急转弯。
假如说,现在枚举的gcd为k,现在要对a这个元素操作,操作1的花费为x,而操作2的花费为(a-k)*y。操作2的花费小于1时的不等式为

(ak)<xy

设x/y为p,则该不等式可变成a-k<=p(除法向下取整之后就从小于变成了小于等于了,想一想为什么)。所以从这个式子看出,我们只需要以元素和k的差值来进行判断选哪个操作了,这时候就可以用前缀和来优化了。
代码如下:

#include<iostream>
#include<algorithm>
using namespace std;

typedef long long ll;

const int maxn = 2000006;
ll s[maxn], sum[maxn];
ll n, x, y;
int main()
{
    cin >> n >> x >> y;
    int p;
    p = x / y;
    ll temp;
    for (int i = 1;i <= n;i++)
    {
        scanf("%lld", &temp);
        s[temp]++;
        sum[temp] += temp;
    }
    for (int i = 1;i <= 2000000;i++)
    {
        s[i] += s[i - 1];
        sum[i] += sum[i - 1];
    }

    ll ans = n*x;
    for (int i = 2;i <= 1000000;i++)//以i为gcd
    {
        ll temp = 0;
        for (int j = i;j <=i+1000000&&temp<ans;j += i)//分块来计算
        {
            int k = max(j - i + 1, j - p);
            temp +=1LL*((s[j] - s[k - 1])*j - (sum[j] - sum[k - 1]))*y;
            temp += 1LL * (s[k - 1] - s[j - i])*x;
        }
        ans = min(ans, temp);
    }
    cout << ans << endl;
    return 0;
}

读者注意循环的区间,为啥第二个循环是i+1000000,因为有些点将其变成i+1000000的代价更低,这也是为什么maxn会开比题意大两倍的空间,因为要计算元素到i+1000000的代价,而i最大为1000000,所以要开2倍。这些小细节希望读者注意。
E题题意:Mojtaba和Arpa玩游戏,有一个序列,当一个人操作时,需要选择一个素数p,和k组成p^k,然后序列中必须有可整除它的数,将这些可以整除它的数除以它。如果一个人不能找出p和k,则代表他输了。
思路:第一次写这么难的博弈论的题,搞了半天才差不多明白了。直接讲做法吧,其实这场游戏可以分成若干个子游戏,即每个素数可以单独考虑胜负,然后用dfs计算sg函数值,判断该状态是必胜态还是必败态。然后将所有子游戏状态合并,看最终是谁胜利。
如何dfs,如何计算sg?,合并关系是怎么样的?
大家可以看代码自己思考思考。

#include<iostream>
#include<map>
#include<math.h>
using namespace std;
map<int, int>M, sg;
int dfs(int num)
{
    if (!num)return 0;
    if (sg.find(num) != sg.end())return sg[num];
    int mx;
    for(int i=30;i>=0;i--)
        if(num>>i&1)
        {
            mx = i;break;
        }
    int vis = 0;
    int temp;
    for (int i = 1;i <= mx;i++)
    {
        temp = ((num >> (i + 1)) << 1) | (num &((1<<i)-1));
        vis |= (1 << dfs(temp));
    }

    for (int i = 0;i <= 30;i++)
        if ((~vis >> i) & 1)
            return sg[num] = i;
}
int main()
{
    int n;
    cin >> n;
    int x;
    for (int i = 1;i <= n;i++)
    {
        scanf("%d", &x);

        for (int j = 2;j*j<=x;j++)
        {
            int cnt = 0;
            while (x%j==0)
            {
                cnt++;
                x /= j;
            }
            if (cnt != 0)
                M[j] |= (1 << cnt);
        }
        if (x != 1)
            M[x] |= (1<<1);
    }
    int sum = 0;
    for (auto it = M.begin();it != M.end();it++)
    {
        sum ^= dfs(it->second);
    }
    if (!sum)
        puts("Arpa");
    else
        puts("Mojtaba");
    return 0;
}

其中有许多位运算,希望读者能自主弄清楚其中的含义,毕竟博主也是这么搞懂了。。。
如真遇到难点,欢迎留言。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值