2019年CCPC秦皇岛站部分题解

前言

感觉这套题有点难度,相比之下博主是真的菜狗。
不过只要lm队长讲明白MUV LUV EXTRA欢姐绝杀掉Forest Program ,我再去挨个折磨,就等于菜狗博主的大胜利!
在这里插入图片描述


Decimal(数论)

比赛链接:https://acm.dingbacode.com/showproblem.php?pid=6734

题目大意

给出一个整数 n n n,问 1 n \frac{1}{n} n1是否是一个无限小数。

思路

n n n 2 2 2 m m m次幂( m > = 0 m>=0 m>=0)或者是由 a a a 2 2 2 b b b 5 5 5相乘而得,则 1 n \frac{1}{n} n1为有限小数。

AC代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=1e5+100;

int main()
{
    
    
    int t;
    cin>>t;
    while(t--){
    
    
        int n;
        cin>>n;
        while(n%2==0){
    
    
            n/=2;
        }
        while(n%5==0){
    
    
            n/=5;
        }
        if(n==1) cout<<"No"<<endl;
        else cout<<"Yes"<<endl;
    }
}

Invoker(动态规划)

比赛链接:https://acm.dingbacode.com/showproblem.php?pid=6739

题目大意

在游戏Dota2中有一个角色叫做Invoker,他有三个基础技能:QuasWexExort
Invoker拥有三个元素槽,游戏开始的时候元素槽是空的。

  • Invoker使用Quas技能时,他会获得Q元素;
  • Invoker使用Wex技能时,他会获得W元素;
  • Invoker使用Exort技能时,他会获得E元素;

当元素槽充满元素时,下一次释放技能所获得的元素会顶替掉元素槽中获得时间最早的元素。

除了这三个基础技能之外,Invoker还有 10 10 10个特殊技能。每个特殊技能都有对应的三种元素:

  • 急速冷却Cold Snap,需要元素QQQ,用 Y 表示;
  • 幽灵漫步Ghost Walk,需要元素QQW,用 V 表示;
  • 寒冰之墙Ice Wall,需要元素QQE,用 G 表示;
  • 电磁脉冲EMP,需要元素WWW,用 C 表示;
  • 强袭飓风Tornado,需要元素QWW,用 X 表示;
  • 灵动迅捷Alacrity,需要元素WWE,用 Z 表示;
  • 阳炎冲击Sun Strike,需要元素EEE,用 T 表示;
  • 熔炉精灵Forge Spirit,需要元素QEE,用 F 表示;
  • 混沌陨石Chaos Meteor,需要元素WEE,用 D 表示;
  • 超震声波Deafening Blast,需要元素QWE,用 B 表示;

如果玩家想要释放特殊技能 M M M,则需要先将Invoker的元素槽调整为特殊技能 M M M所需要的元素类型,然后按下 R R R键使用元素祈唤融合自身所获得的元素,即可释放特殊技能 M M M

三个基础技能分别对应三个独立的按键,每次释放特殊技能时都需要按下 R R R键。元素在被用于释放特殊技能之后并不会消失,时间顺序也不会改变。

游戏开始,系统给出一个特殊技能的释放序列 s t r str str,你需要按顺序依次施放出所有的特殊技能。
请问你最少需要按多少次键?

思路

博主本来就不擅长 d p dp dp,这还是个描述巨长、情况巨多的 d p dp dp。我当时看着题目的时候没想砸桌子,心想这是哪个家伙出的题。
出的真好,大佬喝茶(●’◡’●)。

题目十分有趣,我还为此专门去看了一下Dota2中这个英雄的介绍
没想到Invoker的技能真的就是这么多,博主这种手残党怕是玩不来了。
不瞎扯了,我们还是来看题吧。

首先我们要明白Invoker什么情况下可以释放特殊技能。
如果我们一开始想要释放技能『寒冰之墙』,『寒冰之墙』需要的元素为QQE。那么就产生一个问题:元素的顺序可以变换吗?比如EQQ可以释放『寒冰之墙』吗?
答案是可以的,题目中提及到unordered element combination,意为无序序列。由此我们可以得知,只要当前的元素槽内的元素和技能所需要的元素相同,元素槽内无论是什么顺序我们都能按下R键释放技能。

紧接着我们假设:按下1键可以释放技能Quas,按下2键可以释放技能Wex,按下3键可以释放技能Exort
这样的话使用出每个技能的按键顺序如下所示:
在这里插入图片描述
在我们把每个技能的释放转换成数字之后不难看出,一个技能的所有按键方案就是其对应的数字序列的全排列。

这样的话对于特殊技能 M M M来说,我们至少获取它的一种释放方式,才能通过全排列把所有的按键方案都求出来。很显然,题目中已经给我们了:
在这里插入图片描述
那么我们需要设计一个函数,用于获取到要使用的特殊技能所需要使用的一种按键方案:

