2022牛客蔚来杯第一场

2022牛客蔚来杯第一场

G.

  • 题意

​ 输入n,把1~n中字典序最大的数输出

  • 题解

    签到题,首先是因为数字过大所以需要用字符串表示数字;字典序最大的一定是前length-1个为9,如果n的length-1都是9,那么字典序最大的就是n,否则答案就是length-1个9

  • 代码

#include <iostream>
#include <cstring>

using namespace std;

int main() {
    string str;
    cin>>str;
    
    long long len=str.length();
    string s="";
    for(int i=1;i<len;i++)s+='9';
    cout<<(s>str ? s:str)<<'\n';
    
    return 0;
}

A. Villages: Landlines

  • 题意

    这题题目意思太绕了,但是思维和码量都很小。

    给n个区间范围,求把所有区间的空隙长度为多少

  • 题解

    区间合并算法的应用,把所有可合并的区间合并,不能合并的区间求其间隙

  • 代码

#include <iostream>
#include <algorithm>

using namespace std;
const int N=2e5+5;
typedef long long LL;
typedef pair<int,int> PII;

int n;
PII a[N];

int main() {
    cin>>n;
    for(int i=0;i<n;i++) {
        int x,r;
        cin>>x>>r;
        a[i]={x-r,x+r};//区间左右端点
    }
    
    sort(a,a+n);//左端点排序
    LL res=0;
    int p=a[0].second;
    for(int i=1;i<n;i++) {
        if(a[i].first>p) res+=a[i].first-p;//求间隙长度
        p=max(p,a[i].second);
    }
    
    cout<<res<<'\n';
    return 0;
}

D.Mocha and Railgun

  • 题意

    • 给定一个圆心为原点的圆,以及位于圆内的p点
    • 一个以p为中点且长度为2d的位于圆内的电磁炮,可以p点旋转发射电磁炮
    • 问这个电磁炮能摧毁圆的最大弧长为多少
  • 题解

    • 关于p点和线段2d容易想到两个特殊最值位置,一个是2d线段方向与op连线垂直,另一个是2d线段方向与op平行
    • 画图容易知道,2d线段方向与op垂直时弧长最小,平行最大(用弦长代替弧长判断长短,红色电磁炮为垂直情况,绿色为平行情况)
    • 只需要计算绿色电磁炮对应的弧长即可;为了简化计算,我们可以用圆的对称性,即p(x,y)与p1(sqrt(xx+yy),0)完全等效,那么所有电磁炮方向都看作是向上发射的,可得计算公式:弧长=r*( acos( (sqrt(xx+yy)-d) / r) - acos( (sqrt(xx+yy)+d) / r) )
#include <iostream>
#include <cmath>

using namespace std;

int main() {
    int T;
    cin>>T;
    while(T--) {
        double r,x,y,d;
        cin>>r>>x>>y>>d;
        
        double a=acos((sqrt(x*x+y*y)-d)/r);
        double b=acos((sqrt(x*x+y*y)+d)/r);
        printf("%.12lf\n",r*(a-b));
    }
    return 0;
}

