重新振作第四天----NBUACMer-Beginnerround10.24題解

菜dog的日常生活發表於2024-11-12

題目連結:https://ac.nowcoder.com/acm/contest/94181

目錄
  • A:StringGame (思維)
  • B:SequenceGame (貪心+二分)
  • C:照看小貓 (幾何,思維)
  • D:小H的數列 (數學)
  • E:小H的糖果 (思維)
  • F:學姐的編碼1.0 (DP)
  • G:學姐的編碼2.0 (DFS)
  • H:迷陣 (BFS+雙指標)
  • I:Rating (思維+優先佇列)
  • J:字串修改 (暴力)
  • K:New Game! (計算幾何+最短路)

A:StringGame (思維)

題意介紹:給出一個長度為n的字串s以及操作次數x,我們有一個操作,可以將字串s的第一個字元放在s的後面,並將第一個刪掉。問經過x次操作後,問字串s最後是什麼樣子的。

資料範圍: 1<=n<=1E5 1<=x<=1E18

思路:可以看成一個環,如果當操作了n次時,就又回到了原樣。所以我們要求的是在模n意義下的操作次數x,即x=x%n。然後我們就先輸出後面的n-x個字元,再輸出前x個字元即可。

複雜度:O(n)

程式碼實現:

#include<bits/stdc++.h>
using namespace std;
int main(){
    long long n,x;
    string s;
    cin>>n>>x;
    cin>>s;
    x=x%n;
    if(x==0)
        cout<<s<<endl;
    else 
    cout<<s.substr(x,n-x)<<s.substr(0,x)<<endl;
}

B:SequenceGame (貪心+二分)

題意介紹:簡單來說,就是給你n組數,每組數有m個字,問當n組數中只能取一個數字或者不取時,所形成的最長上升子序列的長度是多少。

資料範圍:n<1E4 a[i]<=10

思路:類似於最長上升子序列的貪心+二分做法,唯一的不同點就是多了一個分組。對於最長上升子序列,我們的做法是,建立一個陣列f,下標表示長度,元素值代表在這個長度下末尾的最小元素值。既然如此,我們在此基礎上,建立一個臨時的陣列f1,具體的含義也是如上所示。但是,我們二分的時候,還是對於原來的f進行操作,對於f1進行更新,這樣就避免更新的操作影響到組內元素的操作,最後再用f1對於f進行相應的更新即可。例如,一組數 1,2,3。無論如何,最長上升子序列的長度也只能為1,但是,如果不建立臨時的陣列f1,而是直接對於f進行修改的話,那麼在操作後f1 = 1,f1 = 2,f1 = 3。而如果建立了臨時的f1陣列,就可以避免修改對於當前組內的後續操作造成影響,這樣操作的結果就是f1 = 1。其餘的步驟,均和最長上升子序列一致。

複雜度:O(nmlogn)

程式碼實現:

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n,m;
    cin>>m>>n;
    vector<vector<int>>a(n+1,vector<int>(m+1,0));
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
        }
    }
    int len=0;
    vector<int>f(n+1,90000000);
    vector<int>f1(n+1,90000000);
    for(int i=1;i<=n;i++){
        int templen=len;
        for(int j=1;j<=len;j++){
            f1[j]=f[j];
        }
        for(int j=1;j<=m;j++){
            int l=1,r=templen+1,mid;
            while(l<r){
                mid=(l+r)>>1;
                if(f[mid]>=a[i][j])r=mid;
                else l=mid+1;
            }
            f1[l]=min(f1[l],a[i][j]);
            len=max(len,l);
        }
        for(int j=1;j<=len;j++){
            f[j]=f1[j];
        }
    }
    cout<<len<<endl;
    
}

C:照看小貓 (幾何,思維)

題意介紹:給出n個點,這些點全部位於第一象限以及x,y的正半軸。我們可以在這些點當中任意連線,在滿足所連線段和x,y正半軸能夠組成封閉圖形的前提下,線段的長度和最小。

資料範圍: \(1 \le n \le 10^5 , 0 \le x_i,y_i \le 10^9\),同時不存在(0,0)

思路:首先,要是能圍住,那麼一定在x,y正半軸上面至少各有一個點,否則無法練成。那麼最小的長度,肯定是距離x軸最近的y軸上的點,以及距離y軸最近的x軸上的點,這兩點所形成的線段最短。因為三角形,兩邊之和大於第三邊。如果要結合其他點,那必然會形成三角形,就會大於這條線段。
(PS:噁心之處,不關閉同步流,會出現超時現象)

複雜度:O(n)

