【中北大学2013年第一学期新生程序设计大赛 解题报告】

Problem A小学问题(难)
Problem B一元三次方程求根(简单)
Problem CVigenère密码(简单)
Problem D昂贵的奖励(难)
Problem E质数环(中等)
Problem FLucky Word(简单)
Problem G转圈游戏(中等)
Problem H数的统计(简单)
Problem I佳佳的课程设计(简单)
Problem J

关键词墙(简单 )

以上是题目难度分布(个人观点),本次简单题居多,但很多人都不做简单题,一头啃在难题上了。【附上个人代码 并非标程】大家有更好的想法可留言一起探讨!
【A 题】 动态归划 或记忆化搜索

根据题意,我们可以想到每两个数间不是乘号就是加号,所以我们只需要考虑乘号数即可。
以前i个数间插j个乘号为状态,f[i,j]记录前i个数间插入j个乘号的最大值。
得状态转移方程:f[i,j]=max{f[k,j-1]*sum[k+1,i]|j>=1,sum[1,i]|j=0}
其中sum[i,j]表示第i个数到第j个数的和。
也许这种方程少考虑一种情况,即a*b+c,这种转移,即f[k,j]+sum[k+1,j];
但由于a,b,c都是整数,所以a*b+c<a*(b+c),所以最优情况一定是
(a1+a2+..+an)*( b1+b2+...+bn )*...这种情况,所以方程可以少考虑一种转移
——————————————————————————————————————
记忆化搜索
对[i,j]区间加num个乘号,最优是max{[i,k]加t个*[k+1,j]加num-t-1个}
表示记忆化搜索比较好想,可以很轻松的做此类区间动态规划,但得特殊考虑下乘号数等于n-1的情况。

#include <stdio.h>
#include <string.h>
const int M = 20;
int sum[M],f[M][M][M];
int max (int a,int b)
{
    return a > b ? a : b;
}

int DP(int l,int r,int n)
{
    if (f[l][r][n] > -1)
        return f[l][r][n] ;
    if (l == r || !n)
        return f[l][r][n] = sum[r] - sum[l-1];

    for (int t = 0;t < n;t ++)
        for (int k = l;k < r;k ++)
            f[l][r][n] = max (f[l][r][n],DP(l,k,t)*DP(k+1,r,n-t-1));
    return f[l][r][n];
}

int main ()
{
    int n,num,k,i,ans;
    while (~scanf ("%d%d",&n,&k))
    {
        sum[0] = 0;
        ans = 1;
        memset (f,-1,sizeof(f));
        for (i = 1;i <= n;i ++)
        {
            scanf ("%d",&num);
            sum[i] = sum[i-1] + num;
            ans *= num;
        }
        if (k == n-1)
            printf ("%d\n",ans);
        else
            printf ("%d\n",DP(1,n,k));
    }
    return 0;
}

【B题】 二分

这题有两种方法 一是二分,二是直接模拟。

注意到题目给的提示 【记方程f(x)=0,若存在2个数x1和x2,且x12,f(x1)*f(x2)<0,则在(x1,x2)之间一定有一个根。】

解法一:

可用二分查找根所在的范围,过程如下。

1.  取当前可能存在解的区间(a,b);

2.若a+0.001>b或f((a+b)/2)=0,则可确定根为a+b/2并退出过程;

3.若f(a)*f((a+b)/2)<0,则可知根在区间(a,a+b/2)中,故对区间(a,a+b/2)重复该过程;

4.若f(a)*f((a+b)/2)>0,则必有f(b)*f((a+b)/2)<0也就是说根在(a+b/2,b)中,应该对此区间重复该过程。

最后,就可以得到精确到0.001的根。

解法二:根据题意 从-100到100 每次加0.001 如果满足条件就为解。

【此题就不附代码了】

【C题】  字符串处理 找规律

根据题意与图 可以找到以下规律M[i] = (C[i]-k[j]+Mod)%Mod ,大小写要跟密文一致,所以提前做一下处理及可。

#include <stdio.h>
#include <string.h>

const int M = 1005;
const int N = 105;
const int Mod = 26;
int main ()
{
    int T;
    char key[N],C[M];
    scanf ("%d",&T);
    while (T --)
    {
        scanf ("%s %s",key,C);
        int len = strlen(key);
        char flag;
        for (int i = 0,j = 0; C[i] != '\0'; i ++,j ++)
        {
            j %= len;
            if (key[j]>='a'&&key[j]<='z') key[j] -= ('a'-'A');
            if (C[i]>='A'&&C[i]<='Z')
                flag = 'A';
            else{
                flag = 'a';
                C[i] -= ('a' - 'A');
            }
            printf ("%c",(C[i]-key[j]+Mod)%Mod+flag);
        }
        printf ("\n");
    }
    return 0;
}

【D题】贪心+高精度

