牛客算法周周练8

偷懒了快两个月。。终于回来了

链接:https://ac.nowcoder.com/acm/contest/5803

A - 小A买彩票

题意:

买一张彩票需要$3$元,可能得到$1,2,3,4$元,问连续买n张彩票不亏损的概率是多少,要求以最简分数表示概率

思路:

用$dp[i][j]$表示买了$i$张彩票得到$j$元的方案数,显然总的方案数为$4^{n}$

那么很显然状态转移方程为: $dp[i][j] = dp[i-1][j-1] + dp[i-1][j-2] + dp[i][j-3] + dp[i][j-4] $

最后,由于要最简分数,分子分母必须同时除上二者的$GCD$

#include<iostream>
#include<algorithm>
#include<cstring>
#include<cmath> 
 using namespace std;
 typedef long long ll;
 ll dp[32][125];
 int main()
 {
     int n;
     scanf("%d",&n);
     memset(dp,0,sizeof(dp));
     if(n==0){
         cout<<"1/1";
         return 0;
     }
    if(n==1){
        cout<<"1/2";
        return 0;
    }
    ll x=0,y=1;
    for(int i=1;i<=n;i++) y*=4;
    dp[1][1]=dp[1][2]=dp[1][3]=dp[1][4]=1;
    for(int i=2;i<=n;i++){
        for(int j=1;j<=120;j++){
            if(j-1>0&&j>=i) dp[i][j]+=dp[i-1][j-1];
            if(j-2>0&&j-1>=i) dp[i][j]+=dp[i-1][j-2];
            if(j-3>0&&j-2>=i) dp[i][j]+=dp[i-1][j-3];
            if(j-4>0&&j-3>=i) dp[i][j]+=dp[i-1][j-4];
            if(i==n&&j>=n*3) x+=dp[i][j];
        }
    }
    ll k=__gcd(x,y);
    x/=k,y/=k;
    cout<<x<<"/"<<y; 
     return 0;
 }
View Code

B  -「金」点石成金

题意:

$n$次操作,你可以选择增加$a_{i}$点财富,减少$b_{i}$点法力,或者增加$c_{i}$点法力,减少$d_{i}$点财富(如果财富或法力小于$0$时,则变为$0$),求最后法力*财富的最大值

思路:

由于$n$最大为15,我们可以暴力枚举每种情况,因此可以使用$DFS$,但是我选择了二进制枚举的做法

枚举$0$到$2^{n}-1$中的每一个数,如果该数二进制表示中第$i$位为$1$则表示该次进行第一种操作,否则表示进行第二次操作

每次模拟得到最后的值,并记录最大值即可

#include<iostream>
#include<algorithm>
 using namespace std;
 typedef long long ll;
 const int maxn=1e6+10;
 struct node{
    ll a,b,c,d;
 }arr[maxn];
 int main()
 {
    int n;
    scanf("%d",&n);
    ll x,y,ans=0;
    for(int i=1;i<=n;i++)
        scanf("%lld%lld%lld%lld",&arr[i].a,&arr[i].b,&arr[i].c,&arr[i].d);
    for(int i=0;i<(1<<n);i++){
        ll x=0,y=0;
        for(int j=0;j<n;j++){
            if((i>>j)&1==1){
                x+=arr[j+1].a;
                y=max(0ll,y-arr[j+1].b);
            }
            else{
                y+=arr[j+1].c;
                x=max(0ll,x-arr[j+1].d);
            }
        }
        ans=max(ans,x*y);
    }
    cout<<ans;
    return 0;
 }
View Code

C - 小阳的贝壳(线段树维护差分数组)

题意:

给一个序列,进行三种操作

操作$1$.  区间$[l,r]$所有数加上$x$

操作$2$. 求区间$[l,r]$中所有相邻的数的最大差值

操作$3$. 求区间$[l,r]$中所有数的最大公因数

思路:

对于本题,我们不应该维护普通数组,而应该维护一个差分数组

