基础算法测试test20170326

来源:互联网 发布:中国新一代人工智能 编辑:程序博客网 时间:2024/05/16 17:10

    • 前言
    • 1无聊的军官officerpasccpp
    • 2拯救savepasccpp
    • 3魔法物品magicpasccpp
    • 4邮递员carrierpasccpp
    • 总结

前言

这次考试竟然出现了许多玄学的情况,请听我下面一一列举…

注:这次考试所有的题目均为 时限:1S 空限:256M

1、无聊的军官(officer.pas/c/cpp)

【问题描述】
每个学年的开始,高一新生们都要进行传统的军训。今年有一个军训教官十分奇怪,他为了测试学员们的反应能力,每次吹哨后学员们都会变换位置。每次左数第i位学员都会站到第ai个位置,经过若干次之后,队伍又会回到原来的样子。
你的任务是计算n个人的队伍至少经过多少次之后,队伍恢复到原来样子。
输入officer.in
输入文件的第一行包含一个整数N(0N10000),表示队伍的人数。
接下来N行,每行一个正整数ai表示左起第i个人接下来出现在左起第ai个位置上。
输出officer.out
仅包括一行,一个正整数M,表示军官最少的吹哨次数。
【样例输入】
5
2
3
4
5
1
【样例输出】
5
【数据规模】
对于30%的数据,有N100
对于100%的数据,有N10000;
对于全部数据,答案在均在64位整数范围之内。

【题解】

这个题目记得好像是一道NOI导刊上的题目(也有可能记错了),总之这道题并不是很难,考场上大家的实际得分也证明如此。

其实这道题目,只需要根据i和ai的关系建立图形,求出整个图中所有的环的长度,再最后求最小公倍数即可。

【代码】

#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;typedef long long LL;const int size = 10000+10;bool b[size];LL n,ans,Gcd,step,next[size];LL gcd(LL a,LL b);void dfs(LL x);int main() {    freopen("officer.in","r",stdin);    freopen("officer.out","w",stdout);    scanf("%lld",&n);    for(int i=1;i<=n;i++)    scanf("%lld",&next[i]);    for(int i=1;i<=n;i++)    if(!b[i]) {        step=0; dfs(i);        if(ans==0) ans = step;        else {        Gcd = gcd(ans,step);        ans=(ans*step)/Gcd;        }    }    printf("%lld\n",ans);    return 0;}inline LL gcd(LL a,LL b) { return b==0 ? a : gcd(b,a%b);}inline void dfs(LL x) { b[x] = true; step++; if(b[next[x]]) return; else dfs(next[x]);}

2、拯救save.pas/c/cpp

【问题描述】

正义之士被恶魔抓了,被关在小黑屋里,无法继续他的正义事业,你决定去拯救他。
关正义之士的小黑屋迅速被你打开,可是正义之士却被恶魔用一把锁给锁住了。这把锁包含了N个小锁。只有打开前K-2个锁,且锁上第K-1个锁,才能改变第K个锁的状态(打开或锁上该锁),第1个锁可以任意改变状态,当第1个锁锁上时第2个锁就可以改变状态。
为了知道你到底是要留下来开锁,还是“走为上”,你需要知道到底需要多少次操作才能开锁(打开或锁上一把锁算一次操作,只有当N个小锁都被打开进才算开了锁)。

输入save.in
输入文件save.in第一行为一个N(小锁的个数,1N1000)。
第二行为n个整数a1,a2,…,an(每个都是0或者1),中间用单个空格隔开。 如果是ai=1,表示第i个锁是锁着的,反之表示该锁已被打开。
输出save.out
输出文件save.out包括一个数,表示最少要操作的次数。

【样例输入】
4
1 0 1 0
【样例输出】
6
【样例说明】

1010111001100100110010000000

【数据规模】

对于40%的数据,有N30

对于100%的数据,有N1000;

【题解】

这个题目我并没有严格的证明。思路如下

用数学归纳法我们可以知道将数串000…01(一共i个数,前i-1个数都是0,第i个数是1)最后变换成i个0所需要的次数是2i11

