CCPC2020威海区域赛(A、D、H、L)

  • A. Golden Spirit

  • 题意:一座桥的两边各有\(n(1 \leq n \leq 10^9)\)位老人,他们每个人在你的帮助下过河时间均为\(t(1 \leq t \leq 10^9)\)(只有你帮助他们,他们才能过河),需要过到桥的对面休息\(x(1 \leq x \leq 10^9)\)分钟后回来,问帮助所有老人最后回到原来的位置所需要的最少时间。

  • 思路:思维题、分类讨论。

  • 解析:相当于一个有\(2n\)位老人,那么假设桥的两边分别为A、B,假设护送者一开始在A,那么从A送一个老人到B并放该老人在B休息,接着立刻送一个老人从B到A,以此类推,将所有老人都送到对岸去后这时候假设护送者在A。

    • 若他不需要等待就能把最先到达A的老人送回B去,那么总时间\(time_1 = 4\times n\times t\);

    • 否则说明需要考虑是继续等到他休息完毕后送他回B,还是先自己跑回去B(不送人),先把B处第一个到达的老人(可能护送者到达B后也要等待)护送回A再送他回去(这时候他肯定已经休息结束)。那么总时间\(time_2 = time_1 + min\{x - (2\times n - 2) \times t, max\{t, x - (2\times n - 1) \times t\}\}\)

      实际上此时就是加上一个等待时间后,接着就肯定不用等待就能像流水线工作一样送他们回到原处,因为在\(A_i和B_i\)位置的老人实际上相差时间就是\(t\),那么这时候就要考虑是在A这等还是过去对岸B等。核心点:因为有偶数次操作,所以从哪边开始其实都不重要,重要的是谁先开始。那就比较一下在A等的时间和在B等的时间哪个小即可,但是在B等的时间是护送者完成所有的第一次护送后回到A时候最先到达B的这个老人还需休息的时间和护送者过一次河所花费的时间\(t\)的大者。

  • 代码:

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    int T;
    ll n, t, x;
    
    int main()
    {
        cin >> T;
        while(T --)
        {
            cin >> n >> x >> t;
            ll res = (ll)4 * n * t;
            ll temp1 = x - ((ll)2 * n - (ll)2) * t; //护送者完成所有的第一次护送后回到A时候还需要等待最先到达A的这个老人的时间
            ll temp2 = x - ((ll)2 * n - (ll)1) * t; //护送者完成所有的第一次护送后回到A时候最先到达B的这个老人还需休息的时间
            if(temp1 > 0) //需要等待
                res += min(temp1, max(temp2, t));
            cout << res << endl;
        }
        return 0;
    }
    
    
  • D. ABC Conjecture

  • 题意:给出一个正整数\(c(1 \leq c \leq 10^{18})\),问是否能找出两个正整数\(a, b\)满足\(a + b = c\)且设\(a \times b \times c\)的所有质因数之积\(val\)小于\(c\)。ps:若\(x = 12\),则其质因数为2和3。

  • 思路:数论(唯一分解定理、素数筛) + 思维。

  • 解析:先说结论:由唯一分解定理可得\(c = p_1^{e_1} \times p_2^{e_2} \times p_3^{e_3}... \times p_x^{e_x}\), 若存在\(e_i > 1\),说明可以找到满足条件的\(a、b\)。接着来个不是特别严谨的证明:

    • 若所有的\(e_i = 1\),那么易证\(val \leq c\),必然找不到满足条件的\(a和b\)
    • 反之,我们发现分解成\(a、b\)后,可以发现\(a\times b\)的质因子总是小于\(v\)al,发现可以使\(a\)的质因子\(\in\) \(c\)的所有质因子。然后可以找出满足所有质因子小于\(val\)的b。

    那么首先需要先筛出\(10^6\)内的素数,接着类似试除法,假如\(c \% prime_i^2 = 0\),说明存在\(e_i > 1\),那么必然可以构造出\(a、b\)满足条件,若仅仅存在\(c\% prime_i = 0\),那么最后试除法结束后此时\(10^6\)以内的素数已经全部验证完毕。那么\(c\)剩下的因子数量不可能超过两种或一种的两个,因为假如存在三个大于\(10^6\)的数相乘必然大于\(10^{18}\),超出\(c\)的范围,那么可以判断此时的\(c\)是否由两个相同的质因子组成即可,也就是判断是否存在\(e_i > 1\),若不是由相同的两个质因子组成(可能是一个质因子或者两个不同的质因子),则说明所有的\(e_i = 1\),找不到可以构造的\(a, b\),反之可以找到可以构造的\(a, b\)

  • 代码:

    #include<iostream>
    #include<cstdio>
    #include<vector>
    #include<cmath>
    #include<map>
    using namespace std;
    typedef long long ll;
    const int N = 1e6 + 6;
    int T;
    ll prime[N], vis[N], cnt = 0;
    
    void is_Prime()
    {
        vis[0] = vis[1] = 1;
        for(ll i = 2; i <= (ll)1e6; i++)
        {
            if(!vis[i]) prime[++cnt] = i;
            for(int j = 1; j <= cnt && i * prime[j] <= (ll)1e6; j++)
            {
                vis[i * prime[j]] = 1;
                if(i % prime[j] == 0) break;
            }
        }
    }
    
    int main()
    {
        is_Prime();
        scanf("%d", &T);
        while(T --)
        {
            ll c, temp;
            int flag = 0;
            scanf("%lld", &c);
            for(ll i = 1; i <= cnt && !flag; i++)
            {
                if(c % (prime[i] * prime[i]) == 0)
                    flag = 1;
                else if(c % prime[i] == 0)
                    c /= prime[i];
            }
            if(c > 1)
            {
                temp = sqrt(c);
                if(temp * temp == c)
                    flag = 1;
            }
            else flag = 0; ////说明所有的ei = 1
            if(flag) printf("yes\n");
            else printf("no\n");
        }
        return 0;
    }
    
    
  • H. Message Bomb

  • 题意:一共有\(n(1\leq n \leq 10^5)\)个群,\(m(1 \leq m \leq 2\times 10^5)\)位同学,每位同学可以加入到多个群中,先给出\(s(1 \leq s \leq 10^6)\)个操作,每次操作给出三个数\(t(1 \leq t \leq 3)、x(1\leq x \leq m )、y(1\leq y \leq n)\)

    \(t = 1\)表示编号为\(x\)的同学加入编号为\(y\)的群(保证\(x\)之前不在\(y\)中);

    \(t = 2\)表示编号为\(x\)的同学退出编号为\(y\)的群(保证\(x\)之前在\(y\)中);

    \(t = 3\)表示编号为\(x\)的同学给其所在编号为\(y\)的群的所有成员(不包括自己)发送一条消息。

    最后需要给出编号为1~m(这个编号一开始就默认从1开始编号)所有同学所收到的消息数量。

  • 思路:思维、差分思想。

  • 解析:首先需要一个数组s_sum记录第x位学生所收到的消息数量,再用一个数组g_num记录第y个群目前已经收到的信息数量,最后用一个set维护每个群中所在的同学编号。

    接着利用差分思想:当编号为\(x\)的同学加入编号为\(y\)的群后,那么该同学所收到的消息数量先减去第\(y\)个群目前已经收到的消息数量,当编号为\(x\)的同学退出编号为\(y\)的群时,此时该群的信息数量已经发生变化,那么编号为\(x\)的同学加上该群目前的总消息数量即为他进入到该群到退出该群期间,该群消息增加的数量。

    最后完成所有操作后(利用set)判断一下哪些群里还有哪些同学未退群,此时让这些同学加上他们所在的群目前总共接受到的消息数量即可,其实这就是利用了差分的思想。

    那当编号为\(x\)的同学给编号为\(y\)的群发了一条消息,那么编号为\(y\)的群的总消息数量是需要加一的,而该同学的总消息数量需要减一,因为是他发送的消息,不能看作其收到一条消息。

  • 代码:

    #include<iostream>
    #include<cstdio>
    #include<set>
    using namespace std;
    const int N = 1e5 + 5;
    const int M = 2e5 + 5;
    int n, m, s;
    int g_num[N], s_num[M]; //第i个小组当前收到总的消息数量,第x个学生收到的消息总数
    set<int> g[N];
    
    int main()
    {
        scanf("%d%d%d", &n, &m, &s);
        for(int i = 1; i <= s; i++)
        {
            int op, x, y;
            scanf("%d%d%d", &op, &x, &y);
            if(op == 1)
            {
                g[y].insert(x); //将编号为x的学生加入第y组
                s_num[x] -= g_num[y]; //减去第y小组当前总的消息数量
            }
            else if(op == 2)
            {
                g[y].erase(x); //将编号为x的学生从第y组剔除
                s_num[x] += g_num[y]; //加上第y小组当前总的消息量,实际就是自身在该小组收到的消息量
            }
            else
            {
                s_num[x] --;
                g_num[y] ++;
            }
        }
        //加上还在某个组里面的同学在该组所接收到的消息数
        for(int i = 1; i <= n; i++)
        {
            for(set<int> :: iterator it = g[i].begin(); it != g[i].end(); it++)
            {
                s_num[*it] += g_num[i];
            }
        }
        for(int i = 1; i <= m; i++) printf("%d\n", s_num[i]);
        return 0;
    }
    
  • L. Clock Master

  • 题意:给出一个数\(b(1 \leq b \leq 3\times 10^4)\),问将\(b\)分解成若干个数\(x_1、x_2、x_3...x_n(保证x_1 + x_2 + x_3 + ... + x_n = b)\),使这若干个数的最小公倍数最大,要求输出其最小公倍数并取ln。

  • 思路:数论、分组背包。

  • 解析:若希望分解的所有数的最小公倍数最大,那么肯定尽可能的希望他们是互质的,那么\(x_1与x_2\)就应该是由不同质数的若干次幂得到的值,若是相同质数的不同次幂,那么他们的lcm必然是大者。那如何保证\(x_1 + x_2 + x_3 + ... + x_n = b\),实际上只要保证他们相加不超过\(b\),那么剩下的值可以用1来补。

    所以,我们需要先打个\(30000\)内的质数表,接着将所有质数的若干次幂的值(这个值在\(30000\)内即可)求出来,接着利用分组背包(需空间优化)进行求解:

    首先将不同质数的若干次幂得到的值分为若干组,例如:\(2^1,2^2\)为第一组的第一个物品和第二个物品,\(3^1, 3^2\)为第二组的第一个物品和第二个物品。(以下均用物品替代某质数的若干次幂)

    集合划分:\(f_{i, j}\)表示当前数值\(j\)\(i\)组物品得到的\(max\{lcm\}\);

    状态转移:

    • 不选择第\(i\)组的物品:\(f_{i, j} = f_{i - 1, j}\);
    • 若当前的数值(体积)大于或等于第\(i\)组的第\(k\)个物品的值,则\(f_{i, j} = max\{f_{i - 1, j - val_{i, k}} \times val_{i, k} \}\);

    最后优化成一维即可~

    ps:建议用double,不然求lcm的时候可能会爆long long。

    代码:

    #include<iostream>
    #include<cstdio>
    #include<cmath>
    using namespace std;
    typedef long long ll;
    const int N = 3e4;
    double f[N + 5];
    int cnt[N + 5], vis[N + 5], num = 0;
    int prime[N + 5], val[N + 5][20];
    int T, b;
    
    void init()
    {
        vis[0] = vis[1] = 1;
        for(int i = 2; i <= N; i++)
        {
            if(!vis[i]) prime[++num] = i;
            for(int j = 1; j <= num && i * prime[j] <= N; j++)
            {
                vis[i * prime[j]] = 1;
                if(i % prime[j] == 0) break;
            }
        }
        for(int i = 1; i <= num; i++)
        {
            int len = 1;
            int tempV = pow(prime[i], len);
            while(tempV <= N)
            {
                val[i][len++] = tempV;
                tempV = pow(prime[i], len);
            }
            cnt[i] = len - 1;
        }
    }
    
    void solve()
    {
        fill(f, f + N + 1, 1);
        for(int i = 1; i <= num; i++)
        {
            for(int j = N; j >= 1; j--)
            {
                for(int k = 1; k <= cnt[i]; k++)
                {
                    if(j >= val[i][k])
                        f[j] = max(f[j], f[j - val[i][k]] * (double)val[i][k]);
                }
            }
        }
    }
    
    
    int main()
    {
        scanf("%d", &T);
        init();
        solve();
        while(T --)
        {
            scanf("%d", &b);
            printf("%.9lf\n", log(f[b]));
        }
        return 0;
    }
    
posted @ 2021-10-25 19:55  ~K2MnO4  阅读(330)  评论(0)    收藏  举报