I.Chiitoitsu

  • 题意

    • 初始有手牌13张,相同牌最多2张。34种牌,每种都是4张。
    • 摸一张牌打一张牌,知道凑齐7对牌胜利
    • 给定初始手牌,问最优策略下胜利的期望轮数
  • 题解

    最优策略:开启上帝视角,摸一张牌只要和之后在手中的牌成对就留下否则打出

    使用期望dp

    • 定义:f[i,j]表示牌堆中剩余i张,手中有j张单牌时糊牌的期望轮数

    • 转移

      1. 当再摸一轮,摸到的是新的单牌(牌堆-1,单牌数不变)
        f [ i , j ] + = p 1 ∗ ( f [ i − 1 , j ] + 1 ) , p 1 = 1 − 3 ∗ j / i f[i,j]+=p_1*(f[i-1,j]+1),p_1=1-3*j/i f[i,j]+=p1(f[i1,j]+1)p1=13j/i

      2. 当再摸一轮,摸到的是可以成对的牌(牌堆-1,单牌数-2;牌堆中所有可与单牌配对的种类还有3张,故概率为3j/i )
        f [ i , j ] + = p 2 ∗ ( f [ i − 1 , j − 2 ] + 1 ) , p 2 = 3 ∗ j / i f[i,j]+=p_2*(f[i-1,j-2]+1),p_2=3*j/i f[i,j]+=p2(f[i1,j2]+1),p2=3j/i

    • 故:转移方程为
      f [ i , j ] = p 1 ∗ ( f [ i − 1 , j ] + 1 ) + p 2 ∗ ( f [ i − 1 , j − 2 ] + 1 ) f[i,j]=p_1*(f[i-1,j]+1)+p_2*(f[i-1,j-2]+1) f[i,j]=p1(f[i1,j]+1)+p2(f[i1,j2]+1)

    • 注意:

      • 初始化:f[i,0]=0,单牌数为0时,不需要再摸牌,即期望轮数为0
      • 每次转移会造成j-2,所以j=1需要单独计算
      • 除法取模需要用快速幂
  • 代码

dfs写法进行动态方程转移(记忆化搜索)

#include <iostream>
#include <unordered_map>
#include <cstring>

using namespace std;
const int MOD=1e9+7;
typedef long long LL;

LL f[200][20];
int Case;

LL qmi(int a,int b,int p){
    LL res=1%p;
    while(b){
        if(b&1)res=res*a%p;
        a=a*(LL)a%p;
        b>>=1;
    }
    return res;
}
LL inv(int x) {
    return qmi(x,MOD-2,MOD);
}

LL dfs(int i,int j) {
    if(f[i][j]!=-1) return f[i][j];
    if(j==0) return 0;
    
    LL p1=(i-3*j)*inv(i)%MOD;
    LL p2=(3*j)*inv(i)%MOD;
    if(j==1) f[i][j]=( p1*(dfs(i-1,j)+1)%MOD + p2*1%MOD ) % MOD;
    else f[i][j]=( p1*(dfs(i-1,j)+1)%MOD + p2*(dfs(i-1,j-2)+1)%MOD ) % MOD;

    return f[i][j];
}

void solve() {
    string s;
    cin>>s;
    
    unordered_map<string, int> mp;
    for(int i=0;i<26;i+=2) {
        string str="";
        str+=s[i];
        str+=s[i+1];
        mp[str]++;
    }
    int cnt=0;
    for(auto i:mp) cnt+=(i.second==1);
    
    cout<<"Case #"<<++Case<<": "<<dfs(123,cnt)<<'\n';
}

int main() {
    int T;
    cin>>T;
    
    memset(f, -1 , sizeof f);
    while(T--) solve();
    
    return 0;
}

for循环动态方程转移(全记忆dp)

#include <iostream>
#include <unordered_map>
#include <cstring>

using namespace std;
const int MOD=1e9+7;
typedef long long LL;

LL f[200][20];
int Case;

LL qmi(int a,int b,int p){
    LL res=1%p;
    while(b){
        if(b&1)res=res*a%p;
        a=a*(LL)a%p;
        b>>=1;
    }
    return res;
}
LL inv(int x) {
    return qmi(x,MOD-2,MOD);
}

void init() {
    for(int i=3;i<=123;i++) {
        LL p1=(i-3)*inv(i)%MOD;
        LL p2=3*inv(i)%MOD;
        f[i][1]=( p1*(f[i-1][1]+1)%MOD + p2*1%MOD ) %MOD;
    }
    for(int i=3;i<=123;i++) {
        for(int j=3;j<=13;j++) {
            LL p1=(i-3*j)*inv(i)%MOD;
            LL p2=3*j*inv(i)%MOD;
            f[i][j]=( p1*(f[i-1][j]+1)%MOD + p2*(f[i-1][j-2]+1)%MOD ) %MOD;
        }
    }
}