这题略难 贪心应该很容易能看得出来,注意一下数据量 10000的1000次方,64位是存不下来的,得用高精度;高精度写起来比较恶心。

贪心:假设任意两个相邻的人手上的数分别为 a1,b1;a2,b2;

则 a1/b2 < a2/b1;这样的排序肯定能使最大值最小 ,转化一下 即a1*b1 < a2*b2,

所以以a*b 从小到大排序就行了

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
#define MAXN 1001
using namespace std;
struct Person
{
    int a, b;
} p[MAXN];
struct High
{
    int len;
    int num[4001];
};
int n;
bool operator < (Person n1, Person n2)
{
    return n1.a * n1.b < n2.a * n2.b;
}
bool operator > (High n1, High n2)
{
    if (n1.len > n2.len)
        return true;
    if (n1.len < n2.len)
        return false;
    for (int i = n1.len - 1; i > -1; --i)
    {
        if (n1.num[i] > n2.num[i])
            return true;
        if (n1.num[i] < n2.num[i])
            return false;
    }
    return true;
}
High operator * (High n1, int number)
{
    int k = 0;
    n1.len += 5;
    for (int i = 0; i < n1.len; ++i)
        n1.num[i] *= number;
    for (int i = 0; i < n1.len; ++i)
    {
        n1.num[i] += k;
        k = n1.num[i]/10;
        n1.num[i] %= 10;
    }
    while (n1.num[n1.len - 1] == 0)
        --n1.len;
    return n1;
}
High operator / (High n1, int number)
{
    High ans;
    memset(ans.num, 0, sizeof ans.num);
    ans.len = 0;
    int now = 0;
    for (int i = n1.len - 1; i > -1; --i)
    {
        now = now * 10 + n1.num[i];
        if (now < number && ans.len == 0)
            continue;
        ans.num[ans.len++] = now / number;
        now %= number;
    }
    for (int i = 0; i < ans.len / 2; ++i)
        swap (ans.num[i], ans.num[ans.len - i - 1]);
    return ans;
}
void Give(High *n1, int number)
{
    memset (n1 -> num, 0, sizeof n1->num);
    n1 -> len = 0;
    do
    {
        n1 -> num[(n1 -> len)++] = number % 10;
        number /= 10;
    }
    while (number != 0);
}
void Print(High n1)
{
    for (int i = n1.len - 1; i > -1; --i)
        printf("%d", n1.num[i]);
    printf("\n");
}
void Init()
{
    scanf("%d", &n);
    for (int i = 0; i <= n; ++i)
        scanf("%d %d", &p[i].a, &p[i].b);
    for (int i = 1, j; i < n; ++i)
        for (j = i + 1; j <= n; ++j)
            if (!(p[i] < p[j]))
                swap (p[i], p[j]);
}
int main()
{
    #ifdef LOCAL
        freopen("data.in","r",stdin);
    #endif
    Init();
    High s, maxx;
    Give(&s,p[0].a);
    Give(&maxx,0);
    for (int i = 1; i <= n; ++i)
    {
        if (s / p[i].b > maxx)
            maxx = s / p[i].b;
        s = s * p[i].a;
    }
    Print(maxx);
    return 0;
}

【E题】 搜索

这题学过 搜索的人 应该一眼就能看出来。从1~n 每个数尝试一下,如果能够成质数环 则输出,因为你是从小到大搜 所以不会出现重复的情况。注意判断无解的情况。

#include <stdio.h>
#include <string.h>
int ans[20],prime[50],vis[20];

void GetPrime()
{
    for (int i = 0; i <= 50; i ++)
        prime[i] = i;
    prime[1] = 0;
    for (int i = 2; i <= 50; i ++)
        for (int j = 2; j <= 50; j ++)
            if (i*j <= 50) prime[i*j] = 0;
}
bool flag;
void dfs (int dep,int n)
{
    if (dep > n)
    {
        if (!prime[ans[1]+ans[n]])
            return ;
        flag = true;
        for (int i = 1; i < n; i ++)
            printf ("%d ",ans[i]);
        printf ("%d\n",ans[n]);

        return ;

    }
    for (int i = 2; i <= n; i ++)
    {
        if (!vis[i]&&prime[ans[dep-1]+i])
        {
            ans[dep] = i;
            vis[i] = 1;
            dfs (dep+1,n);
            vis[i] = 0;
        }
    }
}
int main ()
{
#ifdef LOCAL
    freopen("data.in","r",stdin);
    freopen("data.out","w",stdout);
#endif
    int n,i;
    GetPrime();
    while (~scanf ("%d",&n))
    {
        ans[1] = 1;
        flag = false;
        memset (vis,0,sizeof(vis));
        vis[1] = 1;
        dfs(2,n);
        if (!flag) printf ("No Answer\n");
    }
    return 0;
}

【F题】 简单题

