2020牛客寒假算法基礎集訓營1 部分題解

A B C D E F G H I J
\(\checkmark\) \(\checkmark\) \(O\) \(\checkmark\) \(\checkmark\) \(O\) \(\checkmark\) \(O\) \(O\) \(\times\)

\(\checkmark\):表明比賽時經過。node

\(O\):表明賽後補題經過。c++

\(\times\):表明目前還未經過。shell

A. honoka和格點三角形

題目連接數組

題目大意

在一個\(n\)\(m\)列的矩陣點陣中求出知足一下要求的三角形的個數:spa

  • 三角形的三個頂點均爲格點(橫縱座標均爲整數)。
  • 三角形的面積爲\(1\)
  • 三角形至少有一條邊和\(x\)軸或者\(y\)軸平行。

解題思路

三角形的面積爲\(1\),而且橫縱座標均爲整數,那麼分爲兩種狀況(平行\(x\)軸或者\(y\)軸的爲底邊):code

  • 底邊爲\(1\),高爲\(2\)
  • 底邊爲\(2\),高爲\(1\)

再根據底邊平行的軸不一樣,分爲四種狀況便可。排序

AC代碼

#include<bits/stdc++.h>
const int mod = 1e9+7;
const int maxn=1e5+10;
typedef long long ll;
using namespace std;
int main()
{
    ll y,x;
    cin>>y>>x;
    ll res;
    if(x>=3) 
        res = ((2LL*(y-1)*(x-2))%mod)*(x-2+y)%mod;
    if(y>=3)
        res+= ((2*(x-1)*(y-2))%mod)*(x+y-2)%mod;
    res%=mod;
    cout<<res<<endl;
}

總結

規律總結題。ci

B. kotori和bangdream

題目連接字符串

題目大意

每一個音符有\(x\%\)的機率得\(a\)分,有\((100-x)\%\)的機率得\(b\)分,求\(n\)個字符的得分指望。get

解題思路

求出\(n\)個字符分爲所有爲\(a\)分和\(b\)的得分,乘以對應的機率即爲答案。

AC代碼

#include<bits/stdc++.h>
const int maxn=1e5+10;
typedef long long ll;
using namespace std;
int main()
{
    double n,x,a,b;
    cin>>n>>x>>a>>b;
    a*=n;
    b*=n;
    a*=x;
    a/=100;
    b*=(100-x);
    b/=100;
    printf("%.2f",a+b);
    return 0;
}

總結

簽到題,但因爲本身的粗心,沒有注意浮點數的使用。在比勝過程中遇到除運算的時候,應當當心一點。

C. umi和弓道

題目連接

題目大意

在二維座標中有一個起始點\((x_0,y_0)\)與其餘\(n\)個點相連構成\(n\)條射線,在\(x\)軸或者\(y\)軸上放一個擋板,切斷兩點之間的鏈接,現求擋板的最小長度以使沒有被切斷的連線的數量不超過\(k\)

解題思路

\(n\)個點中與起始點在同一象限的點是不可能被擋板給擋住的,那麼分別統計與起始點不在同一象限的點與起始點的連線在\(x\)軸和\(y\)軸的交點。題目求沒有被擋板擋住的連線不超過\(k\),換言之就是要擋住至少\(n-k\)個點。而後找出連續\(n-k\)個點構成區間的最小值。

AC代碼

#include<bits/stdc++.h>
const int maxn=1e5+10;
typedef long long ll;
using namespace std;
vector<double>v1,v2;
int main()
{
    double x0,y0;
    cin>>x0>>y0;
    int n,k;
    cin>>n>>k;
    k=n-k;
    for(int i=0;i<n;i++){
        double x1,y1;
        cin>>x1>>y1;
        double a=(y0-y1),b =(x1-x0);
        if(x1*x0<0){
            double jiao=a/b*x0+y0;
            v2.push_back(jiao);
        }
        if(y1*y0<0){
            double jiao=x0+b/a*y0;
            v1.push_back(jiao);
        }
    }
    sort(v1.begin(),v1.end());
    sort(v2.begin(),v2.end());
    double res=1e18;
    if(v1.size()>=k){
        int st=0,ed=st+k-1;
        while(ed<v1.size()){
            res=min(res,v1[ed]-v1[st]);
            st++,ed++;
        }
    }
    if(v2.size()>=k){
        int st=0,ed=st+k-1;
        while(ed<v2.size()){
            res=min(res,v2[ed]-v2[st]);
            st++,ed++;
        }
    }
    if(res==1e18)cout<<"-1"<<endl;
    else printf("%.7lf",res);
    return 0;
}

