NOIP2007題解

統計數字:
題目大意:給你n個數,求每個數出現的次數。
題解:排個序,記錄每個數出現的個數,如果碰到不同的數了輸出並清零即可。時間複雜度:O(n log n),空間複雜度:O(n)。

#include<cstdio>
#include<algorithm>
using namespace std;
int n,i,a[200010],x,cnt;
int main(){
    scanf("%d",&n);
    for(i=1;i<=n;i++)scanf("%d",&a[i]);
    sort(a+1,a+1+n);
    printf("%d",a[1]),cnt=1;
    for(i=2;i<=n;i++)
        if(a[i]==a[i-1])cnt++;
        else printf(" %d\n%d",cnt,a[i]),cnt=1;
    printf(" %d",cnt);
    return 0;
}

字符串的展開:
題目大意:
給你一個字符串,只有小寫字母,數字和’-’,給定三個參數,當出現’-’時,s1,s2,s3按以下規則展開:
1.在輸入的字符串中,出現了減號’-’,減號兩側
同爲小寫字母或同爲數字,且按照ASCII 碼的順序,減號右邊的字符嚴格大於左邊的字符。
2.參數p1:展開方式。p1=1 時,對於字母子串,填充小寫字母;p1=2 時,對於字母子串,
填充大寫字母。這兩種情況下數字子串的填充方式相同。p1=3 時,不論是字母子串還是數字子串,都用與要填充的字母個數相同的星號’*’來填充。
3.參數p2:填充字符的重複個數。p2=k 表示同一個字符要連續填充k 個。例如,當p2=3
時,子串“d-h”應擴展爲”deeefffgggh”。減號兩側的字符不變。
4.參數p3:是否改爲逆序:p3=1 表示維持原有順序,p3=2 表示採用逆序輸出,注意這時
仍然不包括減號兩端的字符。例如當p1=1、p2=2、p3=2 時,子串”d-h”應擴展爲”dggffeeh”。
5.如果減號右邊的字符恰好是左邊字符的後繼,只刪除中間的減號,例如:”d-e”應輸出
爲”de”,”3-4”應輸出爲”34”。如果減號右邊的字符按照ASCII碼的順序小於或等於左邊字符,輸出時,要保留中間的減號,例如:”d-d”應輸出爲”d-d”,”3-1”應輸出爲”3-1”。len<=100.
題解:
直接模擬,注意判斷幾種特殊情況:’-’兩邊有一邊爲’-’,’-’在首尾的位置。時間複雜度:O(len*p2),空間複雜度:O(len)。

#include<cstdio>
#include<cstring>
int i,n,p1,p2,p3,j,k;
char s[110];
void putsz(){
    if(p1==3)
        for(j=s[i-1]+1;j<s[i+1];j++)
            for(k=1;k<=p2;k++)putchar('*');
    else
     if(p3==1)
        for(j=s[i-1]+1;j<s[i+1];j++)
            for(k=1;k<=p2;k++)putchar(j);
    else
        for(j=s[i+1]-1;j>s[i-1];j--)
            for(k=1;k<=p2;k++)putchar(j);
}
void putch(){
    if(p1==3)
        for(j=s[i-1]+1;j<s[i+1];j++)
            for(k=1;k<=p2;k++)putchar('*');
    else
     if(p3==1){
        if(p1==1)
          for(j=s[i-1]+1;j<s[i+1];j++)
                for(k=1;k<=p2;k++)putchar(j);
         else
          for(j=s[i-1]+1;j<s[i+1];j++)
                for(k=1;k<=p2;k++)putchar(j-'a'+'A');
     }
     else{
        if(p1==1)
          for(j=s[i+1]-1;j>s[i-1];j--)
                for(k=1;k<=p2;k++)putchar(j);
         else
          for(j=s[i+1]-1;j>s[i-1];j--)
                for(k=1;k<=p2;k++)putchar(j-'a'+'A');
     }
}
int main(){
    scanf("%d%d%d",&p1,&p2,&p3);
    scanf("%s",s+1);
    n=strlen(s+1);
    for(i=1;i<=n;i++)
        if(s[i]!='-')putchar(s[i]);
        else
         if(s[i+1]<=s[i-1])putchar('-');
         else
          if((s[i-1]>='a'&&s[i-1]<='z'&&(s[i+1]<'a'||s[i+1]>'z'))||((s[i+1]<'0'||s[i+1]>'9')&&s[i-1]>='0'&&s[i-1]<='9')||i==1||i==n||s[i-1]=='-'||s[i+1]=='-')putchar('-');
          else{
            if(s[i-1]>='a'&&s[i-1]<='z')putch();
            else putsz();
          }
    return 0;
}