f[i]表示把前i个数都变成0所需要的步数,h[i]表示把前i个数变成00001(i-1个0,最后一个是1)的最小次数;

那么

a[i]=1h[i]=f[i1]f[i]=h[i1]+2i11+1

a[i]=0h[i]=h[i1]+2i11+1f[i]=f[i1]

以上方程的目标为f[n]

然后你可以轻松拿到40分,为何不能AC,再看数据你会惊奇的发现h[i]肯定会爆掉,21000用ULL也存不下,只有高精了。

【代码】

//高精度是复制模板的#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;typedef long long LL;const int MAXN = 4000;const int size = 1000+10;struct BIGNUM {    int len,s[MAXN];    BIGNUM () {    memset(s,0,sizeof(s));    len=1;    }    BIGNUM operator = (const char* num) {    len=strlen(num);    for (int i=0; i<len; ++i) s[i]=num[len-i-1]-'0';    return *this;    }    BIGNUM operator = (const int num) {    char a[MAXN];    sprintf(a,"%d",num);    *this = a;    return *this;    }    BIGNUM (const int num) {    *this=num;    }    BIGNUM (const char * num) {    *this=num;    }    BIGNUM operator + (const BIGNUM & a) {    BIGNUM c;    c.len=max(len,a.len)+1;    for (int i=0,x=0; i<c.len; ++i) {        c.s[i]=s[i]+a.s[i]+x;        x=c.s[i]/10;        c.s[i]=c.s[i]%10;    }    if (c.s[c.len-1]==0) --c.len;    return c;    }    BIGNUM operator += (const BIGNUM & a) {    *this = *this+a;    return *this;    }    BIGNUM operator * (const BIGNUM & x) {    BIGNUM c;    c.len=len+x.len;    for (int i=0; i<len; ++i) {        for (int j=0; j<x.len; ++j) {        c.s[i+j]+=s[i]*x.s[j];        c.s[i+j+1]+=c.s[i+j]/10;        c.s[i+j]%=10;        }    }    if (c.s[c.len-1]==0) --c.len;    return c;    }    BIGNUM operator *= (const BIGNUM & a) {    *this = *this * a;    return *this;    }    bool operator < (const BIGNUM & x) const {    if (len != x.len) return len<x.len;    for (int i=len-1; i>=0; --i) {        if (s[i] != x.s[i]) return s[i]<x.s[i];    }    return false;    }    bool operator > (const BIGNUM & x) const {    return x<*this;    }    bool operator <= (const BIGNUM & x) const {    return !(x<*this);    }    bool operator >= (const BIGNUM & x) const {    return !(*this<x);    }    bool operator == (const BIGNUM & x) const {    return !(x<*this||*this<x);    }    bool operator != (const BIGNUM & x) const {    return x<*this||*this<x;    }}f[size],h[size],mi[size];ostream& operator << (ostream &out,const BIGNUM& x) {    for (int i=x.len-1; i>=0; --i)    cout<<x.s[i];    return out;}istream& operator >> (istream &in,BIGNUM &x) {    char num[MAXN];    in>>num;    x=num;    return in;}LL n,a[size];int main() {    freopen("save.in","r",stdin);    freopen("save.out","w",stdout);    scanf("%lld",&n);    mi[0]=1;    for(int i=1;i<=n;i++)    mi[i]=mi[i-1]*2;    for(int i=1;i<=n;i++)    scanf("%lld",&a[i]);    if(a[1]==0) {    f[1]=0;h[1]=1;    }    else {    f[1]=1;h[1]=0;    }    for(int i=1;i<=n;i++)    if(a[i]==1) {        h[i]=f[i-1];        f[i]=h[i-1]+mi[i-1];//f[i]=h[i-1]+1+mi[i-1]-1;    }        else {        h[i]=h[i-1]+mi[i-1];//h[i]=h[i-1]+1+mi[i-1]-1;        f[i]=f[i-1];    }    cout << f[n];    return 0;}//抨击这题还要打高精度!

3、魔法物品magic.pas/c/cpp

【问题描述】

有两种类型的物品:普通物品和魔法物品。普通物品没有魔法属性而魔法物品拥有一些魔法属性。每种普通物品有一个价值P,但每种魔法物品有两种价值:鉴定前的价值P1和鉴定后的价值P2(当然,P2总是大于P1)。

为了鉴定一个魔法物品,你需要购买一个鉴定卷轴,用它来鉴定魔法物品。鉴定完一件魔法物品以后,鉴定卷轴便会消失。每个鉴定将会消耗Pi元钱,如果没有足够的钱,你将无法购买任何鉴定卷轴。

现在,你正在一个集市中,同时拥有很多物品。你知道每件物品的价值并且想要出售全部物品。那么,你最多能够获得多少钱呢?

你可以假定:
• 开始的时候你没有钱。
• 所有的魔法物品都还没有被鉴定。
• 只要你有足够的钱,你可以购买任意多的鉴定卷轴。

输入magic.in

第一行有两个整数N和Pi (0<Pi5000),表示你拥有的物品数和一个鉴定卷轴价格。
接下来N行,每行给出一件物品的价格。
对于每件普通物品,那一行仅有一个整数P (0<P10000)。
对于每件魔法物品,那一行将会有两个整数P1和P2 (0<P1<P210000)

输出magic.out
一个整数表示你最多能够获得多少钱。

【样例输入】
2 10
10
20 100
【样例输出】
100
【题解】
说实话刚开始以为是贪心,后来觉得贪心有点不靠谱,往动归方面去想,然后就…MLE了。

其实可以这样去想啊

首先显然如果全部是普通物品或者p2p1p时这些物品显然可以卖掉(某人:不然还可以吃掉啊)

卖掉了这些之后我们手里有了一些Money,这个时候我们可以来判断,若这些Money足够买一个卷轴的话,我们显然可以在每卖下一个物品之前利用手中的钱买一个卷轴,然后作鉴定,这样做一定是获益最大的。(还是有点贪心的成分在吧)

那么怎么说上面这段话是正确的呢?

首先,这些剩下的物品满足的特征一定是P2P1>P,其实就是这样说吧,先买一个卷轴将它鉴定,然后再卖出去一定是赚钱的;其次,你会发现由于P2P1>P,所以P2一定大于P,其实,你若按照鉴定完了的价钱卖出的话,你一定有足够的钱再买一个卷轴,然后依次类推,你一定可以买卷轴卖物品买卷轴卖物品一直到卖掉所有的物品。这样的情况是最简单的,就是模拟一下买卷轴和买物品就行了。

可是可是可是,若这些钱买不起卷轴,那么我们就要按照鉴定前的价格卖出一些物品,让我们有钱够买卷轴,之后问题就转化成了上面的第一种情况。

但是用哪些物品来凑钱呢?我们定义一个物品的亏损=(P2P)P1(由P2P1>P可知这个式子一定大于0,其含义为使它鉴定的收益和不使它鉴定直接卖出的收益之差),我们要选出一些物品,条件是使这些物品按照原价卖出后加上原来手里有的钱足够买一个卷轴,且总亏损最小。

那么就可以轻松用背包来解。f[v]表示将物品恰好放入体积为v的背包里的最小亏损,然后枚举一下满足条件的体积即可

【代码】

#include <cstdio>#include <cstring>#include <algorithm>using namespace std;const int size = 1000+10;const int maxn = 10000000+10;int n,p,x,y,money,tmp,k,m,ans;int f[maxn];struct node{    int p1,p2,cnt;}a[size];int main() {    freopen("magic.in","r",stdin);    freopen("magic.out","w",stdout);    scanf("%d%d",&n,&p);    for(int i=1;i<=n;i++) {    scanf("%d",&x);    char ch=getchar();    if(ch!=' ') money+=x;    else {        scanf("%d",&y);        if(y-x<=p) money+=x;        else {        a[++tmp].p1=x; a[tmp].p2=y;        a[tmp].cnt=y-x-p;        m+=x; k+=y;        }    }    }    if(money>=p) {    money=money-p*tmp+k;    return printf("%d\n",money),0;    }    memset(f,127/3,sizeof(f));    f[0]=0;    for(int i=1;i<=tmp;i++)    for(int v=m;v>=a[i].p1;v--) {        f[v]=min(f[v],f[v-a[i].p1]+a[i].cnt);        if(v>=p-money&&f[v]!=707406378)//707406378=127/3        ans = max(ans,money+k-(f[v]+p*tmp));    }    if(ans==0) ans = money+m;    printf("%d\n",ans);    return 0;}

4、邮递员carrier.pas/c/cpp

【题目描述】

邮局需要你来帮助他们为某个邮递员设计出一条能够穿过那遥远乡村的所有村子和小路至少一次的邮路(输入数据将会保证这么一条路是一定存在的)。
但是,每条路线都是有一个花费的。各个村子里的村民希望邮递员到达他们村子的时间越早越好。因此,各个村子里的人们采用了一些措施:假设第i号村子是邮递员在他的邮递路线上到达的第k个村子。如果kw(i),那么这个村子的村民就会付给邮局w(i)k欧元。当然,如果k>w(i),邮局也同意付kw(i)欧元给这个村子,对某些村子重复经过要重复收费。此外,邮递员每经过一条小路,邮局也要付给邮递员1欧元作为补贴。
现在有n个村子,编号依次为1到n。邮局就位于1号村子,因此邮递员的传递路线从这里开始,也从这个村子结束。能够离开每个村子的路口的数目一定是2,4或者8。这里允许出现同样的村子间存在多条小路,或者某条小路构成了一个自环的情况。
你的任务是设计一个路线使得邮局赚的钱最多(或者说赔的钱最少。如果有多种最优解,输出字典序最小的。
输入carrier.in
第一行:两个整数n,m,分别表示村子的数量和小路的数量。
接下来n行,每行一个整数:w(i)(1w(i)<1000)
接下来m行,每行两个整数u,v,表示这条小路连接的村子的编号。
输出carrier.out
第一行:一个整数k,你的程序所设计的路径的长度
第二行:k+1个整数,v1,v2…vk+l,每个数之间用一个空格隔开,表示你设计的路径所经过的村子的编号,其中需要满足v1=vk+1=1

【样例输入】
6 7
1
7
4
10
20
5
2 4
l 5
2 l
4 5
3 6
1 6
l 3
【样例输出】
7
1 5 4 2 1 6 3 1
【数据规模】

对于30%的数据,有N20
对于100%的数据,有N200;

【样例解释】

邮递员每条路线都要去送信,并且每条线路只要送一次就可以了。同时输出要求字典序最小,但样例输出时只给了一个可行解,没有字典序最小。
hint

【题解】

这个题目我是乱搞出来的,其实也不知道怎么做。总之是数据水吧还是怎么,我连w[i]的值用都没用,却也可以A掉这个题,想象一下吧。

其实就是找欧拉回路。

【代码】

#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int size = 200+10;int n, m,tot;int a[size],w[size][size],sta[size];void dfs(int x);int main() {    freopen("carrier.in","r",stdin);    freopen("carrier.out","w",stdout);    scanf("%d%d",&n,&m);    for(int i=1;i<=n;i++)    scanf("%d",&a[i]);    for(int i=1,x,y;i<=m;i++) {    scanf("%d%d",&x,&y);    w[x][y]++;w[y][x]++;    }    dfs(1);    printf("%d\n",tot-1);    for(int i=tot;i>=1;i--)    printf("%d ",sta[i]);    putchar(10);    return 0;}void dfs(int x) {    for(int i=1;i<=n;i++)    if(w[x][i]>0) {        w[x][i]--;w[i][x]--;        dfs(i);    }    tot++;    sta[tot]=x;}

总结

这次考试总体还好,但还有许多的提升空间,还是得继续稳基础,不得太浮了。

排名 名称 carrier magic officer save 总分 2 zyg 100 0 100 40 240

可怜了我的magic…

0 0
原创粉丝点击