NOIP2007题解

来源:互联网 发布:华为手机记录运动数据 编辑:程序博客网 时间:2024/06/05 11:33

统计数字:
题目大意:给你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;}
0 0