總結

D. hanayo和米飯

題目連接

題目大意

\(1,2,3 \cdots,n\)個數字,如今從中任意拿走一個數字,根據剩下的\(n-1\)個數字,判斷拿走的數字是多少。

解題思路

  • 方案1:直接排序

    將輸入的\(n-1\)個數字存在一個數組當中,排一個序,遍歷數組,若是數字的下標和數字不相等,即爲答案。

  • 方案二:求和

    在輸入的過程當中求出\(n-1\)個數字的和,再根據高斯公式求出\(n\)個數字的和,兩個相減即爲答案。

AC代碼

#include<bits/stdc++.h>
const int maxn=1e5+10;
typedef long long ll;
using namespace std;
int a[maxn];
int main()
{
    int n;
    cin>>n;
    for(int i=0;i<n-1;i++){
        cin>>a[i];
    }
    sort(a,a+n-1);
    for(int i=0;i<n;i++){
        if(a[i]!=i+1){
            cout<<i+1<<endl;
            break;
        }
    }
    return 0;
}

總結

簽到題。

E. rin和快速迭代

題目連接

題目大意

給你一個式子\(f(x)\)表示\(x\)的正整數因子的個數,不斷迭代\(f(x)\)的結果,求最終結果爲\(2\)時的迭代次數。

解題思路

直接模擬題意迭代便可,剛開始拿到這道題的時候,覺得有什麼規律,因此一開始的方向就錯了。

時間複雜度爲\(O(\sqrt{n})\)

AC代碼

#include<bits/stdc++.h>
const int maxn=1e5+10;
typedef long long ll;
using namespace std;
int solve(ll x){
    int cnt=0;
    for(ll i=2;i*i<=x;i++){
        if(x%i==0){
            cnt+=2;
            if(i*i==x)cnt--;
        }
    }
    return cnt;
}
int main()
{
    ll n;
    cin>>n;
    int res=0;
    ll mid=solve(n)+2;
    while(mid!=2){
        res++;
        mid=solve(mid)+2;
        // cout<<mid<<endl;
    }
    res++;
    cout<<res<<endl;
    return 0;
}

總結

在作題的過程當中必定要重視計算時間複雜度,最開始覺得直接模擬會炸,因此沒有去寫,往找規律的方向去思考去了,耽誤了時間。同時要注意數據的範圍,這道題就犯了這個錯誤。

F. maki和tree

題目連接

題目大意

在一個有\(n\)個點的樹上,每一個點被標記爲白色或者黑色,問有多少條只包含一個黑點的簡單路徑。

解題思路

簡單路徑只有兩種狀況下會包含一個黑點:

  • 起始點和終點都爲白點。
  • 兩個斷點其中一個是黑點。

這道題我的認爲切入點爲黑點,由於路徑中只有一個黑點,那麼這個黑點要麼爲起始或者重點,要麼將多個白點鏈接起來做爲中間點。這樣一來就要找到黑點鏈接的白點所在的聯通塊總共有多少個白點,由於是在樹上,因此每一個聯通塊是各自獨立的,不存在重複計算的狀況。計算聯通塊中節點的個數能夠用並查集,並查集中在鏈接兩個節點的時候,統計其所在父親的孩子數量,這樣其餘節點所在聯通塊的節點數量爲其父親節點的孩子數量加\(1\)