这题没有卡时间 ,但不知道比赛的时候怎么那么多人TLE

建一个num[] 数组记录每个字母出现的次数,然后找出最大的和最小的相减 判断一下是不是素数就OK了

这里写一两种求素数的快速算法吧


1、 prime[i] 非0 表示是素数

void GetPrime(int n)
{
    for (int i = 0;i < n;i ++)
        prime[i] = i;
    prime[1] = 0;
    for (int i = 2;i < n/2;i ++)
        for (int j = i;j < n/2;j ++)
            if (i * j > n) break;
            else prime[i*j] = 0;
}

2、 prime[i]  == 0 为素数 跟第一种差不多

void GetPrime(int n)
{
    memset (prime,0,sizeof(prime));
    for (int i = 2; i <= n; i ++)
        for (int j = i*2; j <= n; j += i)
            prime[j] = 1;
}

3、prime[] 保存所有的素数

void GetPrime(int n)
{
    int m = sqrt(n+0.5);
    int c = 0;
    memset (vis,0,sizeof(vis));
    for (int i = 2; i <= m; i ++)
        if (!vis[i])
        {
            prime[c++] = i;
            for (int j = i*i; j <= n; j += i)
                vis[j]  = 1;
        }
}

【G题】 快速幂

注意一下k的范围 10^9 如果用普通的方法求 10^k%Mod 肯定会超时。

X号人实际转的次数为 tmp=10^k mod n,然后ans=(tmp*m+x) mod n 就这么简单。注意得用long long

#include <stdio.h>
#define LL long long

LL Power(LL a,LL b,LL c)
{
    LL ans = 1;
   a %= c;
    while (b > 0)
    {
        if(b%2) ans = (ans*a)%c;
        b /= 2;
        a = (a*a)%c;
    }
    return ans;
}
int main ()
{
    #ifdef LOCAL
        freopen("circle.in","r",stdin);
    #endif
    LL n,m,k,x;
    while (~scanf ("%lld%lld%lld%lld",&n,&m,&k,&x))
    {
       LL tmp = Power(10,k,n);
       LL ans = (x+m*tmp)%n;
       printf ("%lld\n",ans);
    }
    return 0;
}

【H题】递推

这题应很容易就想到用递归 求解,但很可惜 数太大了,会TLE。

仔细想想会发现可以用数组把每一个数能组成的个数保存下来,求解时 只接加上就OK了,不用再往下计算。

#include <stdio.h>
#include <string.h>

int a[1005];
int main()
{
    #ifdef LOCAL
        freopen("data.in","r",stdin);
        freopen("data.out","w",stdout);
    #endif
    a[1]=1,a[2]=2;
    for(int i=3;i<=1000;i++)
    {
        a[i]=1;
        for(int j=1;j<=i/2;j++)
          a[i]+=a[j];
    }
    int T,n;
    scanf ("%d",&T);
    while (T --){
        scanf ("%d",&n);
        printf ("%d\n",a[n]);
    }
    return 0;
}

【I题】模拟题

这题是模拟队列,建一个循队列 每个进程进一次队列时间片减1,当为0时不再进队列并记录它所用的时间。最后除以n;

#include <stdio.h>
#include <string.h>
const int N = 105;
int que[N];
int BFS(int front,int rear,int n)
{
    int ret = 0,tt = 0;
    while (front != rear)
    {
        int u = que[front ++];
        front %= (n+1);
        tt ++,u --;
        if (u == 0)
            ret += tt;
        else {
            que[rear++] = u;
            rear %= (n+1);
        }
    }
    return ret;
}
int main ()
{
    #ifdef LOCAL
        freopen("data1.in","r",stdin);
    #endif
    int n,a,cnt = 0;
    while (~scanf ("%d",&n))
    {
        int rear = 0,front = 0;
        for (int i = 0;i < n;i ++){
            scanf ("%d",&a);
            que[rear++] = a;
        }
        printf ("Case #%d: %.3f\n",++cnt,BFS(front,rear,n)*1.0/n);
    }
    return 0;
}


【J题】排序

赤裸裸的结构体排序,以它们的频度排序输出就OK,不管你用冒泡还是快排。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 105;
struct Data
{
    char str[25];
    int ff;
}d[N];
bool cmp(const Data  &a,const Data &b)
{
    if (a.ff > b.ff) return true;
    if (a.ff == b.ff&&strcmp(a.str,b.str) < 0)
        return true;
    return false;

}
int main ()
{
    int n = 0;
    while (~scanf ("%s %d",d[n].str,&d[n].ff))
        n ++;
    sort (d,d+n,cmp);
    for (int i = 0;i < n;i ++)
        printf ("%s\n",d[i].str);
    return 0;
}


【以上只是个人观点,未能保证100%正确,有错的欢迎指出,有更好的解法的,可留言,一起探讨学习】




  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值