程式碼實現:

#include<bits/stdc++.h>
using namespace std;
#define ios ios::sync_with_stdio(0);cin.tie(0);cout.tie(0)
int main(){
    ios;
    int n;
    double x=1E18,y=1E18,u,v;
    int f1=0,f2=0;
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>u>>v;
        if(u==0){
            f1=1;
            y=min(y,v);
        }
        if(v==0){
            f2=1;
            x=min(u,x);
        }
    }
    if(f1&&f2){
        cout<<fixed<<setprecision(6)<<sqrtl(x*x+y*y)<<'\n';
    }
    else {
        cout<<"Poor Little H!"<<'\n';
    }
}

D:小H的數列 (數學)

題意介紹:一個數列滿足a1 = 1,4ai-1ai = (ai-1 +ai -1)2。給出數字n,求出第n項的值。

資料範圍:n<1E(10086)

思路: 把上面的公式化簡一下,就可以得到\(\sqrt[]a_i\)-\(\sqrt[]{a_{i-1}}=1\),我們不難看出,這個其實是完全平方數,因為只有完全平方數開平方之後的值相減才是1。我們直接輸出n的平方即可,但是考慮到資料範圍,我們不如直接用python,省得去找C++高精度的模板。

複雜度:O(1)

程式碼實現:

n=int(input())
print(n*n)

E:小H的糖果 (思維)

題意介紹:總共有T組測試,每組給出兩個整數k,n。我們一開始的數字是1,我們可以進行兩種操作,第一種是直接+1,第二種是*k。求從1變化到n的最小操作次數是多少。

資料範圍:n<1E4 a[i]<=10

思路:這題是思維題,當k為1的時候,我們採取第二種操作其實沒有任何意義,因此我們直接輸出n-1即可。而當k不等於1的時候,我們反向計算,如果n大於等於k的時候,我們直接先求要減去多少次,才可以使得當前的n是k的倍數,即ans=ans+n%k+1,然後我們再更新n,讓n整除k。由此進行,n最終的值只能小於k。這個時候答案其實很明顯,就是ans+k-1。那麼為什麼這個方法是可以的呢?首先,我們知道k的變化速度可能要大於+1,那麼肯定能使用k越多越好,但是這樣又會存在萬一多乘了該怎麼辦,我們不存在-1的操作。這個時候我們不如反向思考,既然我們從小可以得到大,我們也會再某個時候+1,那不如直接就反過來,如果能整除k,肯定優先整除,否則減去再整除,這樣我們既可以在保證不會多計算的同時次數最少。相當於一個數的k進位制一般。這個才是思考的關鍵部分。在前面部分的+1,其實就是在更高的進位制位上面+1,因為我們後面會存在*k操作。
(PS:這題還有個噁心點就是,不能使用endl,只能用“\n”,否則會超時)。

複雜度:O(Tlogn)

程式碼實現:

#include<bits/stdc++.h>
using namespace std;
long long t;
long long k,n,ans;
int main(){
    ios::sync_with_stdio(false);
    cin.tie(0),cout.tie(0);
    cin>>t;
   for(int i=1;i<=t;i++){
        cin>>k>>n;
        if(k==1){
            cout<<n-1<<'\n';
            continue;
        }
        ans=0;
        while(n>=k){
            ans+=n%k;
            ans++;
            n/=k;
        }
        cout<<ans+n-1<<'\n';
    }
}

F:學姐的編碼1.0 (DP)

題意介紹:給出一個長度為n的字串,字串由十六進位制編碼組成。求不重複的遞增子序列有多少個?

資料範圍: \(1 \le n \le 10^6\)

思路:首先,我們考慮當第i個字元是B時,我們考慮前i-1個字元,如果存在十六進位制下小於B的字元,那麼我們以B結尾的子序列,就是前面以十六進位制下小於B的字元結尾的子序列之和+1。這樣的情況下,如果後面又再次遇到B的時候,會出現重複的情況,但我們可以注意到的是,前一個B所形成的子序列,肯定是後一個B所形成子序列的子集,也就是說後面的包含了前面的。如果那麼我們不妨每次再次遇到相同字母的時候,進行一個初始化,然後再進行操作即可。

複雜度:O(16n)

程式碼實現:

