2020牛客寒假算法基礎集訓營2全部題解

寫在前面:- -因爲D題的傻逼原因,導致完全不想做HIJ,後來發現H題和自己一眼秒的dp方程一樣,I題和J題稍微想想也能夠到。還是自己的鍋,再也不想碰計算幾何了(高度自閉)。

B-排數字

題意:給出一個長度爲n的字符串,問重新排列後最多能組成多少個616。
題解:只要計數6和1的個數就行了,甚至拿了個全場一血。

#include "stdio.h"
#include "string.h"
#include "algorithm"
#include "iostream"
#include "math.h"
#include "vector"
#include "map"
using namespace std;
const int maxn=2e5+10;
const double INF=4e9+10;
typedef long long ll;
int n,m,ans,sum;
char s[maxn];
int main(){
    int T;
    scanf("%d",&n);
    scanf("%s",&s);
    for(int i=0;i<n;i++)
        if(s[i]=='6')ans++;
        else if(s[i]=='1')sum++;
    if(ans>sum)printf("%d\n",sum);
    else printf("%d\n",ans-1);
    return 0;
}

A-做遊戲

題意:兩個人玩石頭剪刀布,分別給出他們出石頭、剪刀、布的個數,問第一個人最多能贏多少次。
題解:不需要考慮順序,第一個人的石頭儘可能贏第二個人的剪刀,剪刀儘可能贏布,布儘可能贏石頭就好。注意範圍要開long long。

#include "stdio.h"
#include "string.h"
#include "algorithm"
#include "iostream"
#include "math.h"
#include "vector"
#include "map"
using namespace std;
const int maxn=2e5+10;
const double INF=4e9+10;
typedef long long ll;
ll a,b,c;
ll x,y,z;
int main(){
    scanf("%lld%lld%lld",&a,&b,&c);
    scanf("%lld%lld%lld",&x,&y,&z);
    ll ans=min(a,y)+min(b,z)+min(c,x);
    printf("%lld\n",ans);
    return 0;
}

C-算概率

題意:有n道題,每道題各有pipi的概率答對。問分別答對0,1,2n0,1,2…n道題的概率爲多少。
題解:給出的是膜意義下的概率,實際上無需考慮轉換。可以發現一個顯然的dp方程。dp[i][j]dp[i][j]表示到第i題時答對j題的概率是dp[i][j]dp[i][j],狀態轉移方程即爲dp[i][j]=dp[i1][j1]p[i]+dp[i1][j](1p[i])dp[i][j]=dp[i-1][j-1]*p[i]+dp[i-1][j]*(1-p[i])

#include "stdio.h"
#include "string.h"
#include "algorithm"
#include "iostream"
#include "math.h"
#include "vector"
#include "map"
using namespace std;
typedef long long ll;
const int maxn=2e3+10;
const ll mod=1e9+7;
const double eps=1e-8;
int n,p[maxn];
ll dp[maxn][maxn];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%d",&p[i]);
    dp[0][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=i;j++)
            if(j==0)dp[i][j]=dp[i-1][j]*(1-p[i]+mod)%mod;
            else dp[i][j]=(dp[i-1][j-1]*p[i]%mod+dp[i-1][j]*(1-p[i]+mod)%mod)%mod;
    for(int i=0;i<n;i++)printf("%lld ",dp[n][i]);
    printf("%lld\n",dp[n][n]);
    return 0;
}

E-做計數

題意:簡單分析一下題意可以發現就是問小於等於n的所有完全平方數因子和。
題解:按照題意暴力即可。數據規模再大點可以考慮一下記憶化啥的。

#include "stdio.h"
#include "string.h"
#include "algorithm"
#include "iostream"
#include "math.h"
#include "vector"
#include "map"
using namespace std;
const int maxn=2e3+10;
const double INF=4e9+10;
typedef long long ll;
ll n,ans;
int main(){
    scanf("%lld",&n);
    for(int i=1;i*i<=n;i++){
        for(int j=1;j*j<=i*i;j++)
            if(i*i%j==0){
                if(j*j==i*i)ans++;
                else ans+=2;
            }
    }
    printf("%lld\n",ans);
}

G-判正誤

題意:問ad+be+cf=ga^d+b^e+c^f=g是否正確。
題解:不知道爲什麼取模會是正確的做法。但是隨便寫了一個快速冪取模就過了。

#include "stdio.h"
#include "string.h"
#include "algorithm"
#include "iostream"
#include "math.h"
#include "vector"
#include "map"
using namespace std;
typedef long long ll;
const int maxn=2e3+10;
const ll mod=1e9+7;
const double eps=1e-8;
ll a,b,c,d,e,f,g;
int flag;
ll quick(ll a,ll b){
    ll res=1;
    while(b){
        if(b&1)res=res*a%mod;
        a=a*a%mod;
        b>>=1;
    }
    return res;
}
int main(){
    int T;
    scanf("%d",&T);
    while(T--){
        flag=0;
        scanf("%lld%lld%lld%lld%lld%lld%lld",&a,&b,&c,&d,&e,&f,&g);
        a=quick(a,d);
        b=quick(b,e);
        c=quick(c,f);
        if(a+b+c==g)puts("Yes");
        else puts("No");
    }
    return 0;
}

