zoukankan      html  css  js  c++  java
  • 2020牛客寒假算法基础集训营1(全解)

    题目链接:https://ac.nowcoder.com/acm/contest/3002#question

    emmm,没什么好说的,就是送人头了,

    题目说明:

    A.honoka和格点三角形      B.kotori和bangdream    C.umi和弓道   D.hanayo和米饭

    (计数问题)                      (水题计算)                 (卡精度题)     (水题)

    E.rin和快速迭代       F.maki和tree        G.eli和字符串           H.nozomi和字符串

    (暴力)                  (DFS遍历)              (二分+前缀和)       (二分+前缀和)

    I.nico和niconiconi    J.u's的影响力

    (DP)                      (矩阵快速幂+数论)

    A.honoka和格点三角形

    题目大意:给你n*m的点图,问你其中有多少个好三角形,其中好三角形定义如下:

    1.所有的点在格点上

    2.至少一条边平行于x或y轴

    3.其面积为1

    样例:

    输入

    2 3

    输出

    6

    输入

    100 100

    输出

    7683984

     没什么好说的,上图:

     

     以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    #define ll long long
    const int mac=2e5+10;
    const int inf=1e9+10;
    const int mod=1e9+7;
    
    int main()
    {
        ll n,m;
        cin>>n>>m;
        ll a=((n-2)*n%mod*(m-1)%mod+(m-2)*m%mod*(n-1)%mod)%mod*2%mod;
        ll b=((m-2)*(n-1)%mod*n+(n-2)*(m-1)%mod*m)%mod*2%mod;
        ll c=((n-2)*(m-1)%mod+(m-2)*(n-1)%mod)%mod*4%mod;
        cout<<(a+b-c+mod)%mod<<endl;
        return 0;
    }
    View Code

    B.kotori和bangdream    

    题目大意:你有x%的概率敲出perfect的响声,得分为a,其余的得b分,问你n个字符你能拿多少分

    示例

    输入

    100 50 500 400

    输出

    45000.00

    每个音符的得分期望是$x%*a+(100-x)%*b$,n个音符的得分总期望就乘以n好了

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    int main()
    {
        int n,x,a,b;
        scanf ("%d%d%d%d",&n,&x,&a,&b);
        double pa,pb;
        pa=x*1.0/100;pb=(100-x)*1.0/100;
        double ans=n*pa*a+n*pb*b;
        printf("%.2f
    ",ans);
        return 0;
    }
    View Code

    C.umi和弓道   

    题目大意:给你一个坐标,你要射n个点,要使得你最多只能射到k个,求挡板的最小长度。挡板只能在x轴或者y轴上,其中每个点都不在坐标轴上

    示例

    输入

    1 1
    2 0
    -1 2
    -2 1

    输出

    0.50000000

     由于要计算挡板的最短长度,那么挡板一定是挡住了n-k个,如果挡住了n-k个以上,那么一定可以将挡板长度减少,所以我们判断n-k就好了,又所有的点都不在坐标轴上就很好办了。首先确定umi所在位置的象限。很明显同一象限的点是不可能用挡板挡掉的,对于剩下的点找出线段和 x轴或 y 轴的交点,统计坐标位置。

    $kx_{1}+b=y_{1}$

    $kx_{2}+b=y_{2}$

    可得:

    $b=frac{y_{1}x_{2}-y_{2}x_{1}}{x_{2}-x_{1}}$   $k=frac{y_{1}-y_{2}}{x_{1}-x_{2}}$

    当交点是x轴的时候,我们令y=0,那么$x=-frac{b}{k}$,交点是y轴的时候就是b了,然后我们交上去就会发现WA了。。。。

    在WA了无数发之后我觉得已经没有什么能改的了,只有精度的问题了,那么在计算坐标点的时候我们尽量减少除法的使用,实际上x,y轴的交点可以更快算出来:

    我们知道斜率$k=frac{y_{1}-y_{2}}{x_{1}-x_{2}}$那么b的值就可以直接随便带个点去减了:$b=y_{0}-kx_{0}$

    仿照b的求法,我们将式子同时除以k:$x+frac{1}{k}b=frac{1}{k}y$ 那么令$y_{0}=0$的时候$x=-frac{1}{k}b$ 而上面的式子我们又可以算出

    $-frac{1}{k}b=x_{0}-frac{1}{k}y_{0}$那么答案也就出来了。我们减少了一次除法运算,只计算了一次k的值

    然后我们分别对x,y轴上的点进行循环取最小n-k长度的大小。

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=1e5+10;
    const double esp=1e-7;
    const double inf=1e10+10;
    
    double point_x[mac],point_y[mac];
    int cntx,cnty;
    
    int same(double x,double y,double x0,double y0)
    {
        if (1.0*x/x0>0 && 1.0*y/y0>0) return 1;
        return 0;
    }
    
    int cross(double x,double y,double x0,double y0)//0->x,1->y,2->x,y
    {
        if (1.0*x/x0<0 && 1.0*y/y0<0) return 2;
        else if (1.0*x/x0<0) return 1;
        else if (1.0*y/y0<0) return 0;
    }
    
    void deal(double x,double y,double x0,double y0,int pt)
    {
        //y=kx+b
        //double k=1.0*(y-y0)/(x-x0);
        //double b=1.0*(y0*x-y*x0)/(x-x0);//刚开始int,y0*x会爆
        //double cross_x=-b/k;
        double b=y0-1.0*x0*(y-y0)/(x-x0);
        double cross_x=x0-1.0*y0*(x-x0)/(y-y0);
        if (pt==0) point_x[++cntx]=cross_x;
        else if (pt==1) point_y[++cnty]=b;
        else {
            point_x[++cntx]=cross_x;
            point_y[++cnty]=b;
        }
    }
    
    int main(int argc, char const *argv[])
    {
        int n,k;
        double x0,y0;
        scanf ("%lf%lf",&x0,&y0);
        scanf ("%d%d",&n,&k);
        int len_num=n-k;
        for (int i=1; i<=n; i++){
            double x,y;
            scanf ("%lf%lf",&x,&y);
            if (same(x,y,x0,y0)) continue;
            int point=cross(x,y,x0,y0);//0代表交点在x,1代表在y,2代表x,y都有
            deal(x,y,x0,y0,point);//找出所有与x,y轴的交点
        }
        sort(point_x+1,point_x+1+cntx);
        sort(point_y+1,point_y+1+cnty);
        double ans=inf;
        for (int i=1; i+len_num-1<=cntx; i++){
            double len_len=point_x[i+len_num-1]-point_x[i];
            ans=min(ans,len_len);
        }
        for (int i=1; i+len_num-1<=cnty; i++){
            double len_len=point_y[i+len_num-1]-point_y[i];
            ans=min(ans,len_len);
        }
        if (fabs(ans-inf)<esp) printf("-1
    ");
        else printf("%.8f
    ",ans);
        return 0;
    }
    View Code

    D.hanayo和米饭

    题目大意:问你1到n缺了哪一个数,给出n,和n-1个数

    示例

    输入

    5
    2 5 1 3

    输出

    4

    没什么好说的,签到题,每个数标记一下,然后遍历输出没标记的那个数就可以了。

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=1e5+10;
    
    int vis[mac];
    
    int main()
    {
        int n,x;
        scanf ("%d",&n);
        for (int i=1; i<n; i++)
            scanf("%d",&x),vis[x]=1;
        for (int i=1; i<=n; i++)
            if (!vis[i]){
                printf("%d
    ",i);
                break;
            } 
        return 0;
    }
    View Code

    E.rin和快速迭代    

    题目大意:$f(x)$为x的因子个数,将f一直迭代下去问迭代到2要多少次例如:$f(12)=6,f(6)=4,f(4)=3,f(3)=2$总共四次

    示例

    输入

    12

    输出

    4

    $10^{12}$看起来很多,实际上我们算因子的时候最多只需要循环$10^{6}$次,而每次计算因子的时候都要开根号,所以直接暴力计算因子数所花费的时间并不是很多

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    #define ll long long
    
    int f(ll x)
    {
        int ans=0;
        if (x==2) return ans;
        ans=1;
        int sum=0;
        ll m=sqrt(x);
        if (m*m==x) sum++,m--;
        for (int i=1; i<=m; i++){
            if (x%i==0) sum+=2;
        }
        return ans+f(sum);
    }
    
    int main()
    {
        ll n;
        scanf ("%lld",&n);
        int ans=f(n);
        printf("%d
    ",ans);
        return 0;
    }
    View Code

    F.maki和tree        

    题目大意:给你一棵树,这个树有 $n$个顶点, $n-1$ 条边。每个顶点被染成了白色或者黑色。取两个不同的点,它们的简单路径上有且仅有一个黑色点的取法有多少?

    示例

    输入

    3
    WBW
    1 2
    2 3

    输出

    3

    经过一个黑点的路径有两种:两个端点都是白点;其中一个端点是黑点。
    因此我们可以先预处理,将每个白点连通块上的白点个数统计出来。这样我们就可以得知每个黑点所连接的白点的权值(即连通块白点数)。
    设某黑点连接了 个白点,第 i 个白点的权值为 f(i) 。

    那么第一种路径的数量就是$sum_{i=1}^{k}sum_{j=i+1}^{k}f(i)*f(j)$如图所示:

     2到其他的白点的有2-4,2-3,2-5,2-6,2-7

    接下来就是4和5到其他白点,由于白块2已经遍历过了,所以往前找,那么就是2*(1+2)....

    第二种就没什么好说的了,把所以的白点个数加起来就好了。

    emmm,不知道为什么段错误。然后我把手动循环改成auto就可以了。。蜜汁BUG。注意答案要用long long,被坑了。。。

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=1e5+10;
    
    vector<int>g[mac],blk[mac];
    int mark[mac],root[mac],sz[mac];
    char s[mac];
    
    void dfs(int x,int fa)
    {
        sz[x]=1;
        for (auto v:g[x]){
            if (v==fa) continue;
            dfs(v,x);
            sz[x]+=sz[v];
        }
    }
    
    int main(int argc, char const *argv[])
    {
        int n;
        scanf ("%d",&n);
        scanf ("%s",s+1);
        int cnt=0;
        for (int i=1; i<=n; i++){
            mark[i]=s[i]=='B';
            if (mark[i]) root[++cnt]=i;
        }
        for (int i=1; i<n; i++){
            int u,v;
            scanf("%d%d",&u,&v);
            if (mark[u] || mark[v]) {
                if (mark[u] && mark[v]) continue;
                if (mark[u]) blk[u].push_back(v);//黑点u的白儿子
                else blk[v].push_back(u);//黑点v的白儿子
                continue;
            }
            g[u].push_back(v);
            g[v].push_back(u);
        }
        int ans=0;
        for (int i=1; i<=cnt; i++){
            int u=root[i];
            int sum=0;
            for (auto v:blk[u]){
                dfs(v,0);//以白儿子v为根进行遍历计算连通块v的大小
                sum+=sz[v];
            }
            ans+=sum;
            for (auto v:blk[u]){
                ans+=sz[v]*(sum-sz[v]);
                sum-=sz[v];
            }
        }
        printf("%d
    ",ans);
        return 0;
    }
    View Code

    G.eli和字符串     

    题目大意:一个仅由小写字母组成的字符串。截取一段连续子串使得这个子串包含至少 $k$ 个相同的某个字母。问子串的长度最小值是多少?

    示例

    输入

    5 2
    abeba

    输出

    3

    看一下题目。。。秒出二分,至于怎么求区间相同字母的个数,直接用前缀和就好了时间复杂度$O(26n)$。加上二分的log就是$O(logn*26n)$

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    #define ll long long
    const int mac=2e5+10;
    const int inf=1e9+10;
    
    char s[mac];
    int dp[mac][30];
    
    int ok(int x,int n,int k)
    {
        for (int i=1; i+x-1<=n; i++){
            int p=-1;
            for (int j=0; j<='z'-'a'; j++){
                p=max(dp[i+x-1][j]-dp[i-1][j],p);
            }
            if (p>=k) return 1;
        }
        return 0;
    }
    
    int main()
    {
        int n,k;
        scanf ("%d%d",&n,&k);
        scanf ("%s",s+1);
        int l=1,r=n,mid,ans=inf;
        for (int i=1; i<=n; i++)
            for (int j=0; j<='z'-'a'; j++){
                dp[i][j]=dp[i-1][j]+(s[i]=='a'+j);
            }
        while (l<=r){
            int mid=(l+r)>>1;
            if (ok(mid,n,k)){
                ans=mid;
                r=mid-1;
            }
            else l=mid+1;
        }
        if (ans==inf) printf("-1
    ");
        else printf("%d
    ",ans);
        return 0;
    }
    View Code

    H.nozomi和字符串

    题目大意:给你一个字符串(只包含01)你有k次将变化字母的机会,你要找一个尽量长的子串,使得你能够在k次操作以内将其全部变成一样的字母,问最长的子串长度

    示例

    输入

    5 1
    10101

    输出

    3

    这题也是一眼二分,和G题一样的,搞个前缀和维护一下就好了

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    const int mac=2e5+10;
    
    char s[mac];
    int dp[mac][2];
    
    int ok(int x,int k,int n)
    {
        for (int i=1; i+x-1<=n; i++){
            if (dp[i+x-1][0]-dp[i-1][0]<=k) return 1;
            if (dp[i+x-1][1]-dp[i-1][1]<=k) return 1;
        }
        return 0;
    }
    
    int main()
    {
        int n,k;
        scanf ("%d%d",&n,&k);
        scanf ("%s",s+1);
        for (int i=1; i<=n; i++){
            for (int j=0; j<=1; j++){
                dp[i][j]=dp[i-1][j]+(s[i]=='0'+j);
            }
        }
        int l=1,r=n,mid,ans=-1;
        while (l<=r){
            mid=(l+r)>>1;
            if (ok(mid,k,n)){
                ans=mid;
                l=mid+1;
            }
            else r=mid-1;
        }
        printf("%d
    ",ans);
        return 0;
    }
    View Code

    I.nico和niconiconi

    题目大意:给你一字符串,其中$nico$得a分,$niconi$得b分,$niconiconi$得c分,其中字符不可重复使用,问你最多能得多少分

    示例

    输入

    19 1 2 5
    niconiconiconiconi~

    输出

    7

    这题一眼dp,状态转移也很好写:

    $if (sbtring(i-3,i)==nico)  dp[i]=max(dp[i],dp[i-4]+a)$

    $if (sbtring(i-5,i)==niconi)  dp[i]=max(dp[i],dp[i-6]+b)$

    $if (sbtring(i-9,i)==niconiconi)  dp[i]=max(dp[i],dp[i-10]+c)$

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
     
    #define ll long long
     
    const int mac=3e5+10;
     
    char s[mac];
    ll dp[mac];
    string s1,s2,s3;
     
    int ok(int x,string ss)
    {
        int len=ss.length();
        int cnt=0;
        if (x<len) return 0;
        for (int i=x-len+1; i<=x; i++){
            if (s[i]!=ss[cnt++]) return 0;
        }
        return 1;
    }
     
    int main(int argc, char const *argv[])
    {
        int n,a,b,c;
        scanf ("%d%d%d%d",&n,&a,&b,&c);
        scanf("%s",s+1);
        s1="nico";s2="niconi";s3="niconiconi";
        for (int i=1; i<=n; i++){
            dp[i]=dp[i-1];
            if (ok(i,s1)) dp[i]=max(dp[i],dp[i-4]+a);
            if (ok(i,s2)) dp[i]=max(dp[i],dp[i-6]+b);
            if (ok(i,s3)) dp[i]=max(dp[i],dp[i-10]+c);
        }
        printf("%lld
    ",dp[n]);
        return 0;
    }
    View Code

    J.u's的影响力

    题目大意:$f(i)=f(i-1)*f(i-2)*a^{b}$,其中$f(1)=x,f(2)=y$,问$f(n)$。重点是$n,x,y,a,b<=10^{12}$。取模1e9+7

    示例

    输入

    4 2 3 2 1

    输出

    72

    这一题才是重头戏。。。

    我们可以先找规律:

    $f(1)=x,f(2)=y,f(3)=xya^{b},f(4)=xy^{2}a^{2b}$

    $f(5)=x^{2}y^{3}a^{4b},f(6)=x^{3}y^{5}a^{7b},f(7)=x^{5}y^{8}a^{12b}$

    .....

    很明显我们可以x,y,a的幂是满足斐波那契数列的变形,

    其中x和y的幂满足$f(i)=f(i-1)+f(i-2)$  a的幂满足$f(i)=f(i-1)+f(i-2)+b$

    那么我们将每个幂算出来就好了(一个简单的矩阵快速幂)。。。。然后你们发现幂太大了,存不下(难道取模吗?)

    。。。。幂如果取模的话好像有问题,不过注意这里的模数是1e9+7,是个素数,我们根据费马小定理$a^{p-1}equiv 1(modp)$

    那么有$a^{1e9+6}equiv 1(mod 1e9+7)$

    也就是说我们可以直接对幂的(1e9+6)取模。。。。好像不用欧拉降幂了

    这里的矩阵也很简单x的幂是$f(1)=1,f(2)=0$,y的幂是$f(1)=0,f(2)=1$,a的幂是$f(1)=0,f(2)=0,f(3)=b$

    那么三个矩阵也很好写出来了:

    这是x的幂

     这是y的幂

     这是a的幂

    以下是AC代码:

    #include <bits/stdc++.h>
    using namespace std;
    
    typedef long long ll;
    const int mod=1e9+7;
    
    int up;
    
    struct Mat
    {
        ll m[5][5];
        Mat(){memset(m,0,sizeof m);}
    };
    
    ll qick(ll a,ll b)
    {
        ll ans=1;
        a%=mod;
        while (b){
            if (b&1) ans=ans*a%mod;
            a=a*a%mod;
            b>>=1;
        }
        return ans;
    }
    
    Mat multi(Mat a,Mat b)
    {
        Mat ans;
        for (int i=1; i<=up; i++)
            for (int j=1; j<=up; j++){
                for (int k=1; k<=up; k++){
                    ans.m[i][j]+=a.m[i][k]*b.m[k][j]%(mod-1);
                    ans.m[i][j]%=(mod-1);
                }
            }
        return ans;
    }
    
    Mat qick_mat(Mat a,ll n)
    {
        Mat ans;
        for (int i=1; i<=up; i++) ans.m[i][i]=1;
        while (n){
            if (n&1) ans=multi(ans,a);
            a=multi(a,a);
            n>>=1;
        }
        return ans;
    }
    
    int main(int argc, char const *argv[])
    {
        ll n,x,y,a,b;
        cin>>n>>x>>y>>a>>b;
        if (n==1){cout<<x%mod<<endl;return 0;}
        else if (n==2){cout<<y%mod<<endl;return 0;}
        else if (x%mod==0 || y%mod==0 || a%mod==0) {cout<<0<<endl;return 0;}//注意!!!
        else {
            up=2;
            Mat mx,star_mx;
            mx.m[1][1]=mx.m[1][2]=mx.m[2][1]=1;
            star_mx.m[2][1]=1;
            mx=qick_mat(mx,n-2);
            mx=multi(mx,star_mx);
            ll ans1=qick(x,mx.m[1][1]);
            //cout<<ans1<<endl;
    
            Mat my,star_my;
            my.m[1][1]=my.m[1][2]=my.m[2][1]=1;
            star_my.m[1][1]=1;
            my=qick_mat(my,n-2);
            my=multi(my,star_my);
            ll ans2=qick(y,my.m[1][1]);
            //cout<<ans2<<endl;
    
            up=3;
            Mat ma,star_ma;
            ma.m[1][1]=ma.m[1][2]=ma.m[1][3]=1;
            ma.m[2][1]=ma.m[3][3]=1;
            star_ma.m[1][1]=b%(mod-1);star_ma.m[2][1]=0;star_ma.m[3][1]=b%(mod-1);
            ma=qick_mat(ma,n-3);
            ma=multi(ma,star_ma);
            ll ans3=qick(a,ma.m[1][1]);
            //cout<<ans3<<endl;
    
            ll ans=(ans1*ans2%mod*ans3)%mod;
            cout<<ans<<endl;
        } 
        return 0;
    }
    View Code
    路漫漫兮
  • 相关阅读:
    C# Linq to XML
    C# StopWatch
    C# 深拷贝代码
    基础练习 完美的代价
    基础练习 矩形面积交
    基础练习 矩阵乘法 时间限制:1.0s 内存限制:512.0MB
    阶乘计算 高精度
    杨辉三角形 递归与非递归
    数的分解 时间限制:1000 ms | 内存限制:65535 KB 难度:1
    数列排序 sort qsort
  • 原文地址:https://www.cnblogs.com/lonely-wind-/p/12261665.html
Copyright © 2011-2022 走看看