#include<bits/stdc++.h>
using namespace std;
int main(){
    string s;
    cin>>s;
    vector<int>a(17,0);
    int n=s.size();
    s=' '+s;
    for(int i=1;i<=n;i++){
        if(s[i]>='A'&&s[i]<='F'){
            a[s[i]-'A'+11]=1;
            for(int j=0;j<s[i]-'A'+11;j++){
                a[s[i]-'A'+11]+=a[j];
            }            
        }
        else {
            a[s[i]-'0']=1;
            for(int j=0;j<s[i]-'0';j++){
                a[s[i]-'0']+=a[j];
            }
        }
    }
    long long ans=0;
    for(int i=0;i<17;i++){
         //cout<<a[i]<<endl;
        ans+=a[i];
    }
    cout<<ans<<endl;
}

G:學姐的編碼2.0 (DFS)

題意介紹:給出一個長度為n的字串,字串由十六進位制編碼組成。按字典序輸出所有不重複的遞增子序列。

資料範圍:\(1 \le n \le 10^6\)

思路:直接dfs暴力求解,但是直接找的話會超時,我們可以用二分法,判斷是否存在符合位置的下標。

複雜度:O(\(2^{16}logn\))

程式碼實現:

#include<bits/stdc++.h>
using namespace std;
int ans[17];
vector<int>ff[17];
void dfs(int len,int x,int p){
    for(int i=0;i<len;i++){
        if(ans[i]<10)
            cout<<ans[i];
        else {
            cout<<char(ans[i]-10+'A');
        }
        if(i==len-1){
            cout<<endl;
        }
    }
    for(int i=x+1;i<16;i++){
        if(ff[i].size()){
            int p1=upper_bound(ff[i].begin(), ff[i].end(), p)-ff[i].begin();
            if(p1!=ff[i].size()){
                ans[len]=i;
                dfs(len+1,i,ff[i][p1]);
            }
        }
    }
}
int main(){
    string s;
    cin>>s;
    int n=s.size();
    for(int i=0;i<n;i++){
        if(s[i]>='0'&&s[i]<='9'){
            ff[s[i]-'0'].push_back(i);
        }
        else {
            ff[s[i]-'A'+10].push_back(i);
        }
    }
    dfs(0,-1,-1);
}

H:迷陣 (BFS+雙指標)

題意介紹:有一個\(n \times n\) 的矩陣,每一個位置上的元素都有一個權值,每個位置都可以被經過人一次,要求從(1,1)到(n,n)所經過路徑的最大權值和最小權值之差是多少?

資料範圍:\(1 \le n \le 100 , w_{i,j} \le 3000\)

思路:一開始,我是想列舉最小值,然後二分最大值,接著bfs判斷能否到達,但是這樣會超時(3000* n* n* log(max\(a_i\)))(dfs不會。。。。。????)。所以參考別人的寫法,發現可以使用一種類似雙指標的方法去處理這道題。初始化l和r為0,先判斷能否到達,如果不能到達,則r++,直到能夠到達,當能夠到達時,則l++,直到不能到達。之所以這樣是可以的,是因為我們本質上是從小的l開始,找到第一個符合的r,然後再固定r,找到不符合的l,縮小了區間。然後再次迭代。因為最後肯定會存在一個l和r吧,這樣確保了l和r都會經歷到,相當於每次都找到了(l,r)符合的最小子段。

複雜度:O(6000nn)

程式碼實現:

#include<bits/stdc++.h>
using namespace std;
int vis[101][101];
int vis1[101][101];
int n,maxx=0;
int dx[4]={1,-1,0,0};
int dy[4]={0,0,-1,1};
bool bfs(int l,int r){
    memset(vis1,0,sizeof vis1);
    queue<pair<int,int>>q;
    q.push({1,1});
    vis1[1][1]=1;
    if(min(vis[1][1],vis[n][n])<l||max(vis[1][1],vis[n][n])>r){
        return false;
    }
    int f=0;
    while(q.size()){
        if(q.front().first==n&&q.front().second==n){
            return true;
        }
        for(int i=0;i<4;i++){
            int x=q.front().first+dx[i];
            int y=q.front().second+dy[i];
            if(x>=1&&x<=n&&y>=1&&y<=n&&vis[x][y]<=r&&vis[x][y]>=l&&!vis1[x][y]){
                vis1[x][y]=1;
                q.push({x,y});
            }
        }
        q.pop();
    }
    return false;
}
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            cin>>vis[i][j];
            maxx=max(maxx,vis[i][j]);
        }
    }
    int l=0,r=0;
    int res=maxx;
    while(l<=maxx&&r<=maxx){
        if(bfs(l,r)&&r>=l){
            //cout<<r<<' '<<l<<endl;
            res=min(r-l,res);
            l++;
        }
        else r++;
    }
    cout<<res<<endl;
}

I:Rating (思維+優先佇列)