int press[5]; //press[1]:第一个按键 ,press[2]:第二个按键 ,press[3]:第三个按键

void init(int pos)//按照题目中已经给出的按键方案更新press数组的值
{
    
    
    if(str[pos]=='Y')
        press[1]=press[2]=press[3]=1;
    else if(str[pos]=='V')
        press[1]=press[2]=1,press[3]=2;
    else if(str[pos]=='G')
        press[1]=press[2]=1,press[3]=3;
    else if(str[pos]=='C')
        press[1]=press[2]=press[3]=2;
    else if(str[pos]=='X')
        press[1]=1,press[2]=press[3]=2;
    else if(str[pos]=='Z')
        press[1]=press[2]=2,press[3]=3;
    else if(str[pos]=='T')
        press[1]=press[2]=press[3]=3;
    else if(str[pos]=='F')
        press[1]=1,press[2]=press[3]=3;
    else if(str[pos]=='D')
        press[1]=2,press[2]=press[3]=3;
    else if(str[pos]=='B')
        press[1]=1,press[2]=2,press[3]=3;
}

接下来说一下dp数组的含义,dp[i][x][y][z]的值为按照xyz的顺序打完技能序列str中前i个技能所需要的最小操作次数,其中xyz属于第i个技能的按键方案之一。

int dp[maxn][5][5][5];//dp[i][x][y][z]:按照xyz的顺序打出前i个技能所需要的最小操作次数

以样例XDTBVV举例说明。
第一个技能是『强袭飓风』(X),它的按键方案就有122212221(因为R键是必须要按的键,这里省略不说),那么 d p [ 0 ] [ 1 ] [ 2 ] [ 2 ] = d p [ 0 ] [ 2 ] [ 1 ] [ 2 ] = d p [ 0 ] [ 2 ] [ 2 ] [ 1 ] = 4 dp[0][1][2][2]=dp[0][2][1][2]=dp[0][2][2][1]=4 dp[0][1][2][2]=dp[0][2][1][2]=dp[0][2][2][1]=4
( 4 4 4的原因是一开始元素槽是空的,所以无论如何都要按三次技能键+一次R键,就是 4 4 4次)

以下为关键部分,把握不住的建议反复看题+看上面的解释。


假设当前我们已经完成了第i个技能的释放,接下来我们需要去释放第(i+1)个技能。由于第(i+1)个技能有多种按键方案,我们需要一一分析。
假设我们要用第(i+1)个技能的第一种释放方式,例举出接下来所有的按键可能:

一.我们很幸运,释放完第i个技能之后,元素槽里时间顺序靠后的两个元素正好是第一种释放方式所需要的前两个元素。

此时我们只需要再施放我们缺少的那个元素所对应的技能,紧接着按下 R R R键,就可以施放出第(i+1)个技能。

for(int x=1; x<=3; x++)//枚举元素槽中的第一个元素是哪个
    dp[pos][press[1]][press[2]][press[3]]=min(dp[pos-1][x][press[1]][press[2]]+2,dp[pos][press[1]][press[2]][press[3]]);

二.运气还行,释放完第i个技能之后,元素槽里时间顺序中最后一个元素正好是第一种释放方式所需要的第一个元素。

此时我们需要按顺序施放我们缺少的两个元素所对应的技能,紧接着按下 R R R键,就可以施放出第(i+1)个技能。

for(int x=1; x<=3; x++)//枚举元素槽中的第一个元素是哪个
    for(int y=1; y<=3; y++)//枚举元素槽中的第二个元素是哪个
        dp[pos][press[1]][press[2]][press[3]]=min(dp[pos-1][x][y][press[1]]+3,dp[pos][press[1]][press[2]][press[3]]);

三.太倒霉了,释放完第i个技能之后,元素槽里没有可以接着用的,需要我们重新施放技能。

例如当前的元素槽312,接下来需要的元素及其顺序为333,那这个312中没有一个元素会留到下一个技能的施放中(第一个 3 3 3会被你接下来获取的 3 3 3顶替掉)。
此时我们就需要按顺序施放第一个按键方案中的三个元素所对应的技能,紧接着按下 R R R键,才可以施放出第(i+1)个技能。

for(int x=1; x<=3; x++)//枚举元素槽中的第一个元素是哪个
    for(int y=1; y<=3; y++)//枚举元素槽中的第二个元素是哪个
        for(int z=1; z<=3; z++)//枚举元素槽中的第三个元素是哪个
            dp[pos][press[1]][press[2]][press[3]]=min(dp[pos-1][x][y][z]+4,dp[pos][press[1]][press[2]][press[3]]);

四.太走运了,第i个技能和第(i+1)是同一个技能,好耶ヽ(✿゚▽゚)ノ

此时我们只需要再按下 R R R键,就可以施放出第(i+1)个技能。

