2022牛客寒假算法基础集训营4

这场应该是目前最简单的一场了,但是我打的稀烂,赛中8赛后10,不出所料的掉分了,差点掉下1600了,棹。

A:双指针

C:正解是差分(但是我写了个尺取也过了)

D:计算几何(补)

E:签到

F:模拟

G:数论,欧拉降幂(补)

H:签到

I:01背包

J:数论,质因数分解

K:构造,签到

D:雪色光晕

 垃圾计算几何,狗都不写(,赛中忘了有板子,搁那手推公式,wa3到结束,然后把所有的int都改成double就莫名其妙地a了,咱也不懂哪里出了问题,那些点的坐标明明都是整数,呜呜呜。

题解里兰子哥哥有一种做法比较简单:

判断垂足是否在线段上可以直接判断三角形的两个角是否都为锐角,不用把垂足计算出来,计算点到直线的距离可以用三角形的面积除边长,面积直接用海伦公式就行。

说得好,我选择去搞一个板子(。

 #include <bits/stdc++.h>
 using namespace std;
 #define fast ios::sync_with_stdio(0), cin.tie(0)
 //就是这里,我本来写的#define int long long,哎,不是很懂
 #define int double
 #define ll long long
 #define INF 0x3f3f3f3f
 const int mod=1000000007;
 ​
 double dis0(int x,int y,int x0,int y0){
     return sqrt((x0-x)*(x0-x)+(y0-y)*(y0-y));
 }
 bool judge(int x1,int y1,int x2,int y2,int x0,int y0){
     double a=y2-y1;
     double b=x1-x2;
     double c=x2*y1-x1*y2; 
     double tem=(b*b*x0-a*b*y0-a*c)/(a*a+b*b);
     if(tem<=max(x1,x2)&&tem>=min(x1,x2)){
         return true;
     }else{
         return false;
     }
 }
 double dis(int x1,int y1,int x2,int y2,int x0,int y0){
     double a=y2-y1;
     double b=x1-x2;
     double c=x2*y1-x1*y2; 
     if(judge(x1,y1,x2,y2,x0,y0)){
         return (abs(a*x0+b*y0+c)/sqrt(a*a+b*b));
     }else{
         return min(dis0(x2,y2,x0,y0),dis0(x1,y1,x0,y0));
     }
     
 }
 signed main(){
     int n;
     cin>>n;
     double ans=INF;
     int x0,y0,x,y;
     cin>>x0>>y0>>x>>y;
     ans=min(ans,dis0(x0,y0,x,y));
     int ox,oy;
     for(int i=0;i<n;i++){
         ox=x0;
         oy=y0;
         int a,b;
         cin>>a>>b;
         x0+=a;
         y0+=b;
         ans=min(ans,dis(ox,oy,x0,y0,x,y));
     }
     printf("%.8lf\n",ans);
 }

G:子序列权值乘积

 一道数论,比赛时卡计算几何没来得及看,虽然看了也不一定会,首先要能想到这些非空子序列我们只需要知道它的最大值和最小值即可,所以简单的想法就是我们去枚举最大值和最小值以及区间长度,但是三重循环肯定会超时,因此我们考虑怎么优化,首先,最大值和最小值都要枚举,那肯定想到先排个序,这样我们只用枚举最大值和最小值,因为定好最大值和最小值后,我们满足这样的最大值和最小值的子序列的其他数只能来自我们刚才枚举的最大值和最小值的中间,而由于我们刚才枚举最大最小值,因此区间长度也能算出,假设区间长度是k,其中有k-2个数可能成为子序列中的数,而每个数有选和不选两种操作,因此是2^(k-2)中方案数,每个方案对答案的价值都是刚才枚举的最大值*最小值。

到这里思路有了,但是n^2的复杂度还是会超时,我们还要优化,刚才我们说到定了区间长度后,方案数就确定了,所以我们考虑枚举这个区间长度会如何(下图来自兰子哥哥的题解【题解】2022年第四场寒假集训营题解ACM竞赛ACM/CSP/ICPC/CCPC/比赛经验/题解/资讯牛客竞赛OJ牛客网 (nowcoder.com)):

我们把指数提取出来,就能发现底数的规律了,每次除两个数(别忘了逆元),或者从长区间向段区间枚举就是每次乘上两个数,那么就可以在O(1)的时间内算出每个长度对答案的贡献了,然而到了这里又出现了一个问题,这题由于数据范围肯定是需要快速幂的,但是快速幂的次方数是不能取模再计算的,那怎么处理这个2的len次方就是个问题。

当当,欧拉降幂就来了:我直接说结论,证明大家可以看兰子哥哥的或者百度。

(前提是a和p互素)

 #include <bits/stdc++.h>
 using namespace std;
 #define int long long
 #define ll long long
 const int mod=1e9+7;
 ll quick_pow(ll a,ll b,ll mod) {ll res=1;a%=mod; assert(b>=0); for(;b;b>>=1){if(b&1)res=res*a%mod;a=a*a%mod;}return res;}
 ll inv(ll x,ll mod) {return quick_pow(x, mod-2,mod);}
 ​
 int x[200001];
 signed main(){
     int n;
     cin>>n;
     int ans=1;
     for(int i=0;i<n;i++){
         cin>>x[i];
         ans*=x[i];
         ans%=mod;
         ans*=x[i];
         ans%=mod;
     }
     sort(x,x+n);
     int l=1,r=n-2;
     int tem=1;
     for(int i=0;i<n-1;i++){
         tem*=x[i];
         tem%=mod;
         tem*=x[i+1];
         tem%=mod;
     }
     for(int len=2;len<=n;len++){
         ans*=quick_pow(tem,quick_pow(2,len-2,mod-1),mod)%mod;
         tem*=inv(x[l++],mod)%mod;
         tem%=mod;
         tem*=inv(x[r--],mod)%mod;
         ans%=mod;
         tem%=mod;
     }
     cout<<ans<<endl;
     return 0;
 }

J:区间合数的最小公倍数

 数论数论,又是数论,卡了两个小时,最后还是在zwt大人的点拨下才a掉,呜呜呜,太菜了。

首先看到l和r的范围很小,我毫不犹豫直接暴力枚举,然后拿gcd求lcm,但是不对,因为取模之后算出的gcd就不是原先的gcd了,所以这个方法寄了。

其实像这种和素数,合数,公倍数,公因数有关的问题立马就应该想到质因数分解(群里说的,呜呜呜)我们要求一堆合数的最小公倍数,首先,每个合数都可以唯一分解成若干个素数的乘积(定理),那么我们只需要找出每个质因子可能需要的最大次数即可,比如4可以分成2*2,那么它就需要两个2,6可以分解成2和3,那么它就需要一个2和一个3,像这样把每个数都去分解质因数,算出每个质因子所需要的最大数量,然后乘起来就是他们的最小公倍数

 #include <bits/stdc++.h>
 using namespace std;
 #define fast ios::sync_with_stdio(0), cin.tie(0)
 #define int long long
 #define ll long long
 const int mod=1000000007;
 bool is_prime[30010];
 void Eratosthenes(int n)
 {   
     for(int i=0;i<=n;i++){
         is_prime[i]=1;
     }
     for(int i = 2 ; i*i<=n; ++i)
         if(is_prime[i])
         for(int j=i*i ; j<=n ; j+=i)is_prime[j] = false;
 }
 map<int,int>mp;
 signed main(){
     fast;
     Eratosthenes(30010);
     //我这里是把所有的素数拉出来一个一个看它的最大数量是多少,因为素数一共也没几个,因此不会超时
     vector<int>tes;
     for(int i=2;i<=30000;i++){
         if(is_prime[i]==1){
             tes.push_back(i);
         }
     }
     int l,r;
     cin>>l>>r;
     int ans=1;
     for(int i=0;i<tes.size();i++){
         int cnt=0;
         for(int j=l;j<=r;j++){
             cnt=0;
             if(is_prime[j]==1){
                 continue;
             }
             int t=j;
             while(t%tes[i]==0){
                 cnt++;
                 mp[tes[i]]=max(mp[tes[i]],cnt);
                 t/=tes[i];          
             }   
         }
     }
     for(auto i:mp){ 
         for(int j=0;j<i.second;j++){
             ans*=i.first;
             ans%=mod;
         }       
     }
     if(ans==1){
         cout<<-1<<endl;
         return 0;
     }
     if(ans<0){
         ans+=mod;
     }
     cout<<ans<<endl;
 }

总结:打的稀烂,卡个数论卡这么久,早点去做背包估计计算几何也出了,呜呜呜。B题是进制线段树,笑死,线段树都忘完了,留个坑,以后再补。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

wuhudaduizhang

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

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

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

打赏作者

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

抵扣说明:

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

余额充值