void solve() {
    string s;
    cin>>s;
    
    unordered_map<string, int> mp;
    for(int i=0;i<26;i+=2) {
        string str="";
        str+=s[i];
        str+=s[i+1];
        mp[str]++;
    }
    int cnt=0;
    for(auto i:mp) cnt+=(i.second==1);
    
    cout<<"Case #"<<++Case<<": "<<f[123][cnt]<<'\n';
}

int main() {
    int T;
    cin>>T;
    
    init();
    while(T--) solve();
    
    return 0;
}

打表做法

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
int ans[]={927105416,745749140,707741534,882102328,781250051,100000041,31,0},I=0;
char str[1005];
void Solve(){
	scanf("%s",str);
	map<string,int> mp;
	for(int i=0;i<26;i+=2){
		string s;
		s+=str[i];
		s+=str[i+1];
		mp[s]++;
	}
	int c2=0;
	for(auto i:mp)c2+=(i.second==2);
	printf("Case #%d: %d\n",++I,ans[c2]);
}
int main(){
	int t;
	cin>>t;
	while(t--)Solve();
}

C.Grab the Seat!

  • 题意

    在(0,1)-(0,m)有一块黑板,在nm的教室里给定k个同学座位,问q次不被挡住的的位置总共有多少个,每问一次有一个同学的位置换动

  • 题解

    • 有座位的人能挡住的范围为黑板断点与座位连线的交线后方的区域。同时对于每一行最靠近黑板的位置是该行的最大遮挡位置,意味着只需要把最靠近黑板的位置找出来就可以替代该行的净遮挡位置

    • 同时可以发现(0,1)与点的交线以及(0,m)与点的交线相互独立,故可以分别计算两个方向的射线未被遮挡的位置数量

    • 以斜率为正的一边为例,从下往上扫,当次行斜率大于上一行的有点的斜率时,被上一行遮挡的一定被这一行遮挡,并且这行遮挡更多,所以更新最大遮挡射线

  • 代码

#include <bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2e5 + 9;
int n, m, k, q;
int x1[N], mn[N];//记录每行最靠近黑板点的横坐标;记录每行没被挡位置的数量
int X[N], Y[N];//座位坐标
signed main() {
    cin >> n >> m >> k >> q;

    for (int i = 1; i <= k; i++)
        scanf("%lld%lld", &X[i], &Y[i]);

    while (q--) {
        int id;
        cin >> id;
        scanf("%lld%lld", &X[id], &Y[id]);//更换座位

        for (int i = 1; i <= m; i++)//初始化每行的x1和mn数组
            x1[i] = n + 1, mn[i] = n;

        for (int i = 1; i <= k; i++)//更新每行最靠近黑板的横坐标
            x1[Y[i]] = min(x1[Y[i]], X[i]);

        int j = 0;

        for (int i = 1; i <= m; i++) { //(0,1)射线的未被遮挡数量
            // (0,1,x1[i],i) (0,1,x1[j],j)
            if (x1[i] != n + 1 && (!j || x1[i] * (j - 1) - (i - 1)*x1[j] < 0))//如果这行有点,且要么是最初状态要么是斜率变大的情况
                j = i;//更新射线纵坐标

            if (j == 1)//如果j=1特殊处理,因为没斜率,直接看坐标
                mn[i] = min(mn[i], x1[i] - 1);
            else//否则(0,1,x1[j],j)射线与x=i的交点计算未被遮挡点的横坐标,记得想上取整
                mn[i] = min(mn[i], j ? ((i - 1) * x1[j] - 1) / (j - 1) : n);
        }

      //对称处理(0,m)射线
        j = 0;

        for (int i = m; i >= 1; i--) { //(0,m)
            // (0,m,x1[i],i) (0,m,x1[j],j)
            if (x1[i] != n + 1 && (!j || x1[i] * (j - m) - (i - m)*x1[j] > 0))
                j = i;

            if (j == m)
                mn[i] = min(mn[i], x1[i] - 1 );
            else
                mn[i] = min(mn[i], j ? ((m - i) * x1[j] - 1) / (m - j) : n);
        }

        int ans = 0;

        for (int i = 1; i <= m; i++)
            ans += mn[i];

        cout << ans << endl;
    }

    return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值