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\n",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\n");
    else printf("%.8f\n",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\n",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\n",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\n",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\n");
    else printf("%d\n",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\n",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\n",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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM