2016.12.05 蓝桥杯校赛题解

(搬一下ak小锋的题解)

ProblemA 神秘的三位数(代码填空)

问题描述 有这样⼀一个3位数,组成它的3个数字阶乘之和正好等于它本身。即:abc =a! +b! +c! 下⾯面的程序⽤用于搜索这样的3位数。请补全缺失的代码。

解题思路 模拟,将所有三位数的数字阶乘算出来和原数字⽐比较即可找出,题中阶乘数组已给,因此空 格部分代码为sum+=jc[x%10];

Problem B 用一个正整数产⽣生一个回文数

问题描述 若⼀一个数(⾸首位不不为0)从左到右读与从右到左读都是⼀一样,这个数就叫做回⽂文数,例例如12521 就是⼀一个回⽂文数。 给定⼀一个正整数,把它的每⼀一个位上的数字倒过来排列列组成⼀一个新数,然后与原数相加,如 果是回⽂文数则停⽌止,如果不不是,则重复这个操作,直到和为回⽂文数为⽌止。给定的数本身不不为 回⽂文数。 例例如,给定⼀一个正整数87则有: STEP1: 87+78=165 STEP2: 165+561=726 STEP3: 726+627=1353 STEP4: 1353+3531=4884 编写⼀一个程序,输⼊入M(12<=M<=1000) ,输出最少经过⼏几步可以得到回⽂文数。如果在8步以 内(含8步)不不可能得到回⽂文数,则输出0。

解题思路 暴力模拟,记录步数

AC 代码

include<bits/stdc++.h> 
using namespace std; 
int f(int a) 
{ 
    int b=0; 
    while(a) 
    { 
        b=b*10+a%10; 
        a/=10; 
    } 
    return b; 
} 

int main()
{
    intT; 
    scanf("%d",&T); 
    while(T--) 
    { 
        int a; 
        scanf("%d",&a); 
        int ans=0; 
        while(ans<=8) 
        { 
            ans++; 
            a+=f(a); 
            if(a==f(a)) break; 
        } 
        if(ans>8) ans=0; 
        printf("%d\n",ans); 
    }
    return 0;
}

Problem C 自守数

问题描述 自守数是指⼀一个数的平⽅方的尾数等于该数⾃自身的⾃自然数。例例如: 25^2=625 76^2=5776 9376^2=87909376 (其中的^表示次方,如:25^2 表示25的平方) 。 给你一批2147483647以内 的正整数,判断它们是不不是自守数,是输出Y,不不是输出N。例如: 输入:67276625,则必须输出:YNNY

解题思路 模拟,爆int 就用long long 嘛,a平方算出来,位数记录一下,对a平方取个模和原数比较即可。

AC 代码

#include<bits/stdc++.h> 
using namespace std; 
int main() 
{ 
    int a; 
    while(scanf("%d",&a)!=EOF) 
    { 
        if(!a) 
        { 
            printf("Y"); 
            continue;
        } 
        long long b=a; 
        b=b*b; 
        long long c=1,d=a; 
        while(a) 
        { 
            c*=10; 
            a/=10; 
        } 
        if(b%c==d) printf("Y"); 
        else printf("N");
    } 
    return 0;
}

Problem D 高兴天数

问题描述 小X性格很独特,如果她今天高兴度比上次⼀一样或更高,她就会很善良,相反,如果她今天高兴度比上次低,她就会很凶!现在已经知道小X在N天⾥里里每天的高兴度M。根据这N天 中她每天高兴度M,合理安排与她相处时间,使大家与小X友好相处尽量多天数。现在要 求计算出最多能和小X友好相处多少天。

解题思路 LIS,⽤用一个数组记录每个长度的最小尾数,遍历数据数组,二分出这个数作为尾数的最长子序列长度,并更新尾数数组与答案,时间复杂度O(nlogn)

AC 代码

#include<bits/stdc++.h> 
using namespace std; 
int data[30005]={0},d[30005]; 
int main() 
{ 
    int n; 
    scanf("%d",&n); 
    for(int i=1;i<=n;i++)
        scanf("%d",&data[i]); 
    for(int i=1;i<=n;i++) 
        d[i]=35000; 
    int ans=0; 
    for(int i=1;i<=n;i++) 
    { 
        int p=upper_bound(d+1,d+n+1,data[i])-d;
        d[p]=data[i]; 
        ans=max(ans,p); 
    }
    printf("%d\n",ans); 
    return 0;
}

