UPC 2022暑期个人训练赛第01场

本文介绍了多个算法问题的解决思路,包括数论中的求三角形个数、四则运算的顺序搜索、差分与前缀和在加密情报中的应用、相邻正方形数量的计算、棋盘模拟、相邻数交换、特定数的素数对计数以及线性DP。文章强调了在处理不同问题时的关键知识点和优化技巧,如等差数列求和的取模规则、搜索策略和动态规划的应用。
摘要由CSDN通过智能技术生成

问题 A: 牛牛数三角形(数论)

思路:

先求出所给目标点上面行的的总个数再加上本行的总个数对 10 取模即可,所给目标点上边行恰好是一个等差数列 , 求解前 n 项 和 ,注意要 开 ll , 并且边处理 便取模;

知识点 :1 取模规则:

在这里插入图片描述
一定一定注意除法是不能拆开取模的;!!!

知识点 2:数据范围
int 2e9
long long 9e18
知识点 3:

在等差数列前 n 项 和公式中 (首项+末项) 和 项数一定有一个是 2的倍数
可以借助这个性质防止爆 ll

#include<bits/stdc++.h>
using namespace std;
 
const int N = 2e5+10;
typedef long long ll;
 
ll n,x,y,sum;//sum 记录上方行个数
 
const int p = 10;
 
int main()
{
    cin>>n>>x>>y;//n 是首项
     
    ll m=n-(x-2);//末项
    ll k=x-1;//项数
    
    if(x==1)
    {
    	sum=0;
	}//x==1 时无上方行
    else
    if(k%2==0)
    {
    	k=(k/2)%p;//项数是 2 的倍数 ,先对项数除2
		sum=(m%p+n%p)%p*k;
		sum%=p;//再计算前 n 项和
	}
	else
	{
		sum=(n+m)/2%p;//如果 (首项+末项)是 2 的倍数,对这部分 ÷ 2 ,一定注意 不要对 n 和 m 取余 ,因为后面要 除 2 ,出发不满足连续取模的性质!!!!!!!!!!!
		sum=(sum*k)%p;
	}
    
    sum=(sum+y-1)%p;//位置从 0 开始 ,最后要 减 1
    cout<<sum;
}

问题 B: 牛牛的四则运算

思路:

搜索两遍,第一遍记下乘除的顺序,第二遍记下加减的顺序,然后输出即可

#include<bits/stdc++.h>
using namespace std;
 
const int N = 1e6+10;
typedef long long ll;
 
int n;
string s;
int len,cnt;
 
int a[N];
 
int main()
{
    cin>>len;
    cin>>s;
    for(int i=0;i<len;i++)
    {
        if(s[i]=='*'||s[i]=='/')
        {
            a[i]=++cnt;
        }
    }
    for(int i=0;i<len;i++)
    {
        if(s[i]=='+'||s[i]=='-')
        {
            a[i]=++cnt;
        }
    }
    for(int i=0;i<len;i++)
    {
        if(s[i]!='+'&&s[i]!='-'&&s[i]!='*'&&s[i]!='/')
        {
            cout<<s[i];
        }
        else
        {
            cout<<s[i];
            cout<<"["<<a[i]<<"]";
        }
    }
     

问题 E: X特工的加密情报(差分,前缀和)

思路:

操作 m 次, 每次对 一个区间加 1,这不就是差分前缀和的操作吗

#include<bits/stdc++.h>
using namespace std;
 
const int N = 1e6+10;
typedef long long ll;
 
string s;
int cf[N];
int n,a,b,sum;
 
int main()
{
    cin>>s;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        cin>>a>>b;
        cf[a-1]++;
        cf[b]--;
    }
    //由于字符串下标 0 开始 ,所以差分数组也要对应左移一位
     
    for(int i=0;i<(int)(s.size());i++)
    {
        sum=sum+cf[i];
         
        int k=(s[i]-'a'+1+sum)%26;
        if(k==0) k=26;
         
        cout<<(char)(k+'a'-1);
    }
     
     
     
}
问题 L: Adjacent Squares
大意:

给出一个 n * m 的矩形 ,每个点都是一个小正方形 ,给出其中一个小正方型的坐标 , 问其相邻的小正方形有几个(相邻就是上下左右)

思路:

搜索所给位置上下左右四个点是否越界即可

#include<bits/stdc++.h>
using namespace std;
 
const int N = 1e6+10;
typedef long long ll;
 
int n,m;
int d1,d2,cnt;
 
int main()
{
    cin>>n>>m;
    cin>>d1>>d2;
     
    if(d1-1>=1) cnt++;
    if(d1+1<=n) cnt++;
    if(d2-1>=1) cnt++;
    if(d2+1<=m) cnt++;
    cout<<cnt<<endl;
     
     
}

问题 M: Enlarged Checker Board

大意:

构建一个 a*b 的由点组成的矩形 ,然后以这个矩形为单位,构造 n 行 n 列 这样的矩形 ,要求是相邻矩形组成不相同,即点矩形旁边都是 #矩形 #矩形旁边都是点矩形

思路;

自己模拟算了,没什么具体思路;

#include<bits/stdc++.h>
using namespace std;
 
const int N = 1e6+10;
typedef long long ll;
 
int n,a,b;
string s1,s2;
int flag;
 
