2020 CCPC 威海(赛后重现)


比赛分析

出题人的想法:(给一个链接自己看去)

本博主的看法(以下的就都是俺的想法了):属于是为了签到签完而努力着!我们属于是赛后重现赛,自己拉一场打的。一场比赛就过两道题。哎~~。分析一下整场比赛(下面有部分题的分析):牌线6/5/4,还在打铁的路上啊!什么时候能进入铜器时代呢?看了一下大佬们的过题时间,签到都挺快……大致上以B题、G题作为一个分界线。B题我补了一下,有个卡点,想到了在处理上也会有问题;G题队友补了一下,也是存在难处理的点。作为分界线的这两道题大体思路不难,就是在关键点上不一定能处理出来。一场比赛对知识基础的考验还是不小的!先分析一波,然后持续打铁中~~~~

比赛题目

题目链接
A题:老人过桥
一个桥,桥的两端各有n个老头,他们要去对面。但是老头自己不敢过桥,需要你来帮忙!之后老头过桥之后要去对面花费时间t。老头要歇息时间x为。然后老头门还要回来,求最小花费时间。

B题:Labyrinth
走迷宫,一个n*m的图,有k个地方毁坏(称为黑洞),有s次询问:问(x1,y1)到(x2,y2)的最小移动次数

C题: Rencontre
一张图,有三个人,每个人分别选几个点,求这三个人到达某个点的最短期望!

D题:ABC Conjecture
给你一个数c找到是否存在a、b两个数,满足 a+b=c && rad(abc) < c;

r a d ( n ) = ∏ p ∣ n n p ( p ∈ p r i m e ) rad(n) = \prod_{p|n}^{n} p (p \in prime) rad(n)=pnnp(pprime)

E题:(一道可以不要的题了)

F题:(又一个不要的题)

G题:Caesar Cipher
一串数字,两种操作:1、将l~r区间内的数字+1 同时%65536;2、判断两个区间内的数字是否相同

H题:Message Bomb
有n个组,m个人,s个操作!操作有三种(设三个变量t,x,y),t = 1、将x个人加入到y组里;2、x退出y组;3、x在y组中分享一个信息(保证x一定在y组之中)问,m个人每一个人收到的信息是多少?

I题:(再见吧)

J题:Steins;Game
有n堆石子,两种颜色。A和B两个人开始进行游戏,内容如下:两个人可以依次从白色石头堆里或者最小数量的黑色石头堆中拿任意数量的石头(保证是黑色石头中数量最小的)。A先那,B个石子染色,问有多少种颜色方法B㲌获胜

K题:缺德二叉树(Tree Tweaking)
先有一个数组,可以将l,r区间内的数字任意转换位置,将变换之后的数组构成一颗二叉搜索树,求最小深度和。

L题:Clock Master
T组样例,给定一个数P。找到一堆数,使其和等于P,同时使这堆数的lcm最大,求 ln ⁡ ( l c m ) \ln(lcm) ln(lcm)

部分题目思路及代码

A题:老头过桥

考点:注意两种情况

思路:存在两种情况,当送完老头之后,要不要过桥。可以判断过桥的条件。不过,直接把过桥和不过桥两种情况的时间分别求出来,去最小值就行注意休息时间就行

代码:

#include <iostream>
#include <algorithm>

using namespace std;

typedef long long int ll;

int main()
{
    int T; scanf("%d",&T);
    while(T--)
    {
        ll n,x,t;scanf("%lld%lld%lld",&n,&x,&t);
        ll ans1 = 2 * n * t;
        ll ans2 = 2 * n * t + t;
        
        if(ans1 < x + 2 * t)ans1 += x + 2 * t - ans1;
        if(ans2 < x + t)    ans2 += x + t - ans2;
        
        ans1 += 2 * n * t;
        ans2 += 2 * n * t;
        printf("%lld\n",min(ans1,ans2));
    }
    return 0;
}

D题:ABC Conjecture

考点:问:会不会pollard_rho;对数据的拆解分析

思路:pollard_rho板子题,(记住得了,看不懂的东西)还有一种方法是拆数据!

代码:

#include <iostream>
#include <algorithm>
#include <ctime>
#include <cmath>
#include <cstdlib>
#include <cstdio>

using namespace std;
typedef long long ll;
const int N = 1e6+7;
int t, s = 20, cnt;
ll fac[N];