題意介紹:首先,給出n,m,分別表n個cf號以及m場比賽表現分,每一次比賽,會選擇一個cf號的分數,與表現分相加併除以2。求最終的可能最高分時多少。

資料範圍:$n,m \le 10^5 $

思路:每一次都選擇最低分數,這樣萬一得分比最低分高,則總分增加最多,比最低低,則總分減少最少。這個,每次選擇最低分的過程,我們可以使用優先佇列。

複雜度:O(nlogn)

程式碼實現:

#include<bits/stdc++.h>
using namespace std;
int main(){
    priority_queue<double,vector<double>,greater<double>>q;
    int n,m;
    cin>>n>>m;
    double ans=0;
    for(int i=1;i<=n;i++){
        double x;
        cin>>x;
        q.push(x);
        ans+=x;
    }
    for(int i=1;i<=m;i++){
        double x,y;
        x=q.top();
        q.pop();
        cin>>y;
        q.push((x+y)/2);
        ans=ans-x+(x+y)/2;
        cout<<fixed<<setprecision(2)<<ans<<endl;
    }
}

J:字串修改 (暴力)

題意介紹:給出一個字串s,當下標為奇數時,\(s_i\)變為\(s_i + i\),否則,變為\(s_i\)變為\(s_i - i\)。字元加法被定義為a+1=b......z+1=a,減法同理。

資料範圍:\(1 \le n \le 10^5\)

思路:其實也是在模意義下,找出原先的數,即\(s_i - a\),然後在\(+-i\)之後,取模即可。

複雜度:O(n)

程式碼實現:

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n;
    string s;
    cin>>n;
    cin>>s;
    s=' '+s;
    for(int i=1;i<=n;i++){
        if(i&1){
            cout<<(char)((s[i]-'a'+i)%26+'a');
        }
        else {
            cout<<(char)(((s[i]-'a'-i)%26+26)%26+'a');
        }
    }
}

K:New Game! (計算幾何+最短路)

題意介紹:給出兩條平行直線和n個圓。在直線上和圓形中移動不消耗體力,否則消耗的體力為歐氏距離。求從直線l1到直線l2的最小花費體力。

資料範圍:\(1 \le n \le 1000\) 座標值的絕對值小於等於10000

思路:直接暴力計算出,將直線和圓形看成一個個地點,然後暴力計算出兩兩之間的距離,然後暴力進行Dijkstra一遍即可。

複雜度:O(n^2)

程式碼實現:

#include<bits/stdc++.h>
using namespace std;
int main(){
    int n,a,b,c1,c2;
    cin>>n>>a>>b>>c1>>c2;
    vector<double>dis(n+2,1E9+7),x(n+2),y(n+2),r(n+2);
    vector<vector<double>>edge(n+2,vector<double>(n+2,0.0));
    edge[0][n+1]=abs(c1-c2)*1.0/sqrt(a*a+b*b);
    edge[n+1][0]=abs(c1-c2)*1.0/sqrt(a*a+b*b);
    for(int i=1;i<=n;i++){
        cin>>x[i]>>y[i]>>r[i];
        edge[0][i]=max(0.0,1.0*abs(a*x[i]+b*y[i]+c1)/sqrt(a*a+b*b)-r[i]);
        edge[i][0]=max(0.0,1.0*abs(a*x[i]+b*y[i]+c1)/sqrt(a*a+b*b)-r[i]);
        edge[i][n+1]=max(0.0,1.0*abs(a*x[i]+b*y[i]+c2)/sqrt(a*a+b*b)-r[i]);
        edge[n+1][i]=max(0.0,1.0*abs(a*x[i]+b*y[i]+c2)/sqrt(a*a+b*b)-r[i]);
    }
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            edge[i][j]=max(0.0,1.0*sqrt((x[i]-x[j])*(x[i]-x[j])+(y[i]-y[j])*(y[i]-y[j]))-r[i]-r[j]);
        }
    }
    dis[0]=0;
    vector<bool>vis(n+2,0);
    for(int i=0;i<=n+1;i++){
        double maxd=1E9+10;
        int p=-1;
        for(int j=0;j<=n+1;j++){
            if(!vis[j]&&maxd>dis[j]){
                maxd=dis[j];
                p=j;
            }
        }
        //cout<<p<<' '<<dis[p]<<endl;
        vis[p]=true;
        for(int j=0;j<=n+1;j++){
            dis[j]=min(dis[p]+edge[p][j],dis[j]);
        }
    }
    cout<<dis[n+1]<<endl;
    
}

相關文章