dp[i][press[1]][press[2]][press[3]]=min(dp[i][press[1]][press[2]][press[3]],dp[i-1][press[1]][press[2]][press[3]]+1);

由于我们并不知道实际上会发生上面哪种可能,所以我们必须全部考虑到。

void solve(int pos,int cnt) //到第i个字符,假定现在需要按cnt次键
{
    
    
    if(cnt==1)
    {
    
    
        for(int x=1; x<=3; x++)
            dp[pos][press[1]][press[2]][press[3]]=min(dp[pos-1][x][press[1]][press[2]]+2,dp[pos][press[1]][press[2]][press[3]]);
    }
    else if(cnt==2)
    {
    
    
        for(int x=1; x<=3; x++)
            for(int y=1; y<=3; y++)
                dp[pos][press[1]][press[2]][press[3]]=min(dp[pos-1][x][y][press[1]]+3,dp[pos][press[1]][press[2]][press[3]]);
    }
    else if(cnt==3)
    {
    
    
        for(int x=1; x<=3; x++)
            for(int y=1; y<=3; y++)
                for(int z=1; z<=3; z++)
                    dp[pos][press[1]][press[2]][press[3]]=min(dp[pos-1][x][y][z]+4,dp[pos][press[1]][press[2]][press[3]]);
    }
}


for(int j=1; j<=3; j++)
{
    
    
    solve(i,j);
    //只按R键的情况
    dp[i][press[1]][press[2]][press[3]]=min(dp[i][press[1]][press[2]][press[3]],dp[i-1][press[1]][press[2]][press[3]]+1);
}

然后就大功告成了,最后遍历最后一个技能所有的按键方案所对应的dp值,找最小即可。
博主比较懒,不想自己枚举出一个技能对应的所有按键方案,所以我就用next_permutation逃课了。
(因为是求3个数的全排列,所以可以逃课)

do
{
    
    
	/**代码主体**/
}while(next_permutation(press+1,press+4));

AC代码

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1e5+100;

int dp[maxn][5][5][5];//dp[i][x][y][z]:按照xyz的顺序打出第i个技能所需要的最小操作次数
int press[5];
string str;
void init(int pos)
{
    
    
    if(str[pos]=='Y')
        press[1]=press[2]=press[3]=1;
    else if(str[pos]=='V')
        press[1]=press[2]=1,press[3]=2;
    else if(str[pos]=='G')
        press[1]=press[2]=1,press[3]=3;
    else if(str[pos]=='C')
        press[1]=press[2]=press[3]=2;
    else if(str[pos]=='X')
        press[1]=1,press[2]=press[3]=2;
    else if(str[pos]=='Z')
        press[1]=press[2]=2,press[3]=3;
    else if(str[pos]=='T')
        press[1]=press[2]=press[3]=3;
    else if(str[pos]=='F')
        press[1]=1,press[2]=press[3]=3;
    else if(str[pos]=='D')
        press[1]=2,press[2]=press[3]=3;
    else if(str[pos]=='B')
        press[1]=1,press[2]=2,press[3]=3;
}

void solve(int pos,int cnt)
{
    
    
    if(cnt==1)
    {
    
    
        for(int x=1; x<=3; x++)
            dp[pos][press[1]][press[2]][press[3]]=min(dp[pos-1][x][press[1]][press[2]]+2,dp[pos][press[1]][press[2]][press[3]]);
    }
    else if(cnt==2)
    {
    
    
        for(int x=1; x<=3; x++)
            for(int y=1; y<=3; y++)
                dp[pos][press[1]][press[2]][press[3]]=min(dp[pos-1][x][y][press[1]]+3,dp[pos][press[1]][press[2]][press[3]]);
    }
    else if(cnt==3)
    {
    
    
        for(int x=1; x<=3; x++)
            for(int y=1; y<=3; y++)
                for(int z=1; z<=3; z++)
                    dp[pos][press[1]][press[2]][press[3]]=min(dp[pos-1][x][y][z]+4,dp[pos][press[1]][press[2]][press[3]]);
    }
}
int main()
{
    
    
    while(cin>>str)
    {
    
    
        int len=str.size();
        for(int i=0; i<len; i++)
            for(int x=1; x<=3; x++)
                for(int y=1; y<=3; y++)
                    for(int z=1; z<=3; z++)
                        dp[i][x][y][z]=inf;
        ///初始化第一个技能
        init(0);
        do
        {
    
    
            dp[0][press[1]][press[2]][press[3]]=4;

        }
        while(next_permutation(press+1,press+4));
        for(int i=1; i<len; i++)
        {
    
    
            init(i);
            int cnt=1;
            do
            {
    
    
                for(int j=1; j<=3; j++)
                {
    
    
                    solve(i,j);
                    dp[i][press[1]][press[2]][press[3]]=min(dp[i][press[1]][press[2]][press[3]],dp[i-1][press[1]][press[2]][press[3]]+1);
                }
            }
            while(next_permutation(press+1,press+4));
            //cout<<endl;
        }
        int ans=inf;
        init(len-1);
        do
        {
    
    
            ans=min(ans,dp[len-1][press[1]][press[2]][press[3]]);
        }
        while(next_permutation(press+1,press+4));
        cout<<ans<<endl;
    }
}