ll ksc(ll x, ll y, ll mod)
{
    ll res = 0;
    while(y)
    {
        if(y & 1)
            res = (res + x) % mod;
        x = (x << 1) % mod;
        y >>= 1;
    }
    return res;
}

ll ksm(ll x,ll y,ll mod)
{
    ll res = 1;
    while(y)
    {
        if(y & 1)
            res = ksc(res, x, mod);
        x = ksc(x, x, mod);
        y >>= 1;
    }
    return res;
}

int miller_rabin(ll n)
{
    if(n == 2) return 1;
    if(n < 2 || !(n % 2)) return 0;
    ll u, pre, x;
    int k = 0;
    u = n - 1;
    while(!(u & 1))
    {
        ++k;
        u >>= 1;
    }
    for(int i = 1; i <= s; i++)
    {
        x = rand() % (n - 2) + 2;
        x = ksm(x, u, n);
        pre = x;
        for(int j = 1; j <= k; j++)
        {
            x = ksc(x, x, n);
            if(x == 1 && pre != 1 && pre != n - 1)
                  return 0;
            pre = x;
        }
        if(x != 1)
            return 0;
    }
    return 1;
}

ll gcd(ll a,ll b)
{
    if(a == 0)return 1;
    if(a < 0)return gcd(-a,b);
    while(b)
    {
        ll t = a % b;
        a = b;
        b = t;
    }
    return a;
}

ll pollard_rho(ll n,ll c)
{
    ll i = 1, k = 2;
    ll xx = rand() % n, y = xx;
    while(true)
    {
        i++;
        xx = (ksc(xx, xx, n) + c) % n;
        ll d = gcd(y - xx, n);
        if(1 < d && d < n) return d;
        if(y == xx) return n;
        if(i == k)
        {
            y = xx;
            k <<= 1;
        }
    }
}

void find(ll n)
{
    if(miller_rabin(n))
    {
        fac[++cnt] = n;
        return;
    }
    ll p = n;
    while(p >= n)
        p = pollard_rho(p, rand() % (n - 1) + 1);
    find(p);
    find(n/p);
}

int main()
{
    srand((unsigned int)time(0) + 19260817);
    scanf("%d",&t);
    while(t--)
    {
        ll x; scanf("%lld",&x);
        if(miller_rabin(x) || x == 1)
        {
            printf("no\n");
            continue;
        }
        cnt = 0;
        find(x);
        sort(fac, fac + cnt + 1);
        bool flag = true;
        for(int i = 1; i <= cnt; i++)
        {
            if(x % (fac[i] * fac[i]) == 0)
            {
                flag = false;
                printf("yes\n");
                break;
            }
        }
        if(flag) printf("no\n");
    }
    return 0;
}

B题:Labyrinth

考点:考思维,对每一种情况的判断。代码能力,动态申请空间的操作(感觉这就是一个坑!)

思路:属于一道bfs题。分为两点
1、当以起止点作为对顶点所围成的矩形范围内不存在黑洞,最小距离就是曼哈顿距离
2、如果存在黑洞,那么从起始点一定有一条边可以经过黑洞达到终止点。以黑洞四周的四个点维中间点,做bfs找最小距离。

代码:

#include <iostream>
#include <algorithm>
#include <cstring>
#include <queue>
#include <map>
#include <set>
#include <math.h>
using namespace std;

typedef pair<int, int> pii;
const int inf = 0x3f3f3f;

int n,m,k,t;
int mov[4][2] = {{-1,0},{0,1},{1,0},{0,-1}};//移动方向

int *dist[50][5][200010];//记录距离
map<pii, int> vis;//标记坐标
set<pii> helo;//记录黑洞的地点

void bfs(pii s)
{
    int idx = vis[s];
    for(int i = 0 ;i < 4; i++)
    {
        int dx = s.first + mov[i][0];
        int dy = s.second + mov[i][1];
        if(dx < 1 || dy < 1 || dx > n || dy > m) continue;
        if(helo.count(make_pair(dx, dy))) continue;

        for(int k = 1; k <= n; k++)
        {
            dist[idx][i][k] = new int[m + 5];//动态申请空间
            for(int j = 1; j <= m; j++)
                dist[idx][i][k][j] = inf;
        }
        dist[idx][i][dx][dy] = 0;
        queue<pii> q;//开始走bfs
        q.push({dx,dy});
        while(!q.empty())
        {
            pii u = q.front();
            q.pop();
            for(int j = 0; j < 4; j++)
            {
                int x = u.first + mov[j][0];
                int y = u.second + mov[j][1];
                
                if(x < 1 || y < 1 || x > n || y > m) continue;
                if(helo.count(make_pair(x, y))) continue;
                
                if(dist[idx][i][x][y] > dist[idx][i][u.first][u.second] + 1)
                {
                    dist[idx][i][x][y] = dist[idx][i][u.first][u.second] + 1;
                    q.push({x,y});
                }
            }
        }
    }
}