Problem E 作弊的发牌者

问题描述 lyt 正在与他的N-1(2<=N<=100)个朋友打牌。他们玩的牌一副为K(N<=K<= 100,000,K 为N的倍数)张。所有牌中,一共有M(M =K/N)张“好牌”,其余的K-M张为“差牌”。lyt是游戏的发牌者,很自然地,他想把所有好牌都留给自己。他热衷于获胜,即使为此必须采取一 些不正当的手段。 在若干局游戏后,lyt的朋友们开始怀疑lyt在游戏中作弊,于是他们想了个对策:使用新的发牌规则。规则具体如下: 1. lyt把牌堆的最上面一张发给他右边的单身狗; 2. 每当lyt 发完一张牌,他都得将牌堆顶部接下来的P(1<=P<=10)张牌放到底部去(一般把这个操作称为切牌) ; 3. 然后,lyt 对逆时针方向的下一头单身狗重复上述的操作; lyt 绝望地认为,他再也不不可能获胜了,于是他找到了你,希望你告诉他,将好牌放在初始牌堆的哪些位置,能够确保它们在发完牌后全集中到他手里。顺带说明一下,我们把牌堆顶的牌定义为1号牌,从上往下第二张定义为2号牌,依此类推。

解题思路 数据结构模拟,然后把自己被发到的牌丢进vector 排个序输出就好了了,时间复杂度O(kp)。 我刚开始想的双向链表,赛后才想到队列,比双向链表好写多了,太蠢了。那就给双向链表 代码随便看看好了。

AC 代码

#include<bits/stdc++.h> 
using namespace std; 
vector<int>V; 
struct list 
{ 
    int a,nex,pre; 
}data[100005]; 
int main() 
{ 
    int n,k,p; 
    scanf("%d%d%d",&n,&k,&p); 
    for(int i=1;i<=k;i++) 
    { 
        data[i].pre=i-1; 
        data[i].a=i; 
        data[i].nex=i+1; 
    }
    data[k].nex=1; 
    data[1].pre=k; 
    int t=k/n; 
    int no=1; 
    for(int i=0;i<t;i++) 
    { 
        for(int j=0;j<n-1;j++) 
        { 
            int pre=data[no].pre; 
            int nex=data[no].nex; 
            data[pre].nex=nex; 
            data[nex].pre=pre; 
            no=data[no].nex; 
            for(int l=0;l<p;l++) no=data[no].nex; 
        }
        V .push_back(no); 
        int pre=data[no].pre; 
        int nex=data[no].nex; 
        data[pre].nex=nex; 
        data[nex].pre=pre; 
        no=data[no].nex; 
        for(int l=0;l<p;l++) no=data[no].nex; 
    } 
    sort(V .begin(),V .end()); 
    for(int i=0;i<V .size();i++) printf("%d\n",V[i]); 
    return 0;
}

Problem F 朋友

问题描述 有一个城镇,住着n个市⺠民。已知一些⼈人互相为朋友。引用一个名人的话说,朋友的朋友也是朋友。意思是说如果A和B是朋友,C和B是朋友,则A和C是朋友.你的任务是数出最大朋友组的人数。

解题思路 并查集,维护所有节点对应的集合,并且在根节点记录集合大小,每次合并更新并查集状态并更更新答案即可。

AC 代码

include<bits/stdc++.h> 
using namespace std; 
int fa[30005],cnt[30005]; 
int findf(int a)
{
    if(a==fa[a]) return a; 
    else return fa[a]=findf(fa[a]);
} 

int main() 
{ 
    int n,m; 
    scanf("%d%d",&n,&m); 
    for(int i=1;i<=n;i++) 
    {
        fa[i]=i; 
        cnt[i]=1; 
    } 
    int ans=0; 
    while(m--) 
    { 
        int a,b; 
        scanf("%d%d",&a,&b); 
        int aa=findf(a),bb=findf(b); 
        if(aa!=bb) 
        { 
            fa[bb]=aa; 
            cnt[aa]+=cnt[bb]; 
            ans=max(ans,cnt[aa]);
         }
     } 
     printf("%d\n",ans); 
     return 0; 
}

Problem G 连接格点

问题描述 有一个M行N列的点阵,相邻两点可以相连。一条纵向的连线花费一个单位,一条横向的连线花费两个单位。某些点之间已经有连线了,试问至少还需要花费多少个单位才能使所有的点全部连通。

解题思路 因为边最多1000×999×2条,点最多1000×1000个,可以直接用kruskal 做最小生成树。

AC 代码

#include<bits/stdc++.h> 
using namespace std; 
int fa[2000005];
int findf(int a) 
{ 
    if(a==fa[a]) return a; 
    else return fa[a]=findf(fa[a]); 
} 

int main() 
{ 
    int n,m; 
    scanf("%d%d",&n,&m); 
    for(int i=1;i<=2000005;i++) fa[i]=i; 
    int x1,y1,x2,y2,ans=0; 
    while(scanf("%d%d%d%d",&x1,&y1,&x2,&y2)!=EOF) 
    { 
        int a=x1*1005+y1; 
        int b=x2*1005+y2; 
        int aa=findf(a),bb=findf(b); 
        if(aa!=bb) fa[aa]=bb; 
    }
    for(int i=1;i<n;i++)
        for(int j=1;j<=m;j++) 
        { 
            int a=i*1005+j; 
            int b=a+1005; 
            int aa=findf(a),bb=findf(b); 
            if(aa!=bb) fa[aa]=bb,ans++; 
        } 
    for(int i=1;i<=n;i++)
        for(int j=1;j<m;j++) 
        { 
            int a=i*1005+j; 
            int b=a+1; 
            int aa=findf(a),bb=findf(b);
            if(aa!=bb) fa[aa]=bb,ans+=2; 
        } 
    printf("%d\n",ans); 
    return 0; 
}

Problem H 木棍

问题描述 在一个原始部落,有一些人要去打猎了了,每个人都要挑选自己的工具——两根木棍。一 个用作远距离投掷攻击,一个用作近距离搏斗。但是每个人都想挑到最好的,但这是不可能的。但是为了让多数⼈人满意,也为了减少大家的矛盾。部落领袖设计了一个矛盾指数,这个 指数就是每个人的不舒服指数和,不舒服指数就(L1-L2)^2(^代表次⽅方) ,其中L1,L2 分别是一个人的两根木棍的长度。
部落领袖决定让矛盾指数最少,于是他来向你寻求帮助,希望你能告诉他矛盾指数至少有多少。

解题思路 先对木棍长度排序,可以证明最优情况一定是所有人选择了相邻长度的木棍。接下来使用动态规划算法求解,dp[i][j]表示第i个⼈人取到第j根木棍的最小答案,状态转移方程为dp[i][j] 等于dp[i-1][0 到j-2]的最小值加上data[j]-data[j-1]的平方,做到i=n 时更新答案。

AC 代码

#include<bits/stdc++.h> 
using namespace std; 
int data[2005]; 
int dp[505][2005]; 
int main() 
{ 
    int m,n,ans=0x3f3f3f3f; 
    scanf("%d%d",&m,&n); 
    for(int i=1;i<=m;i++) scanf("%d",&data[i]);
    sort(data+1,data+1+m); 
    memset(dp,0x3f3f3f3f,sizeof(dp)); 
    dp[0][0]=0; 
    for(int i=1;i<=n;i++) 
    { 
        int mi=dp[i-1][i*2-2]; 
        for(int j=i*2;j<=m;j++) 
        { 
            mi=min(mi,dp[i-1][j-2]); 
            dp[i][j]=mi+(data[j]-data[j-1])*(data[j]-data[j-1]); 
            if(i==n) ans=min(ans,dp[i][j]); 
        }
    } 
    printf("%d\n",ans); 
    return 0; 
}

Problem I 采药

问题描述 xgs 是个天资聪颖的孩子,他的梦想是成为世界上最伟大的医师。为此,他想拜附近最 有威望的医师为师。医师为了判断他的资质,给他出了一个难题。医师把他带到一个到处都是草药的山洞里对他说:“孩子,这个山洞里有一些不同的草药,采每一株都需要一些时间, 每一株也有它⾃自身的价值。我会给你一段时间,在这段时间里,你可以采到一些草药。如果你是一个聪明的孩子,你应该可以让采到的草药的总价值最大。 ”如果你是xgs,你能完成这个任务吗?

解题思路 裸01背包,但是N,M太大做不了,我们观察到所有草药花费的时间和价值都在10以内,因此我们可以把所有草药中花费时间和价值一样的合并,因此问题变成了了N为100(0无意义过滤掉),M为100000的多重背包,二进制优化T掉了。我们发现,对于背包容量%当前物品重量相等的情况,我们可以一起更新,并使⽤用单调队列优化,即可将时间复杂度优化成 O(NM),相对于二进制优化省了一个log,就能过了。

AC 代码

#include<bits/stdc++.h> 
#define mp make_pair 
using namespace std; 
int data[15][15]={0}; 
int dp[100005]; 
pair<int,int> que[100005]; 
int main() 
{ 
    int ll=0,rr=0; 
    int n,m; 
    scanf("%d%d",&n,&m); 
    while(n--) 
    { 
        int a,b; 
        scanf("%d%d",&a,&b); 
        data[a][b]++; 
    } 
    for(int i=0;i<=10;i++)
    for(int j=0;j<=10;j++)
        if(data[i][j]) 
        { 
            for(int k=0;k<i;k++) 
            { 
                ll=rr=0; 
                que[rr++]=mp(dp[k],0); 
                for(int l=1;l*i+k<=m;l++) 
                { 
                    int nu=l*i+k;
                    while(ll<rr&&que[ll].first+(l-que[ll].second)*j<dp[nu]) rr--; 
                    que[rr++]=mp(dp[nu],l);
                    while(l-que[ll].second>data[i][j]) ll++; 
                    dp[nu]=que[ll].first+(l-que[ll].second)*j;  
                } 
            } 
        } 
    printf("%d\n",dp[m]); 
    return 0;
}

Problem J 三个袋子

问题描述

题目背景:cwz 在公园里游玩时捡到了很多小球,而且每个球都不一样。cwz 找遍了全身只发现了了3个一模一样的袋子。他打算把这些小球都装进袋子里(袋子可以为空) 。他想知道他总共有多少种放法。
问题描述: 将N个不同的球放到3个相同的袋子里,求放球的方案总数M。 结果可能很大,我们仅要求输出Mmod K的结果。 现在,平平已经统计出了了N<=10 的所有情况。⻅见下表: N 1 2 3 4 5 6 7 8 9 10 M 1 2 5 14 41 122 365 1094 3281 9842

解题思路 容易得出m[i]=m[i-1]*3-1,在m[1]=1 的情况下,可推出通项公式m[n]=(3^(n-1)+1)/2%k。由 于 2与 k 不一定互质,因此不不能转成乘法逆元。我们容易得出 a/b%k=a%(b×k)/b,因此 m[n]=(3^(n-1)%2k+1)/2,n比较大的情况考虑快速幂,时间复杂度O(logn)。

AC 代码

#include<bits/stdc++.h> 
using namespace std; 
long long kpow(int a,int b,intc) 
{ 
    long long ans=1; 
    long long tmp=a; 
    while(b) 
    { 
        if(b&1) ans=ans*tmp%c; 
        tmp=tmp*tmp%c; 
        b>>=1; 
    } 
    return ans; 
} 

int main() 
{ 
    int n,k; 
    scanf("%d%d",&n,&k); 
    printf("%lld\n",(kpow(3,n-1,k<<1)+1)>>1); 
    return 0; 
}

Problem K 广告收入

问题描述 大家都知道,lwx 的OI 商店是靠广告来获得收入的。而广告都是那些热心的顾客们点击的。每点击一次,Google就会给lwx的账户中存一定的钱。当该⽉月的收入大于等于100美元时,Google 才会把账户里的钱寄给lwx,然后lwx 就可以拿着这笔钱去资助失学儿童了! Google会在OI 商店的网页上随机的发布N家广告商的广告,每家广告商的广告的价格 (即Google支付给lwx的钱数)是不同的。这个月有M个热心的顾客点击了广告。 假如说资助一个失学儿童需要K美元的话。现在已经知道了了这N家⼴广告商的价格,也 知道了了M个热心的顾客都点了了哪些广告。请你帮lwx算一下,他最多能资助几个失学儿童呢?

解题思路 用map 存下广告价格,然后直接计算即可。

AC 代码

#include<bits/stdc++.h> 
using namespace std; 
map<string,double>M; 
int main() 
{ 
    int n,m,k; 
    cin>>n>>m>>k; 
    while(n--) 
    { 
        string a; 
        double b; 
        cin>>a>>b; 
        M[a]=b; 
    } 
    double ans=0.0; 
    while(m--) 
    { 
        string a,b; 
        cin>>a>>b; 
        ans+=M[b]; 
    } 
    cout<<floor(ans/k)<<endl; 
    return 0; 
}

Problem L 招聘

问题描述 Alice新开了一家公司,它的下面有两个项目,分别需要N1和N2个⼈人来完成。现在有N个人前来应聘,于是Alice通过⾯面试来决定他们中的哪些⼈人会被录用。
Alice在面试中,会仔细考察他们能如何为公司的项目带来收益。她给每个人打了两个分值Q1和Q2,表示他加入第一个和第二项目分别能带来的收益值。同时,她也会仔细考察他们每个人的缺点,并且给每人打了另两个分值C1和C2,表示他们进入每个项目可能带来的负面效应。Alice心目中的最优决策是,在决定好录用哪些人以及每个人在哪个项目下工作之后,他们为公司带来的收益总和,除以他们为项目带来的负面效应总和,这个比值要最大。 你能帮他计算出在最优决策下,这个比值为多少吗? 前来应聘的人数总是大于等于两个项目需求人数的总和,因此 Alice 一定会恰好招 N1+N2 个人,分配给第一个项目N1个人,分配给第二个项目N2个人,没有人会同时属于两个项目。

解题思路 对于答案r,存在一种分配⽅方案,使得Q的总和等于r乘上C的总和,根据这个性质二分答 案,使⽤用动态规划法求出对应 r 的最大 Q 的总和-r 乘上 C 的总和,dp[i][j][k]表示做到前 i 个人,j个人招去做项目1,k个人招去做项目2情况下上述的最大答案,dp[i][j][k]为以下三种情况的最大值 dp[i-1][j][k](不招这个人) dp[i-1][j-1][k]+data[i].q1-data[i].c1*r(招这个人做项目1) dp[i-1][j][k-1]+data[i].q2-data[i].c2*r(招这个人做项目2)

AC 代码

#include<bits/stdc++.h> 
using namespace std; 
struct p 
{ 
    double q1,q2,c1,c2; 
    void input() 
    { 
        scanf("%lf%lf%lf%lf",&q1,&c1,&q2,&c2); 
    } 
}data[55]; 
double dp[55][55][55]; 
int n,n1,n2; 
bool isok(double r) 
{ 
    for(int i=0;i<55;i++)
    for(int j=0;j<55;j++)
    for(int k=0;k<55;k++) 
        dp[i][j][k]=-999999999.0; 
    dp[0][0][0]=0; 
    for(int i=1;i<=n;i++) 
    { 
        for(int j=0;j<=n1;j++)
        for(int k=0;k<=n2;k++) 
        { 
            if(j+k>i) break; 
            dp[i][j][k]=dp[i-1][j][k]; 
            if(j==0&&k==0); 
            else if(k==0) dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][k]+data[i].q1-data[i].c1*r);
            else if(j==0)dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][k-1]+data[i].q2-data[i].c2*r); 
            else 
            { 
                dp[i][j][k]=max(dp[i][j][k],dp[i-1][j-1][k]+data[i].q1-data[i].c1*r); 
                dp[i][j][k]=max(dp[i][j][k],dp[i-1][j][k-1]+data[i].q2-data[i].c2*r); 
            }
        }
    } 
    return dp[n][n1][n2]>0.0;
} 

int main() 
{ 
    intT; 
    scanf("%d",&T); 
    for(int cas=1;cas<=T;cas++) 
    { 
        memset(dp,0,sizeof(dp)); 
        scanf("%d%d%d",&n,&n1,&n2); 
        for(int i=1;i<=n;i++) data[i].input(); 
        double l=0.0,r=20000.0; 
        while(r-l>0.000001) 
        { 
            double mid=(l+r)/2.0; 
            if(isok(mid))l=mid; 
            else r=mid; 
        } 
        printf("Case #%d: %.6lf\n",cas,l); 
    } 
    return 0; 
}
  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值