对于差分数组,如果将原数组的区间$[l.r]$加上$x$等同于在差分数组的$l$位置加上$x$,并且在$r+1$处减去$x$,因此操作一就变成了简单的单点修改了

但是,对于操作二三我们需要维护什么信息呢?

首先,差分数组本身保存的就是原数组中相邻两数的差值,因此对操作二我们只要维护差分数组绝对值的最大值即可

其次,根据辗转相除的定义,$GCD$会有如下性质$gcd(a,b) = gcd(a,a-b) , gcd(b,c) = gcd(b,c-b) gcd(a,b,c)=gcd(a,b-a,c-b)$

因此一个区间的$gcd$就等于原数组中区间第一个数与差分数组中的其他数的$gcd$,所以我们还需要维护差分数组的区间$GCD$

至于区间第一个数如何求,根据定义求一个查分数组的前缀和即可,因此我们还需要维护一个查分数组的区间和

#include<iostream>
#include<algorithm>
#include<cmath>
#define ls rt<<1
#define rs rt<<1|1
 using namespace std;
 const int maxn=1e5+10;
 struct node{
     int sum,mx,gcd;//差分区间和,区间最大值,区间GCD 
 }t[maxn<<2];
 int a[maxn],b[maxn]; //原数组,差分数组 
 void push_up(int rt)
 {
     t[rt].sum=t[ls].sum+t[rs].sum;
     t[rt].mx=max(t[ls].mx,t[rs].mx);
     t[rt].gcd=__gcd(t[ls].gcd,t[rs].gcd);
 }
 void build(int l,int r,int rt)
 {
     if(l==r){
         t[rt].sum=b[l];
         t[rt].gcd=t[rt].mx=abs(b[l]);
         return;
     }
    int mid=(l+r)>>1;
    build(l,mid,ls);
    build(mid+1,r,rs);
    push_up(rt);
 }
 void update(int l,int r,int rt,int pos)
 {
     if(l==r){
         t[rt].sum=b[l];
         t[rt].gcd=t[rt].mx=abs(b[l]);
         return;
     }
    int mid=(l+r)>>1;
    if(pos<=mid) update(l,mid,ls,pos);
    else update(mid+1,r,rs,pos);
    push_up(rt);
 }
 int query_sum(int l,int r,int rt,int L,int R)
 {
     if(l>=L&&r<=R) return t[rt].sum;
     int mid=(l+r)>>1;
     int ans=0;
     if(L<=mid) ans+=query_sum(l,mid,ls,L,R);
    if(R>mid) ans+=query_sum(mid+1,r,rs,L,R);
    return ans;
 }
 int query_max(int l,int r,int rt,int L,int R){
     if(l>=L&&r<=R) return t[rt].mx;
     int mid=(l+r)>>1;
     int ans=0;
     if(L<=mid) ans=max(ans,query_max(l,mid,ls,L,R));
    if(R>mid) ans=max(ans,query_max(mid+1,r,rs,L,R));
    return ans;
 }
 int query_gcd(int l,int r,int rt,int L,int R){
     if(l>=L&&r<=R) return t[rt].gcd;
     int mid=(l+r)>>1;
     int ans=0;
     if(L<=mid) ans=__gcd(ans,query_gcd(l,mid,ls,L,R));
    if(R>mid) ans=__gcd(ans,query_gcd(mid+1,r,rs,L,R));
    return ans;
 }
 int main()
 {
     int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++) cin>>a[i];
    for(int i=1;i<=n;i++) b[i]=a[i]-a[i-1];
    build(1,n,1); 
    for(int i=1;i<=m;i++){
        int op,l,r,x;
        scanf("%d",&op);
        if(op==1){
            cin>>l>>r>>x;
            b[l]+=x;
            b[r+1]-=x;
            update(1,n,1,l);
            update(1,n,1,r+1);    
        }
        else if(op==2){
            cin>>l>>r;
            cout<<query_max(1,n,1,l+1,r)<<endl;
        }
        else if(op==3){
            cin>>l>>r;
            int t=query_sum(1,n,1,1,l);
            if(l==r) cout<<t<<endl;
            else cout<<__gcd(t,query_gcd(1,n,1,l+1,r))<<endl;
        }
    }
    return 0;
 }