bool fid(pii mid, pii u,pii v)//判断黑洞在不在矩形范围内
{
    if(u.first >= mid.first && u.second >= mid.second && v.first <= mid.first && v.second <= mid.second) return false;
    if(u.first <= mid.first && u.second <= mid.second && v.first >= mid.first && v.second >= mid.second) return false;
    if(u.first >= mid.first && u.second <= mid.second && v.first <= mid.first && v.second >= mid.second) return false;
    if(u.first <= mid.first && u.second >= mid.second && v.first >= mid.first && v.second <= mid.second) return false;
    return true;
}
bool jud(pii u, pii v)
{
    for(auto it = helo.begin(); it != helo.end(); it++)
    {
        if(!fid((*it), u, v))
            return false;
    }
    return true;
}


int main()
{
    scanf("%d%d%d%d",&n,&m,&k,&t);
    for(int i = 0; i < k; i++)
    {
        int x,y;scanf("%d%d",&x,&y);
        helo.insert({x,y});
        vis[{x,y}] = i;
    }
    
    for(auto it = helo.begin(); it != helo.end(); it++) bfs(*it);//依次更新每一个黑洞的bfs
    
    while(t--)
    {
        int x1,y1,x2,y2; scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
        pii x = {x1,y1}, y = {x2,y2};
        
        if(jud(x,y))
        {
            printf("%d\n",(int)(fabs(x1 - x2) + fabs(y1 - y2)));
            continue;
        }
        
        int ans = inf;
        for(auto it = helo.begin(); it != helo.end(); it++)
        {
            int idx = vis[(*it)];
            for(int i = 0 ;i < 4; i++)
            {
                int dx = (*it).first + mov[i][0];
                int dy = (*it).second + mov[i][1];
                
                if(dx < 1 || dy < 1 || dx > n || dy > m) continue;
                if(helo.count(make_pair(dx, dy))) continue;
                
                ans = min(ans, dist[idx][i][x1][y1] + dist[idx][i][x2][y2]);
            }
        }
        printf("%d\n",ans == inf ? -1 : ans);
    }
    return 0;
}

附一个做题心得:
写这道题的时候,思路想出来的很快,不是很难想,代码敲的也不慢。敲完后还WA里两发,纯属脑抽。写题时看见数据范围的时候感觉只能用map去写(n,m小于2e5,n*m),但是交一发T了,拿map写会T。就这样,一天过去了!看了一眼题解的代码,只能说学会了、学会了!new int 是个好东西!直接定义指针,之后在程序里重新申请内存,不得不说,比vector都牛皮。哎!属实是代码次/。

H题:Message Bomb

考点:逆向思维

思路:显然,暴力卡死题!对集合和人分别进行分析,可以分析一个集合收到多少信息,人收到的信息和集合的关系
x->人的信息、y->集合的信息
t = 1:将人加入到集合中,x-y(集合中原本的信息新进来的人不会收到)
t = 2:人离开这个集合了,x+y(更新改人已知的信息,同时也可以将来的时候减去的加回来)
t = 3:x–,y++(集合信息+1,x是发布者,不会得到信息)
最后便利一遍每个人参加的集合就行

代码:

#include <iostream>
#include <algorithm>
#include <queue>
#include <set>

using namespace std;
const int N = 2e5+7;

set<int> st[N];
int peo[N], team[N];
int n,m,s;

int main()
{
    scanf("%d%d%d",&n,&m,&s);
    while(s--)
    {
        int t,x,y;scanf("%d%d%d",&t,&x,&y);
        if(t == 1)
        {
            peo[x] -= team[y];
            st[x].insert(y);
        }
        if(t == 2)
        {
            peo[x] += team[y];
            st[x].erase(y);
        }
        if(t == 3)
        {
            peo[x] --;
            team[y] ++;
        }
    }
    for(int i = 1; i <= m; i++)
    {
        for(auto it = st[i].begin(); it != st[i].end(); it++)
            peo[i] += team[*it];
        printf("%d\n",peo[i]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值