F-拿物品

題意:有n堆物品,每個有兩個屬性a和b。兩個人分別拿,其中第一個人的價值爲a屬性之和,第二個人的價值爲b屬性之和,要求兩個人價值差要儘可能的大。
題解:很顯然的,按照每個物品的價值差排序。依次取即可。

#include "stdio.h"
#include "string.h"
#include "algorithm"
#include "iostream"
#include "math.h"
#include "vector"
#include "map"
using namespace std;
typedef long long ll;
const int maxn=2e5+10;
const ll mod=1e9+7;
const double eps=1e-8;
int n,ans1[maxn],ans2[maxn];
struct arr{
    int a,b,no;
}a[maxn];
bool cmp(arr a,arr b){
    return a.a-b.b>b.a-a.b;
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        int x;
        scanf("%d",&x);
        a[i].a=x;a[i].no=i;
    }
    for(int i=1;i<=n;i++){
        int x;
        scanf("%d",&x);
        a[i].b=x;
    }
    sort(a+1,a+1+n,cmp);
    int cnt=0,num=0,sum=0;
    while(sum<n){
        sum++;
        if(sum%2)ans1[cnt++]=a[sum].no;
        else ans2[num++]=a[sum].no;
    }
    for(int i=0;i<cnt-1;i++)printf("%d ",ans1[i]);printf("%d\n",ans1[cnt-1]);
    for(int i=0;i<num-1;i++)printf("%d ",ans2[i]);printf("%d\n",ans2[num-1]);
    return 0;
}

D-數三角

題意:給出n個不重合的點,問最多能組成多少個鈍角三角形。
題解:你跟我說n3n^3能過?現場現學極角排序的應用,結果發現完全沒必要?暴力三個點,check能否組成三角形即可。想學極角排序的可以點擊

#include "stdio.h"
#include "string.h"
#include "algorithm"
#include "iostream"
#include "math.h"
#include "vector"
#include "map"
using namespace std;
const int maxn=2e3+10;
const double INF=4e9+10;
typedef long long ll;
int n,ans;
struct arr{
    int x,y;
}f[maxn];
double a[2020][2020];
inline int read(){
    int f=1,x=0;char ch;
    do{ch=getchar();if(ch=='-')f=-1;}while(ch<'0'||ch>'9');
    do{x=x*10+ch-'0';ch=getchar();}while(ch>='0'&&ch<='9');
    return f*x;
}
inline bool check(int i,int j,int k){
    if((((f[j].x-f[i].x)*(f[k].x-f[i].x)+(f[j].y-f[i].y)*(f[k].y-f[i].y))<0) &&
    (((f[j].x-f[i].x)*(f[k].y-f[i].y)-(f[j].y-f[i].y)*(f[k].x-f[i].x))!=0))return true;
    return false;
}
int main(){
    n=read();
    for(int i=1;i<=n;i++){
        int x,y;
        x=read();y=read();
        f[i].x=x,f[i].y=y;
    }
    for(int i=1;i<n-1;i++)
        for(int j=i+1;j<n;j++)
            for(int k=j+1;k<=n;k++)
                if(check(i,j,k) || check(k,i,j) || check( j,i,k))ans++;
    printf("%d\n",ans);
    return 0;
}

上面的題目就是比賽的時候出的了,後面一個多小時溜出去玩英雄聯盟了,不然估計還能出1~2題。
下面是補了的題目,難度不分先後。

J-求函數

題意:給出n個形如fi=kix+bif_i=k_ix+b_i的函數,並且給出m個操作。第一種操作,將fifi函數的kbk,b改變。第二種,詢問fr(fr1((fl(1))))f_r(f_{r-1}(…(f_l(1))…))
題解:一眼可以看出來是個線段樹的題目,那麼線段樹上面維護什麼呢。首先對於k和b肯定要分別建樹。我們觀察可以發現對於答案的k,他是累乘形式的就相當於i=lrki=n1n2\prod_{i=l}^{r}ki=n1*n2。對於答案的b他是符合形如bikj=m1n2+m2\sum b_i \prod kj=m1*n2+m2。然後正常建樹單點修改區間查詢即可。