int main()
{
    cin>>n>>a>>b;
     
    for(int i=1;i<=b;i++)
    {
        s1+='.';
        s2+='#';
    }
     
    for(int k=1;k<=n;k++)
    {
        for(int i=1;i<=a;i++)
        {
            if(k%2!=0)
            flag=-1;
            else
            flag=1;
            for(int j=1;j<=n;j++)
            {
                if(flag<0) cout<<s1;
                else cout<<s2;
                flag=-flag; 
            }
            cout<<endl;
        }
    }
     
     
}

问题 N: Adjacent Swaps(模拟交换)

大意:

一开始有n个数 ,从 1 - n 排列 ,每次询问找到 数 i 与其右边的数交换, 最右侧的数与其左侧数交换;输出交换完后的序列

思路:

数据范围比较大,我们找一个数,一个不能在数组里搜索这个数,太慢了,我们可以一开始记住这个数的位置,每次移动一次把记录的位置也改变,直接通过位置来访问数就能优化到O(1) 的复杂度了;

#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
 
const int N = 2E5+10;
 
int n,q,k;
int a[N];
int id[N];
 
int main()
{
     
    cin>>n>>q;
     
    for(int i=1;i<=n;i++) a[i]=i,id[i]=i;
     //一开始每个数的位置就是其下标
    for(int i=1;i<=q;i++)
    {
        cin>>k;
         
        if(id[k]!=n)
        {
            int idd=id[k];//找到要询问数的位置
            int ss=a[idd+1];//找到下一个数
            swap(a[idd],a[idd+1]);//交换两个数
            swap(id[k],id[ss]);//交换两个数的位置
        }
        else
        {//询问最后一个数时注意和左侧数交换
            int idd=id[k];
            int ss=a[idd-1];
            swap(a[idd],a[idd-1]);
            swap(id[k],id[ss]);
        }
    }
     
    for(int i=1;i<=n;i++) cout<<a[i]<<" ";
     
}

问题 O: 250-like Number

大意:

给出一个数 n 要求 找出任意两个素数(p,q) 使得 p ∗ q 3 < = n p*q^3<= n pq3<=n ,问这样的素数对有多少;

思路:

没优化,欧拉筛+暴力枚举

#include<bits/stdc++.h>
using namespace std;
 
typedef unsigned long long ll;
//ios::sync_with_stdio(false);
const ll N = 1e8+10;
 
bool as[N+1];
 
int prime[N+1];
 
ll k,n,t,cnt,cnts;
 
void isprime()
{
    as[0]=1;
    as[1]=1;
     
    for(int i=2;i<=N;i++)
    {
        if(!as[i]) prime[++cnt]=i;
         
        for(int j=1;j<=cnt&&i*prime[j]<=N;j++)
        {
            as[i*prime[j]]=1;
            if(i%prime[j]==0) break;
        }
    }
}
 
int main()
{
     
    isprime();
     
    cin>>n;
     
    for(int i=1;i<=cnt;i++)
    {
        ll a=prime[i],s=prime[i+1];
        if(a*s*s*s>n) break;
        for(int j=i+1;j<=cnt;j++)
        {
            ll b=prime[j];
//          cout<<a*b*b*b<<endl;
            if(a*b*b*b<=n) cnts++;
            else break;
        }
    }
     
    cout<<cnts;
     
}
优化思路:

只有大体思路,那就是算出需要枚举的素数范围,减少素数枚举个数;

问题 P: Prefix Equality(线性DP)

大意:

给出两个长度为 n 的序列,每次给出一个询问 p q ,问 a 的 前 p 个元素 和 b 的前 q 个元素 组成是否相同

思路:

我们记下每个元素最开始出现的位置,然后记下每个序列中前 i 个 元素 在 另一个序列中出现的最大位置,没出现记为无穷大 ,最后比较即可

#include<bits/stdc++.h>
using namespace std;
 
typedef long long ll;
 
const int N = 2e5+10;
const int inf = 0x3f3f3f3f;//无穷大
int n,q,x,y;
int a[N],b[N];
map<int,int>mp1;
map<int,int>mp2;
int ansa[N],ansb[N];
// ansa 对于 a 的前 n 个数,在 b 中出现的初始下标的最大值
 
int main()
{   
    ios::sync_with_stdio(0);
    cin.tie(0);
 
    cin>>n;
     
    for(int i=1;i<=n;i++)
    {
        cin>>a[i];
        if(!mp1.count(a[i])) mp1[a[i]]=i;
    }
    for(int i=1;i<=n;i++)
    {
        cin>>b[i];
        if(!mp2.count(b[i])) mp2[b[i]]=i;
    }
    //记录每个元素首次出现的位置
     
     
    for(int i=1;i<=n;i++)
    {
        ansa[i]=max(ansa[i-1],(mp2.count(a[i]) ? mp2[a[i]] : inf));
        ansb[i]=max(ansb[i-1],(mp1.count(b[i]) ? mp1[b[i]] : inf));
    }//记录每个序列前 i 个元素 在 另一个序列中出现的最大位置
     
    cin>>q;
     
    while(q--)
    {
        cin>>x>>y;
        //当同时满足这两个条件时一定能保证数集相等,一旦一个里面有另一个不存在的元素就会出现无穷大,就不会满足条件
        if(y>=ansa[x]&&x>=ansb[y])
        {
            puts("Yes");
        }
        else
        {
            puts("No");
        }
    } 
}
反思:

这个题思路真的很巧妙,没有真的去比较每一个元素,而是记下了一个数列中元素在另一个数列中的位置的最大值,看给出的范围是否比最大值大,如果这个元素从来没有出现过的话就标记为无穷大,以后的对应数集不可能再组成相同,非常巧妙;
这个题思路值得反复思考!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

QiWen.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值