View Code

D - Forsaken喜欢字符串

题意:

给$n$个长度为$k$字符串,然后给出询问,每次询问选定一个字符串,让你计算在所有其他字符串中相同子串个数再乘上长度

思路:

由于字符串长度最长只为$5$,那么子串的种类数也最多为$5$,所以我们可以建立$5$个$map$

然后我们直接遍历每个子串,利用$map$可以直接找到所有字符串中与自己相同的字符串个数,再减去本字符串中相同的子串个数乘上长度就为答案

#include<iostream>
#include<algorithm>
#include<map>
 using namespace std;
 const int maxn=5e4+10;
 typedef long long ll;
 map<string,int> m1,m2,m3,m4,m5;
 string s[maxn];
 int main()
 {
     int n,k,m,x,len;
     cin>>n>>k;
     for(int i=1;i<=n;i++)
         cin>>s[i];
     for(int i=1;i<=n;i++){
         for(int l=1;l<=k;l++){
             for(int j=0;j+l-1<k;j++){
                 string s1=s[i].substr(j,l);
                 if(l==1) m1[s1]++;
                 else if(l==2) m2[s1]++;
                 else if(l==3) m3[s1]++;
                 else if(l==4) m4[s1]++;
                 else if(l==5) m5[s1]++;
             }
         }
     }
     cin>>m;
     for(int i=1;i<=m;i++){
         cin>>x>>len;
         int ans=0;
         for(int j=0;j+len-1<k;j++){
             int num=0;
             string s1=s[x].substr(j,len);
             for(int l=0;l+len-1<k;l++){
                 string s2=s[x].substr(l,len);
                 if(s2==s1) num++;
             }
             if(len==1) ans+=len*(m1[s1]-num);
            else if(len==2) ans+=len*(m2[s1]-num); 
            else if(len==3) ans+=len*(m3[s1]-num); 
            else if(len==4) ans+=len*(m4[s1]-num); 
            else if(len==5) ans+=len*(m5[s1]-num); 
         }
        cout<<ans<<endl;
     }
     return 0;
  } 
View Code

E - Board

题意:

给一个全为$0$的矩阵,每次将矩阵的某一行或某一列全部加上$1$,进行若干次操作后,会得到一个新的矩阵,将矩阵中的某一个位置遮去,求该位置的值

思路:

直接遍历每一行与每一列,并且每个元素减去一行(或一列)的最小值(含有$-1$的行与列不进行此操作)

最后的答案就为$-1$所在行与列数字的和

#include<iostream>
#include<algorithm>
 using namespace std;
 const int maxn=1005;
 int a[maxn][maxn];
 int main()
 {
     int n,x,y;
     scanf("%d",&n);
     for(int i=1;i<=n;i++){
         for(int j=1;j<=n;j++){
             scanf("%d",&a[i][j]);
             if(a[i][j]==-1){
                 x=i;
                y=j;
             }
         }
     }
    for(int i=1;i<=n;i++){
        if(i==x) continue;
        int mi=100000;
        for(int j=1;j<=n;j++)
            mi=min(mi,a[i][j]);
        for(int j=1;j<=n;j++)
            a[i][j]-=mi;
    }
    for(int j=1;j<=n;j++){
        if(j==y) continue;
        int mi=100000;
        for(int i=1;i<=n;i++)
            mi=min(mi,a[i][j]);
        for(int i=1;i<=n;i++)
            a[i][j]-=mi;
    }
    /*cout<<endl;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++)
            cout<<a[i][j]<<" ";
        cout<<endl;
    }*/
    cout<<max(a[x-1][y],a[x+1][y])+max(a[x][y-1],a[x][y+1]);
    return 0; 
 }
 /*
3
2 3 1
1 -1 0
2 3 1
*/
View Code

 

posted @ 2020-05-27 17:09  overrate_wsj  阅读(235)  评论(0编辑  收藏  举报