#include "stdio.h"
#include "string.h"
#include "algorithm"
#include "iostream"
#include "math.h"
#include "vector"
#include "map"
using namespace std;
const int maxn=2e5+10;
const double INF=4e9+10;
const int mod=1e9+7;
typedef long long ll;
int n,m,k[maxn],b[maxn];
struct arr{
    ll k,b;
}s[maxn*4];
void pushup(int node){
    s[node].k=s[node<<1].k*s[node<<1|1].k%mod;
    s[node].b=(s[node<<1|1].k*s[node<<1].b%mod+s[node<<1|1].b)%mod;
}
void build(int node,int l,int r){
    int mid=(l+r)>>1;
    if(l==r){
        s[node].k=1ll*k[l];
        s[node].b=1ll*b[l];
        return ;
    }
    build(node<<1,l,mid);
    build(node<<1|1,mid+1,r);
    pushup(node);
}
void modify(int L,int k,int b,int l,int r,int node){
    if(l==r){
        s[node].k=1ll*k;
        s[node].b=1ll*b;
        return;
    }
    int mid=(l+r)>>1;
    if(L<=mid)modify(L, k, b, l, mid, node<<1);
    else modify(L, k, b, mid+1, r, node<<1|1);
    pushup(node);
}
arr push(arr a,arr b){
    arr newo;
    newo.k=(a.k*b.k)%mod;
    newo.b=(b.k*a.b+b.b)%mod;
    return newo;
}
arr query(int L,int R,int l,int r,int node){
    if(L<=l && r<=R)return s[node];
    int mid=(l+r)>>1;
    arr sum={1,0};
    if(L<=mid)sum=push(sum,query(L, R, l, mid, node<<1));
    if(R>mid)sum=push(sum,query(L, R, mid+1, r, node<<1|1));
    return sum;
}
int main(){
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++)scanf("%d",&k[i]);
    for(int i=1;i<=n;i++)scanf("%d",&b[i]);
    build(1,1,n);
    while(m--){
        int opt;
        scanf("%d",&opt);
        if(opt==1){
            int x,y,z;
            scanf("%d%d%d",&x,&y,&z);
            modify(x,y,z,1,n,1);
        }else{
            int l,r;
            scanf("%d%d",&l,&r);
            arr ans=query(l,r,1,n,1);
            printf("%lld\n",(ans.k+ans.b)%mod);
        }
    }
    return 0;
}

H-施魔法

題意:有n個元素,每個有一個價值ai。你每次至少選擇k個,代價爲你選擇的k個裏的最大值減最小值。問恰好全部選擇所有元素的總代價。
題解:表面dp。將ai從小到大排序,最佳代價很顯然fi=minj=1ik+1fi1+aiajf_i=min_{j=1}^{i-k+1}{f_{i-1}+ai-aj},fi1ajf_{i-1}-a_j可以用一個前綴和維護,而ai是個遞增的固定代價,每一次都累加即可。

#include "stdio.h"
#include "string.h"
#include "algorithm"
#include "iostream"
#include "math.h"
#include "vector"
#include "map"
using namespace std;
typedef long long ll;
const int maxn=3e5+10;
const ll INF=1e18;
const int mod=1e9+7;
int n,k;
int a[maxn];
ll dp[maxn],f[maxn];
int main(){
    scanf("%d%d",&n,&k);
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    f[0]=INF;
    for(int i=1;i<=n;i++){
        f[i]=min(dp[i-1]-1ll*a[i],f[i-1]);
        if(i-k+1>=1)dp[i]=f[i-k+1]+1ll*a[i];
        else dp[i]=INF;
    }
    printf("%lld\n",dp[n]);
    return 0;
}

I-建通道

題意:n個點,每個點有一個權值ai。連接兩個點的代價爲lowbit(aiaj)lowbit(ai⊕aj)。求連接這n個點的最小花費。
題解:首先去重,假設去重後有n個數。數值大小一樣的點花費肯定爲0。之後找出一個最小的二進制位k,滿足存在viv_i的這個二進制位是0且存在vjv_j的這個二進制位1,答案就是2k(n1)2^k*(n-1)。(這位是0的點與vjv_j連邊,是1的點與viv_i連邊)。

#include "stdio.h"
#include "string.h"
#include "algorithm"
#include "iostream"
#include "math.h"
#include "vector"
#include "map"
using namespace std;
typedef long long ll;
const int maxn=3e5+10;
const ll INF=1e18;
const int mod=1e9+7;
int n,a[maxn];
int main(){
    scanf("%d",&n);
    for(int i=0;i<n;i++)scanf("%d",&a[i]);
    sort(a,a+n);
    ll ans=0;
    n=unique(a, a+n)-a;
    for(int i=0;i<=30;i++){
        int cnt=0;
        for(int j=0;j<n;j++)if((a[j]>>i)&1)cnt++;
        //a[j]>>i &1就是看二進制位第i位是否爲1
        if(cnt>0 && cnt<n){
            ans=(1ll<<i)*(n-1);
            break;
        }
    }
    printf("%lld\n",ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章