假設某一個黑點,鏈接了\(k\)個節點,其中\(f(i)\)表示第\(i\)個節點所在聯通塊的節點個數:

  • 第一種狀況的計算結果爲\(\sum_{i=1}^k{\sum_{j=i+1}^k}f(i)*f(j)\)
  • 第二種狀況的計算結果爲\(\sum_{i=1}^kf(i)\)

AC代碼

#include<bits/stdc++.h>
const int maxn=1e5+10;
typedef long long ll;
using namespace std;
int n;
string color;
vector<int>G[maxn];
int pre[maxn];
void init(){
    for(int i=0;i<maxn;i++){
        pre[i]=i;
    }
}
int childnum[maxn];
int nodenum[maxn];
int find(int x)
{
    int r=x;
    while(r!=pre[r]){
        r=pre[r];
    }
    int i=x,j;
    while(i!=pre[i]){
        j=pre[i];
        pre[i]=r;
        i=j;
    }
    return r;
}
void uni(int x,int y){
    int fx=find(x),fy=find(y);
    if(fx!=fy){
        pre[fx]=fy;
        childnum[fy]+=childnum[fx]+1;
    }
}
ll sum[maxn];
ll solve(vector<int>temp){
    ll res=0;
    for(int i=0;i<temp.size();i++){
        res+=temp[i];
    }
    //求前綴和
    for(int i=0;i<temp.size();i++){
        sum[i+1]=sum[i]+temp[i];
    }
    for(int i=1;i<temp.size();i++){
        res+=temp[i]*sum[i];
    }
    return res;
}
int main()
{
    // freopen("data.txt","r",stdin);
    cin>>n;
    cin>>color;
    //對並查集數組進行初始化
    init();
    for(int i=0;i<n-1;i++){
        int x,y;
        cin>>x>>y;
        G[x].push_back(y);
        G[y].push_back(x);
        if(color[x-1]=='W'&&color[y-1]=='W'){
            uni(x,y);
        }
    }
    ll res=0;
    for(int i=1;i<=n;i++){
        nodenum[i]=childnum[find(i)]+1;
    }
    for(int i=1;i<=n;i++){
        if(color[i-1]=='B'){
            vector<int>temp;
            for(int j=0;j<G[i].size();j++){
                 if(color[G[i][j]-1]=='W')
                    temp.push_back(nodenum[G[i][j]]);
            }
            res+=solve(temp);
        }
    }
    cout<<res<<endl;
    return 0;
}

總結

本覺得本身對並查集的掌握比較牢靠,但這道題仍是沒有作的出來,唉!這道題用並查集來作的思惟仍是比較獨特。但願本身多多積累經驗。

G. eli和字符串

題目連接

題目大意

給你一個只包含小寫字母的字符串,求知足有\(k\)個相同字母的子串的最小長度。

解題思路

利用二維數組,分別統計\(26\)種字母的位置。若是每種字母的個數都小於\(k\),那麼就不存在這樣的字符串,輸\(-1\)
對於每一種字母的狀況,遍歷每一行,\(i\)下標對應的值表示第\(1\)個字母出現的位置,\(i+k-1\)下標對應的值表示第\(k\)個字母出現的位置,維護區間最小值便可。

AC代碼

#include<bits/stdc++.h>
const int maxn=1e5+10;
typedef long long ll;
using namespace std;
vector<int>a[30];
int main()
{
    int n,k;
    cin>>n>>k;
    string str;
    cin>>str;
    for(int i=0;i<str.size();i++){
        a[str[i]-'a'].push_back(i);
    }
    int maxlen=0;
    for(int i=0;i<26;i++){
        int len = a[i].size();
        maxlen=max(len,maxlen);
    }
    if(maxlen<k){
        cout<<"-1"<<endl;
        return 0;
    }
    int res=2e5+10;
    // cout<<a[1][0]<<" "<<a[1][1]<<endl;
    for(int i=0;i<26;i++){
        if(a[i].size()<k)continue;
        for(int j=0;j<a[i].size()&&j+k<=a[i].size();j++){
            res = min(res,a[i][j+k-1]-a[i][j]+1);
        }
        // cout<<res<<endl;
    }
    cout<<res<<endl;
    return 0;
}