Angle Beats(计算几何)

比赛链接:https://acm.dingbacode.com/showproblem.php?pid=6731

题目大意

首先给出n个点的坐标,接下来有q次询问。
每次询问会给出一个新点A,请问在这n+1个点(算上点A),可以组成直角三角形的并含有点A的组合一共有多少种?

思路

大佬的方法https://www.cnblogs.com/carcar/p/11688108.html,看完之后茅塞顿开。

和之前一场ABC的D题类似(放心,那场ABC已经在写了,该有的都会有)。
解题思路从高中时候学过的知识:向量A=(x1,y1)与向量B=(x2,y2)垂直则有x1*x2+y1*y2=0开始下手。
如果三个点可以形成直角三角形,那么我们要考虑点A在哪个位置:

一.点A为直角点。

在这里插入图片描述

二.点A不是直角点。

此时A有两个位置,但是影响吗?不影响,问题不大。
在这里插入图片描述
由于数据量很小( n < = 2000 , q < = 2000 n<=2000,q<=2000 n<=2000,q<=2000),所以我们可以通过枚举直角点的方法来暴力这道题。
关于如何存储向量,大佬的博客里说的很清楚(那场ABC的D题也是这样存就可以)。

最后再说一个点吧,虽然给的时间是15s,但实际跑下来我T了两次。
最后发现是map[]拖的时间,这就需要找人问一下效率问题了。记得之前有一次就是map.find()要比我的map[]快很多。

//T掉的代码
if(mp[getk(-y,x)])
    ans[i]+=mp[getk(-y,x)];

//AC代码(跑了14086ms,相当极限)
if(mp.count(getk(-y,x)))
    ans[i]+=mp[getk(-y,x)];

AC代码

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
using namespace std;
typedef long long ll;
const int maxn=1e5+100;

int ans[2050];
pair<int,int> a[2050],b[2050];

pair<int,int> getk(int x,int y){
    
    
    int m=__gcd(x,y);
    x/=m,y/=m;
    if(x<0) x=-x,y=-y;
    else if(x==0&&y<0) y=-y;
    return make_pair(x,y);
}
int main()
{
    
    
    int n,q;
    while(~scanf("%d%d",&n,&q)){
    
    
        for(int i=1;i<=n;i++)
            scanf("%d%d",&a[i].first,&a[i].second);
        for(int i=1;i<=q;i++)
            scanf("%d%d",&b[i].first,&b[i].second),ans[i]=0;
        map<pair<int,int>,int> mp;
        for(int i=1;i<=q;i++){
    
    
            mp.clear();
            for(int j=1;j<=n;j++){
    
    
                int x=a[j].first-b[i].first;
                int y=a[j].second-b[i].second;
                mp[getk(x,y)]++;
                if(mp.count(getk(-y,x)))
                    ans[i]+=mp[getk(-y,x)];
            }
        }
        for(int i=1;i<=n;i++){
    
    
            mp.clear();
            for(int j=1;j<=n;j++){
    
    
                if(i==j) continue;
                int x=a[i].first-a[j].first;
                int y=a[i].second-a[j].second;
                mp[getk(x,y)]++;
            }
            for(int j=1;j<=q;j++){
    
    
                int x=b[j].first-a[i].first;
                int y=b[j].second-a[i].second;
                if(mp.count(getk(-y,x)))
                    ans[j]+=mp[getk(-y,x)];
            }
        }
        for(int i=1;i<=q;i++)
            printf("%d\n",ans[i]);
    }
}

后话

感谢阅读,希望能对你产生一点用处。

以下台词取自《银魂:完结篇·永远的万事屋》:
(如果心情不好,就去看银魂。一集不够就看一个篇章,还是不够就去看剧场版。)

在这里插入图片描述

"我应该背负的罪业"
"不管重来多少次我都会背负给你看"
"你们那些下三滥的诅咒"
"不管多少次我都会承受给你看"
"不论你们打算诅咒我多少次"
"不管这双手打算毁灭这世界多少次"
"我的世界,也不会毁灭的"
"就凭你们的诅咒,和我这背负罪业的双手"
"是无法分开我和这帮蠢朋友的"

吾日三省吾身:日更否?刷题否?快乐否?
更新了,但不是日更;已刷;激动
路漫漫其修远兮,吾将上下而求索

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/qq_45750296/article/details/121265784