矩陣取數遊戲:
題目大意:給定一個n*m的矩陣,取m次,每次在每一行取一個數,只能在每一行的首尾位置取,第i次的得分爲取的n個數的總和*2^i。求m次取數後的最大得分值。n,m<=80,aij<=1000.
題解:
unsigned long long的最大值爲2^64-1,所以顯然這題不管什麼算法都得套一個高精度……
由秦九韶公式可知:我們從內往外取數的話就每次對於當前ans*2即可。而從內往外取數的話有一個好處:每個數都有可能爲最後一個取的,然後接下來由這個數往左右擴展一個就得到了最後兩次取的數,一直擴展到左右兩邊即可。
這樣子就變成了一個經典的區間DP了。以長度爲第一重循環,對於長度爲len-1的最優取法我們都已經得到了,而對於一段要取的序列l,r,要麼就是先取l,r-1,然後最後取r,或者先取l+1,r,最後取l,兩者取個max即可。
設f[i][j]代表從l開始長度爲j的數列的最大得分值,則:
f[i][j]=max(2*(f[i][j-1]+a[j]),2*(f[i+1][j-1]+a[i]))。時間複雜度:O(n*m*m*G),空間複雜度:O(m*m*G)。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,m,i,j,k,ans[210],f[90][90][110],a[90],na[2][110];
void plus(int a[],int b[]){
    a[0]=max(a[0],b[0]);
    for(int i=1;i<=a[0];i++){
        a[i]+=b[i];
        a[i+1]+=a[i]/10;
        a[i]%=10;
    }
    if(a[a[0]+1])a[0]++;
    while(a[a[0]]>=10){
        a[a[0]+1]+=a[a[0]]/10;
        a[a[0]]%=10;
        a[0]++;
    }
}
bool cmp(int a[],int b[]){
    if(a[0]==b[0]){
        for(int i=a[0];i;i--)
            if(a[i]==b[i])continue;
            else return a[i]<b[i];
    }
    return a[0]<b[0];
}
void mul(int a[]){
    for(int i=1;i<=a[0];i++)a[i]*=2;
    for(int i=1;i<=a[0];i++)a[i+1]+=a[i]/10,a[i]%=10;
    if(a[a[0]+1])a[0]++;
    while(a[a[0]]>=10){
        a[a[0]+1]+=a[a[0]]/10;
        a[a[0]]%=10;
        a[0]++;
    }
}
void pplus(int a[],int b){
    int i=1;
    a[i]+=b;
    while(a[i]>=10){
        a[i+1]+=a[i]/10;
        a[i]%=10;
        i++;
    }
    a[0]=max(a[0],i);
}
int main(){
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++){
        memset(f,0,sizeof(f));
        for(j=1;j<=m;j++)scanf("%d",&a[j]),f[j][1][0]=1,f[j][1][1]=2*a[j];
        for(j=2;j<=m;j++){
            for(k=1;k<=m-j+1;k++){
                memset(na,0,sizeof(na));
                plus(na[0],f[k][j-1]);
                pplus(na[0],a[k+j-1]);
                mul(na[0]);
                plus(na[1],f[k+1][j-1]);
                pplus(na[1],a[k]);
                mul(na[1]);
                if(cmp(na[0],na[1]))memcpy(f[k][j],na[1],sizeof(na[1]));
                else memcpy(f[k][j],na[0],sizeof(na[0]));
            }
        }
        plus(ans,f[1][m]);
    }
    for(i=ans[0];i;i--)printf("%d",ans[i]);
    return 0;
}

樹網的核:
題目大意:給定一棵樹,一段路徑記爲F,D(i,F),代表i到F上最近的點的距離,一段路徑的偏心距爲max(D(i,F))(i<=n)。求樹直徑上的一段長度<=x的路徑的偏心距的最小值。n<=300.
題解:
首先dfs一遍求出相鄰點兩兩之間的距離,再用floyd求出所有點對間距離,並求出所有直徑。我們需要知道這兩個定理:1.一條路徑上的偏心距必定是由某條直徑的端點到這條路徑上的距離。2.核一定是所有直徑的交集的一部分。因此我們可以任取一條直徑,對於該直徑上的一段路徑,判斷是否<=s,然後求出該直徑的端點與該路徑的距離,更新答案即可。時間複雜度:O(n^3),空間複雜度:O(n^2)。然而這題數據太水了,怎麼做都能過。

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,s,i,j,k,f[310][310],x,y,z,ans=2147483647,que[310],w,maxn,maxi,maxj;
int main(){
    scanf("%d%d",&n,&s);
    memset(f,1,sizeof(f));
    for(i=1;i<n;i++){
        scanf("%d%d%d",&x,&y,&z);
        f[i][i]=0;
        f[x][y]=f[y][x]=z;
    }
    f[n][n]=0;
    for(k=1;k<=n;k++)
        for(i=1;i<=n;i++)
            for(j=1;j<=n;j++)
                if(f[i][j]>f[i][k]+f[k][j]&&i!=j)f[i][j]=f[i][k]+f[k][j];
    for(i=1;i<=n;i++)
        for(j=i;j<=n;j++)
            if(f[i][j]>maxn){
                maxn=max(maxn,f[i][j]);
                maxi=i,maxj=j;
            }
    for(i=1;i<=n;i++)
        if(f[maxi][i]+f[i][maxj]==f[maxi][maxj])que[++w]=i;
    for(i=1;i<=w;i++)
        for(j=i;j<=w;j++)
            if(f[que[i]][que[j]]<=s)ans=min(max(min(f[maxi][que[i]],f[maxi][que[j]]),min(f[maxj][que[i]],f[maxj][que[j]])),ans);
    printf("%d",ans);
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章