總結

簡單的統計,沒有什麼難度。

H. nozomi和字符串

題目連接

題目大意

給你一個長度爲\(n\)只包含\(01\)字符的字符串,擁有\(k\)次操做將字符\(0\)變爲\(1\)或者將\(1\)改變爲\(0\),問通過最多\(k\)次操做(\(k\)次機會能夠不用完)以後字符相同的子串的最長長度。

解題思路

整體思路是貪心。先考慮\(k\)大於等於\(1\)的個數或者大於\(0\)的個數的狀況,那麼結果就是字符串的長度;另外一種狀況則統計每個\(1\)的前綴\(1\)和後綴\(1\)的位置,而後遍歷一遍,以\(start\)爲起點,\(end\)爲終點,貪心\(k\)個1的位置,將這\(k\)\(1\)都改變爲\(0\),那麼字符串區間爲爲下標\(end+1\)的值減去下標\(start-1\)的值再加\(1\)

AC代碼

/*
    H題補題
*/
#include<bits/stdc++.h>
using namespace std;
int main()
{
    int n,k;
    cin>>n>>k;
    string str;
    cin>>str;
    vector<int>v[2];
    v[0].push_back(-1);
    v[1].push_back(-1);
    for(int i=0;i<str.size();i++){
        if(str[i]=='0')
            v[0].push_back(i);
        else 
            v[1].push_back(i);
    }
    int res=0;
    if(v[0].size()-1<=k||v[1].size()-1<=k)res=n;
    for(int i=0;i<2;i++){
        for(int j=1;j<v[i].size()&&j+k<=v[i].size();j++){
            res=max(res,v[i][j+k]-v[i][j-1]-1);
        }
    }
    cout<<res<<endl;
    return 0;
}

總結

最開始在作這道題的時候想的太過於複雜,想到用動態規劃去作,沒有往貪心上靠。

I. nico和niconiconi

題目連接

題目大意

給你一個長度爲\(n\)的字符串,其中"nico" 計\(a\)分,"niconi" 計\(b\)分,"niconiconi" 計\(c\)分,求出字符串最多能得多少分。(已經計算過的字符不能重複進行計算)

解題思路

利用動態規劃的思想,\(dp[i]\)表示前\(i\)個字符的最大值,轉移方程爲:
\[ \begin{align} &if(i>=3\&\&substr(i-3,4)=nico)dp[i]=max(dp[i],dp[i-3]+a)\\ &if(i>=5\&\&substr(i-5,6)=niconi)dp[i]=max(dp[i],dp[i-5]+b)\\ &if(i>=9\&\&substr(i-9,10)=niconiconi)dp[i]=max(dp[i],dp[i-9]+c)& \end{align} \]

AC代碼

#include<bits/stdc++.h>
const int maxn=3e5+10;
typedef long long ll;
using namespace std;
ll dp[maxn];
int main()
{
    int n,a,b,c;
    cin>>n>>a>>b>>c;
    string str;
    cin>>str;
    int len=str.size();
    dp[0]=0;
    for(int i=1;i<len;i++){
        dp[i]=dp[i-1];
        if(i>=3&&str.substr(i-3,4)=="nico")
            dp[i]=max(dp[i],dp[i-3]+a);
        if(i>=5&&str.substr(i-5,6)=="niconi")
            dp[i]=max(dp[i],dp[i-5]+b);
        if(i>=9&&str.substr(i-9,10)=="niconiconi")
            dp[i]=max(dp[i],dp[i-9]+c);
    }
    cout<<dp[len-1]<<endl;
    return 0;
}

總結

動態規劃仍是本身的弱點啊,根本沒有想到這方面,其實理解以後仍是蠻簡單的,但就是當時看到這道題過題人數不是不少,給本身形成了心理壓力,先入爲主。

相關文章
相關標籤/搜索