2017年团体程序设计天梯赛 - 大区赛

今天为了团体比赛,只得放弃个人的网易笔试…

3h内只AC了L1-1 ~ L1-8、L2-1 、 L2-3 ~ L2-4、L3-1(赛后已全部AC)

L2-2(已AC) 和 L3-3(已AC)各得22分,L2-2先开始想用链表模拟,但觉得太麻烦,就改用数组模拟,非常好写,但是挂了第二个测试点;L3-3用 dfs 在一个测试点超时,看到空间很大,想到用 map 剪枝,最后没写完(回来后加上各种剪枝还是超时。。。最终才发现是滥用map和set导致超时,剪枝并没有问题)

L3-2(已AC)要求很复杂,感觉有点麻烦就放弃了,补的时候才发现其实也很简单。

比赛网址

交题网址

L1-1. 出生年

题目链接

题意

以上是新浪微博中一奇葩贴:“我出生于1988年,直到25岁才遇到4个数字都不相同的年份。”也就是说,直到2013年才达到“4个数字都不相同”的要求。本题请你根据要求,自动填充“我出生于y年,直到x岁才遇到n个数字都不相同的年份”这句话。
输入格式
输入在一行中给出出生年份y和目标年份中不同数字的个数n,其中y在[1, 3000]之间,n可以是2、或3、或4。注意不足4位的年份要在前面补零,例如公元1年被认为是0001年,有2个不同的数字0和1。
输出格式
根据输入,输出x和能达到要求的年份。数字间以1个空格分隔,行首尾不得有多余空格。年份要按4位输出。注意:所谓“n个数字都不相同”是指不同的数字正好是n个。如“2013”被视为满足“4位数字都不同”的条件,但不被视为满足2位或3位数字不同的条件。
输入样例1
1988 4
输出样例1
25 2013
输入样例2
1 2
输出样例2
0 0001

思路 - 枚举

数据范围和答案范围都很小,所以直接枚举判断即可。

代码

#include 
#include 
#include 

using namespace std;

int cnt;
bool vis[11];

int calc(int y) {
    memset(vis, false, sizeof(vis));
    cnt = 0;
    for(int i = 0; i < 4; ++i) {//因为要统计四位所有的数,所以要包含每一位
        if(!vis[y % 10]) {
            ++cnt;
            vis[y % 10] = true;
        }
        y /= 10;
    }
    return cnt;
}

int y, k;

int main() {
    while(2 == scanf("%d%d", &y, &k)) {
        for(int i = y; ; ++i) {
            if(calc(i) == k) {
                printf("%d %04d\n", i - y, i);
                break;
            }
        }
    }
    return 0;
}

L1-2. 点赞

题目链接

题意

微博上有个“点赞”功能,你可以为你喜欢的博文点个赞表示支持。每篇博文都有一些刻画其特性的标签,而你点赞的博文的类型,也间接刻画了你的特性。本题就要求你写个程序,通过统计一个人点赞的纪录,分析这个人的特性。
输入格式
输入在第一行给出一个正整数N(<=1000),是该用户点赞的博文数量。随后N行,每行给出一篇被其点赞的博文的特性描述,格式为“K F1 … FK”,其中 1<=K<=10,Fi(i=1, …, K)是特性标签的编号,我们将所有特性标签从1到1000编号。数字间以空格分隔。
输出格式
统计所有被点赞的博文中最常出现的那个特性标签,在一行中输出它的编号和出现次数,数字间隔1个空格。如果有并列,则输出编号最大的那个。
输入样例
4
3 889 233 2
5 100 3 233 2 73
4 3 73 889 2
2 233 123
输出样例
233 3

思路 - 模拟

统计每个特性标签出现的次数,然后输出出现次数最多的最大的编号。

代码

#include 
#include 
#include 

using namespace std;

int n, k, num, cnt[1003], mx;

int main() {
    while(1 == scanf("%d", &n)) {
        mx = 0;
        memset(cnt, 0, sizeof(cnt));
        while(n-- > 0) {
            scanf("%d", &k);
            while(k-- > 0) {
                scanf("%d", &num);
                mx = max(mx, ++cnt[num]);
            }
        }
        for(int i = 1000; i >= 0; --i) {
            if(cnt[i] == mx) {
                printf("%d %d\n", i, cnt[i]);
                break;
            }
        }
    }
    return 0;
}

L1-3. 情人节

题目链接

题意

以上是朋友圈中一奇葩贴:“2月14情人节了,我决定造福大家。第2个赞和第14个赞的,我介绍你俩认识…………咱三吃饭…你俩请…”。现给出此贴下点赞的朋友名单,请你找出那两位要请客的倒霉蛋。
输入格式
输入按照点赞的先后顺序给出不知道多少个点赞的人名,每个人名占一行,为不超过10个英文字母的非空单词,以回车结束。一个英文句点“.”标志输入的结束,这个符号不算在点赞名单里。
输出格式
根据点赞情况在一行中输出结论:若存在第2个人A和第14个人B,则输出“A and B are inviting you to dinner…”;若只有A没有B,则输出“A is the only one for you…”;若连A都没有,则输出“Momo… No one is for you …”。
输入样例1
GaoXZh
Magi
Einst
Quark
LaoLao
FatMouse
ZhaShen
fantacy
latesum
SenSen
QuanQuan
whatever
whenever
Potaty
hahaha
.
输出样例1
Magi and Potaty are inviting you to dinner…
输入样例2
LaoLao
FatMouse
whoever
.
输出样例2
FatMouse is the only one for you…
输入样例3
LaoLao
.
输出样例3
Momo… No one is for you …

思路 - 模拟

找出第\(2\)个人和第\(14\)个人(如果有的话),然后按照总人数输出对应的内容即可。

代码

#include 
#include 
#include 

using namespace std;

int cnt;
char a[11], b[11], s[11];

int main() {
    cnt = 0;
    while(true) {
        scanf("%s", s);
        if(s[0] == '.') {
            break;
        }
        ++cnt;
        if(cnt == 2) {
            memcpy(a, s, sizeof(a));
        }
        if(cnt == 14) {
            memcpy(b, s, sizeof(b));
        }
    };
    if(cnt >= 14) {
        printf("%s and %s are inviting you to dinner...\n", a, b);
    }
    else if(cnt >= 2) {
        printf("%s is the only one for you...\n", a);
    }
    else {
        printf("Momo... No one is for you ...\n");
    }
    return 0;
}

L1-4. A乘以B

题目链接

题意

看我没骗你吧 —— 这是一道你可以在10秒内完成的题:给定两个绝对值不超过100的整数A和B,输出A乘以B的值。
输入格式
输入在第一行给出两个整数A和B(-100 <= A, B, <= 100),数字间以空格分隔。
输出格式
在一行中输出A乘以B的值。
输入样例
-8 13
输出样例
-104

思路 - 模拟

按照题意输出 ab 的值。

代码

#include 
#include 
#include 

using namespace std;

int a, b;

int main() {
    while(2 == scanf("%d%d", &a, &b)) {
        printf("%d\n", a * b);
    }
    return 0;
}

L1-5. A乘以B

题目链接

题意

真的是简单题哈 —— 给定两个绝对值不超过100的整数A和B,要求你按照“A/B=商”的格式输出结果。
输入格式
输入在第一行给出两个整数A和B(-100 <= A, B, <= 100),数字间以空格分隔。
输出格式
在一行中输出结果:如果分母是正数,则输出“A/B=商”;如果分母是负数,则要用括号把分母括起来输出;如果分母为零,则输出的商应为“Error”。输出的商应保留小数点后2位。
输入样例1
-1 2
输出样例1
-1/2=-0.50
输入样例2
1 -3
输出样例2
1/(-3)=-0.33
输入样例3
5 0
输出样例3
5/0=Error

思路 - 模拟

按照分母的正负零给表达式的分母和结果进行相应的处理即可。

代码

#include 
#include 
#include 

using namespace std;

int a, b;

int main() {
    while(2 == scanf("%d%d", &a, &b)) {
        if(b < 0) {
            printf("%d/(%d)=%.2lf\n", a, b, 1.0 * a / b);
        }
        else if(b > 0){
            printf("%d/%d=%.2lf\n", a, b, 1.0 * a / b);
        }
        else {
            printf("%d/%d=Error\n", a, b);
        }
    }
    return 0;
}

L1-6. 新世界

题目链接

题意

这道超级简单的题目没有任何输入。
你只需要在第一行中输出程序员钦定名言“Hello World”,并且在第二行中输出更新版的“Hello New World”就可以了。

思路 - 模拟

按照题意输出即可。

代码

#include 
#include 
#include 

using namespace std;

int main() {
    printf("Hello World\nHello New World\n");
    return 0;
}

L1-7. 古风排版

题目链接

题意

中国的古人写文字,是从右向左竖向排版的。本题就请你编写程序,把一段文字按古风排版。
输入格式
输入在第一行给出一个正整数N(<100),是每一列的字符数。第二行给出一个长度不超过1000的非空字符串,以回车结束。
输出格式
按古风格式排版给定的字符串,每列N个字符(除了最后一列可能不足N个)
输入样例
4
This is a test case
输出样例
asa T
st ih
e tsi
ce s

思路 - 模拟

题目给定了行数,根据字符串长度就可以计算出列数,从第一个字符按照规则填入,最后不够的用空格补齐,然后按行输出即可。

代码

#include 
#include 
#include 

using namespace std;

int row, col, r, c, len;
char s[1003], res[103][1003];

int main() {
    while(1 == scanf("%d ", &row)) {
        gets(s);
        len = strlen(s);
        col = len / row + (len % row == 0 ? 0 : 1);
        for(int i = 0; i < row; ++i) {
            res[i][0] = ' ';
            res[i][col] = '\0';
        }
        r = 0;
        c = col - 1;
        for(int i = 0; i < len; ++i) {
            res[r][c] = s[i];
            ++r;
            if(r == row) {
                --c;
                r = 0;
            }
        }
        for(int i = 0; i < row; ++i) {
            printf("%s\n", res[i]);
        }
    }
    return 0;
}

L1-8. 最佳情侣身高差

题目链接

题意

专家通过多组情侣研究数据发现,最佳的情侣身高差遵循着一个公式:(女方的身高)×1.09=(男方的身高)。如果符合,你俩的身高差不管是牵手、拥抱、接吻,都是最和谐的差度。
下面就请你写个程序,为任意一位用户计算他/她的情侣的最佳身高。
输入格式
输入第一行给出正整数N(<=10),为前来查询的用户数。随后N行,每行按照“性别 身高”的格式给出前来查询的用户的性别和身高,其中“性别”为“F”表示女性、“M”表示男性;“身高”为区间 [1.0, 3.0] 之间的实数。
输出格式
对每一个查询,在一行中为该用户计算出其情侣的最佳身高,保留小数点后2位。
输入样例
2
M 1.75
F 1.8
输出样例
1.61
1.96

思路 - 模拟

根据公式可得:
如果是男性,则 1.09
如果是女性,则 1.09

代码

#include 
#include 
#include 

using namespace std;

int n;
double h;
char s[3];

int main() {
    while(1 == scanf("%d", &n)) {
        while(n-- > 0) {
            scanf("%s%lf", s, &h);
            printf("%.2lf\n", s[0] == 'M' ? h / 1.09 : h * 1.09);
        }
    }
    return 0;
}

L2-1. 人以群分

题目链接

题意

社交网络中我们给每个人定义了一个“活跃度”,现希望根据这个指标把人群分为两大类,即外向型(outgoing,即活跃度高的)和内向型(introverted,即活跃度低的)。要求两类人群的规模尽可能接近,而他们的总活跃度差距尽可能拉开。
输入格式
输入第一行给出一个正整数N(2 <= N <= 105)。随后一行给出N个正整数,分别是每个人的活跃度,其间以空格分隔。题目保证这些数字以及它们的和都不会超过231。
输出格式
按下列格式输出

Outgoing #: N1
Introverted #: N2
Diff = N3
其中 N1 是外向型人的个数;N2 是内向型人的个数;N3 是两群人总活跃度之差的绝对值。
输入样例1
10
23 8 10 99 46 2333 46 1 666 555
输出样例1
Outgoing #: 5
Introverted #: 5
Diff = 3611
输入样例2
13
110 79 218 69 3721 100 29 135 2 6 13 5188 85
输出样例2
Outgoing #: 7
Introverted #: 6
Diff = 9359

思路 - 模拟

题目给定了行数,根据字符串长度就可以计算出列数,从第一个字符按照规则填入,最后不够的用空格补齐,然后按行输出即可。

代码

#include 
#include 
#include 

using namespace std;

int n, num[100003], sum, outgoing;

int main() {
    while(1 == scanf("%d", &n)) {
        sum = outgoing = 0;
        for(int i = 0; i < n; ++i) {
            scanf("%d", num + i);
            sum += num[i];
        }
        sort(num, num + n);
        for(int i = n >> 1; i < n; ++i) {
            outgoing += num[i];
        }
        printf("Outgoing #: %d\n", (n + 1) >> 1);
        printf("Introverted #: %d\n", n >> 1);
        printf("Diff = %d\n", (outgoing << 1) - sum);
    }
    return 0;
}

L2-2. 多项式A除以B

题目链接

题意

这仍然是一道关于A/B的题,只不过A和B都换成了多项式。你需要计算两个多项式相除的商Q和余R,其中R的阶数必须小于B的阶数。
输入格式
输入分两行,每行给出一个非零多项式,先给出A,再给出B。每行的格式如下:
N e[1] c[1] … e[N] c[N]

其中N是该多项式非零项的个数,e[i]是第i个非零项的指数,c[i] 是第i个非零项的系数。各项按照指数递减的顺序给出,保证所有指数是各不相同的非负整数,所有系数是非零整数,所有整数在整型范围内。
输出格式
分两行先后输出商和余,输出格式与输入格式相同,输出的系数保留小数点后1位。同行数字间以1个空格分隔,行首尾不得有多余空格。注意:零多项式是一个特殊多项式,对应输出为“0 0 0.0”。但非零多项式不能输出零系数(包括舍入后为0.0)的项。在样例中,余多项式其实有常数项“-1/27”,但因其舍入后为0.0,故不输出。
输入样例
4 4 1 2 -3 1 -1 0 -1
3 2 3 1 -2 0 1
输出样例
3 2 0.3 1 0.2 0 -1.0
1 1 -3.1

思路 - 模拟

比赛时本来想用链表模拟,写了一点感觉太麻烦,就改用数组模拟了。

数组下标表示指数,值表示系数,然后模拟多项式除法即可。
每次消去a的最高指数的项,直到a的阶数小于b的阶数或者a为零多项式(题目数据没有a和b均为常数多项式)。
比赛时忘了可能存在每次消除后剩下的多项式的最高指数小于每次乘以的多项式q的指数,导致一直 WA 在第 2 个测试点,回来想到这样的数据才注意到。

输入:
3 2 1 1 1 0 1
1 0 1
输出:
3 2 1.0 1 1.0 0 1.0
0 0 0.0

代码

#include 
#include 
#include 
#include 
#include 

using namespace std;

const int MAXN = 10003;
const long double EPS = 1e-15;

void read(int &mx, long double c[MAXN]) {
    int cnt, e, x;
    mx = 0;
    scanf("%d\n", &cnt);
    while(cnt-- > 0) {
        scanf("%d%d", &e, &x);
        c[e] = x;
        mx = max(mx, e);
    }
}

inline bool isZero(long double x) {
    if(x < 0.0) {
        return ((int) (x * 100)) > -5;
    }
    return ((int) (x * 100)) < 5;
}

void write(int mx, const long double c[MAXN]) {
    int cnt = 0;
    for(int i = mx; i >= 0; --i) {
        if(!isZero(c[i])) {
            ++cnt;
        }
    }
    printf("%d", cnt);
    if(cnt == 0) {
        printf(" 0 0.0\n");
        return ;
    }
    for(int i = mx; i >= 0; --i) {
        if(!isZero(c[i])) {
            printf(" %d %.1lf", i, ((double) c[i]));
        }
    }
    printf("\n");
}

int mxa, mxb, mxq;
long double a[MAXN], b[MAXN], q[MAXN];

void divide() {
    int de = mxa - mxb;
    long double dc = a[mxa] / b[mxb];
    q[de] = dc;
    mxq = max(mxq, de);
    mxa = 0;
    for(int i = mxb; i >= 0; --i) {
        a[i + de] -= b[i] * dc;
        if(!isZero(a[i + de])) {
            mxa = max(mxa, i + de);
        }
    }
    for(int i = de - 1; i >= 0; --i) {//原来比赛时忘了判断a中指数为[0, de)这一段,可能更高的指数的洗漱均为0
        if(!isZero(a[i])) {
            mxa = max(mxa, i);
        }
    }
}

int main() {
    memset(a, 0, sizeof(a));
    read(mxa, a);
    memset(b, 0, sizeof(b));
    read(mxb, b);
    mxq = 0;
    memset(q, 0, sizeof(q));
    while(mxa >= mxb && (mxa != 0 || !isZero(a[0]))) {
        divide();
    }
    write(mxq, q);
    write(mxa, a);
    return 0;
}

L2-3. 悄悄关注

题目链接

题意

新浪微博上有个“悄悄关注”,一个用户悄悄关注的人,不出现在这个用户的关注列表上,但系统会推送其悄悄关注的人发表的微博给该用户。现在我们来做一回网络侦探,根据某人的关注列表和其对其他用户的点赞情况,扒出有可能被其悄悄关注的人。
输入格式
输入首先在第一行给出某用户的关注列表,格式如下:

人数N 用户1 用户2 …… 用户N

其中N是不超过5000的正整数,每个“用户i”(i=1, …, N)是被其关注的用户的ID,是长度为4位的由数字和英文字母组成的字符串,各项间以空格分隔。

之后给出该用户点赞的信息:首先给出一个不超过10000的正整数M,随后M行,每行给出一个被其点赞的用户ID和对该用户的点赞次数(不超过1000),以空格分隔。注意:用户ID是一个用户的唯一身份标识。题目保证在关注列表中没有重复用户,在点赞信息中也没有重复用户。

输出格式
我们认为被该用户点赞次数大于其点赞平均数、且不在其关注列表上的人,很可能是其悄悄关注的人。根据这个假设,请你按用户ID字母序的升序输出可能是其悄悄关注的人,每行1个ID。如果其实并没有这样的人,则输出“Bing Mei You”。
输入样例1
10 GAO3 Magi Zha1 Sen1 Quan FaMK LSum Eins FatM LLao
8
Magi 50
Pota 30
LLao 3
Ammy 48
Dave 15
GAO3 31
Zoro 1
Cath 60
输出样例1
Ammy
Cath
Pota
输入样例2
11 GAO3 Magi Zha1 Sen1 Quan FaMK LSum Eins FatM LLao Pota
7
Magi 50
Pota 30
LLao 48
Ammy 3
Dave 15
GAO3 31
Zoro 29
输出样例2
Bing Mei You

思路 - 模拟

先用 map 标记列表中的人,然后统计点赞数的平均值,最后将点赞数大于平均值且没有出现在关注列表中的人找出来,最后排序输出即可。

代码

#include 
#include 
#include 
#include 

using namespace std;

int n, m, aver, num[10003], cnt;
string name[10003], ans[10003];
map<string, bool> mp;

int main() {
    ios_base::sync_with_stdio(false);
    while(cin >> n) {
        mp.clear();
        for(int i = 0; i < n; ++i) {
            cin >> name[0];
            mp[name[0]] = true;
        }
        cin >> m;
        aver = 0;
        for(int i = 0; i < m; ++i) {
            cin >> name[i] >> num[i];
            aver += num[i];
        }
        aver /= m;
        cnt = 0;
        for(int i = 0; i < m; ++i) {
            if(num[i] > aver && !mp[name[i]]) {
                ans[cnt++] = name[i];
            }
        }

        if(cnt == 0) {
            cout << "Bing Mei You\n";
            continue;
        }
        sort(ans, ans + cnt);
        for(int i = 0; i < cnt; ++i) {
            cout << ans[i] << "\n";
        }
    }
    return 0;
}

L2-4. 功夫传人

题目链接

题意

一门武功能否传承久远并被发扬光大,是要看缘分的。一般来说,师傅传授给徒弟的武功总要打个折扣,于是越往后传,弟子们的功夫就越弱…… 直到某一支的某一代突然出现一个天分特别高的弟子(或者是吃到了灵丹、挖到了特别的秘笈),会将功夫的威力一下子放大N倍 —— 我们称这种弟子为“得道者”。

这里我们来考察某一位祖师爷门下的徒子徒孙家谱:假设家谱中的每个人只有1位师傅(除了祖师爷没有师傅);每位师傅可以带很多徒弟;并且假设辈分严格有序,即祖师爷这门武功的每个第i代传人只能在第i-1代传人中拜1个师傅。我们假设已知祖师爷的功力值为Z,每向下传承一代,就会减弱r%,除非某一代弟子得道。现给出师门谱系关系,要求你算出所有得道者的功力总值。
输入格式
输入在第一行给出3个正整数,分别是:N(<=105)——整个师门的总人数(于是每个人从0到N-1编号,祖师爷的编号为0);Z——祖师爷的功力值(不一定是整数,但起码是正数);r ——每传一代功夫所打的折扣百分比值(不超过100的正数)。接下来有N行,第i行(i=0, …, N-1)描述编号为i的人所传的徒弟,格式为:

Ki ID[1] ID[2] … ID[Ki]

其中Ki是徒弟的个数,后面跟的是各位徒弟的编号,数字间以空格间隔。Ki为零表示这是一位得道者,这时后面跟的一个数字表示其武功被放大的倍数。
输出格式
在一行中输出所有得道者的功力总值,只保留其整数部分。题目保证输入和正确的输出都不超过1010。
输入样例
10 18.0 1.00
3 2 3 5
1 9
1 4
1 7
0 7
2 6 1
1 8
0 9
0 4
0 3
输出样例
404

思路 - DFS

按照题意可以意识到题目给了一个单根树,得道者就是叶子结点,即求所有叶子结点的权值之和。
根据题意建立有向边,然后从根开始进行 DFS ,计算所有结点的权值并统计叶子结点的权值即可。

代码

#include 
#include 
#include 

using namespace std;

struct Node {
    int e, nxt;
}node[100003];

int tot, fir[100003];

void init() {
    tot = 0;
    memset(fir, -1, sizeof(fir));
}

void add(int s, int e) {
    node[tot].e = e;
    node[tot].nxt = fir[s];
    fir[s] = tot++;
}

int n, mul[100003], k, e;
double z, r;

double dfs(int u, double cur) {
    if(mul[u] != -1) {
        return cur * mul[u];
    }
    double res = 0;
    cur *= r;
    for(int i = fir[u]; i != -1; i = node[i].nxt) {
        res += dfs(node[i].e, cur);
    }
    return res;
}

int main() {
    while(3 == scanf("%d%lf%lf", &n, &z, &r)) {
        r = (100 - r) / 100;
        memset(mul, -1, sizeof(mul));
        init();
        for(int i = 0; i < n; ++i) {
            scanf("%d", &k);
            if(k == 0) {
                scanf("%d", mul + i);
            }
            else {
                while(k-- > 0) {
                    scanf("%d", &e);
                    add(i, e);
                }
            }
        }
        printf("%d\n", ((int) dfs(0, z)));
    }
    return 0;
}

L3-1. 非常弹的球

题目链接

题意

刚上高一的森森为了学好物理,买了一个“非常弹”的球。虽然说是非常弹的球,其实也就是一般的弹力球而已。森森玩了一会儿弹力球后突然想到,假如他在地上用力弹球,球最远能弹到多远去呢?他不太会,你能帮他解决吗?当然为了刚学习物理的森森,我们对环境做一些简化:

假设森森是一个质点,以森森为原点设立坐标轴,则森森位于(0, 0)点。
小球质量为w/100 千克(kg),重力加速度为9.8米/秒平方(m/s2)。
森森在地上用力弹球的过程可简化为球从(0, 0)点以某个森森选择的角度ang (0 < ang < pi/2) 向第一象限抛出,抛出时假设动能为1000 焦耳(J)。
小球在空中仅受重力作用,球纵坐标为0时可视作落地,落地时损失p%动能并反弹。
地面可视为刚体,忽略小球形状、空气阻力及摩擦阻力等。
森森为你准备的公式:

动能公式:E = m * v2 / 2
牛顿力学公式:F = m * a
重力:G = m * g
其中:
E - 动能,单位为“焦耳”
m - 质量,单位为“千克”
v - 速度,单位为“米/秒”
a - 加速度,单位为“米/秒平方”
g - 重力加速度
输入格式
输入在一行中给出两个整数:1 <= w <= 1000 和 1 <= p <= 100,分别表示放大100倍的小球质量、以及损失动力的百分比p。
输出格式
在一行输出最远的投掷距离,保留3位小数。
输入样例
100 90
输出样例
226.757

思路 - 计算

设当前动能为 E ,本次能往前弹的距离为 x ,则有:
E=12mv2
vsin(ang)=gt
x=vcos(ang)(2t)
联立上述方程解得: x=4Emgsin(ang)cos(ang)=2Emgsin(2ang)
则当 ang=π4 xmax=2Emg ;
由于每次E成等比递减,所以对所有的x_{max}求和用等比数列求和公示可得:
s=2Emg11r

代码

#include 
#include 
#include 

using namespace std;

double m, r;

int main() {
    while(2 == scanf("%lf%lf", &m, &r)) {
        r = (100 - r) / 100;
        m /= 100;
        printf("%.3lf\n", 1000 * 2 / (m * 9.8) * 1 / (1 - r));
    }
    return 0;
}

L3-2. 周游世界

题目链接

题意

周游世界是件浪漫事,但规划旅行路线就不一定了…… 全世界有成千上万条航线、铁路线、大巴线,令人眼花缭乱。所以旅行社会选择部分运输公司组成联盟,每家公司提供一条线路,然后帮助客户规划由联盟内企业支持的旅行路线。本题就要求你帮旅行社实现一个自动规划路线的程序,使得对任何给定的起点和终点,可以找出最顺畅的路线。所谓“最顺畅”,首先是指中途经停站最少;如果经停站一样多,则取需要换乘线路次数最少的路线。
输入格式
输入在第一行给出一个正整数N(<= 100),即联盟公司的数量。接下来有N行,第i行(i=1, …, N)描述了第i家公司所提供的线路。格式为:

M S[1] S[2] … S[M]

其中M(<= 100)是经停站的数量,S[i](i=1, …, M)是经停站的编号(由4位0-9的数字组成)。这里假设每条线路都是简单的一条可以双向运行的链路,并且输入保证是按照正确的经停顺序给出的 —— 也就是说,任意一对相邻的S[i]和S[i+1](i=1, …, M-1)之间都不存在其他经停站点。我们称相邻站点之间的线路为一个运营区间,每个运营区间只承包给一家公司。环线是有可能存在的,但不会不经停任何中间站点就从出发地回到出发地。当然,不同公司的线路是可能在某些站点有交叉的,这些站点就是客户的换乘点,我们假设任意换乘点涉及的不同公司的线路都不超过5条。

在描述了联盟线路之后,题目将给出一个正整数K(<= 10),随后K行,每行给出一位客户的需求,即始发地的编号和目的地的编号,中间以一空格分隔。
输出格式
处理每一位客户的需求。如果没有现成的线路可以使其到达目的地,就在一行中输出“Sorry, no line is available.”;如果目的地可达,则首先在一行中输出最顺畅路线的经停站数量(始发地和目的地不包括在内),然后按下列格式给出旅行路线:

Go by the line of company #X1 from S1 to S2.
Go by the line of company #X2 from S2 to S3.
……
其中Xi是线路承包公司的编号,Si是经停站的编号。但必须只输出始发地、换乘点和目的地,不能输出中间的经停站。题目保证满足要求的路线是唯一的。
输入样例
4
7 1001 3212 1003 1204 1005 1306 7797
9 9988 2333 1204 2006 2005 2004 2003 2302 2001
13 3011 3812 3013 3001 1306 3003 2333 3066 3212 3008 2302 3010 3011
4 6666 8432 4011 1306
4
3011 3013
6666 2001
2004 3001
2222 6666
输出样例
2
Go by the line of company #3 from 3011 to 3013.
10
Go by the line of company #4 from 6666 to 1306.
Go by the line of company #3 from 1306 to 2302.
Go by the line of company #2 from 2302 to 2001.
6
Go by the line of company #2 from 2004 to 1204.
Go by the line of company #1 from 1204 to 1306.
Go by the line of company #3 from 1306 to 3001.
Sorry, no line is available.

思路 - BFS

读题时少读几个条件,导致写得特别麻烦,写得时候有特别烦躁,最后还是有5分的测试点 WA ,不明还有什么数据过不了。。。(最后才发现是用 set 代替 priorityqueue 导致的,但是我 set Node 的所有成员变量都参与小于号比较了的,而且重复的结点应该不影响结果吧?想不通,不过以后还是不要太扣时间什么的了,方便熟悉才最重要)

题意就是最短路,但是每次花费都是 1 ,所以退化成 bfs ,在优先队列的结点里保存: station 表示当前站, cnt 表示经过的站数, line 表示当前所在的线路, num 表示经过的线路数,然后每次取经过站数最少,经过线路最少的结点扩展即可。

代码

#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;

const int MAXN = 101;
const int MAXM = 10001;

struct Edge {
    int e, nxt;
}edge[MAXM << 1];

struct Arc {
    int s, e;

    Arc(int ss, int ee) {
        s = min(ss, ee);
        e = max(ss, ee);
    }

    bool operator < (const Arc& a) const {
        return s < a.s || (s == a.s && e < a.e);
    }
};

struct Node {
    int station, cnt, line, num;//station表示当前站,cnt表示经过的站数,line表示当前所在的线路,num表示经过的线路数

    Node() {}
    Node(int sstation, int ccnt, int lline, int nnum): station(sstation), cnt(ccnt), line(lline), num(nnum) {}

    bool operator < (const Node& a) const {
        return cnt > a.cnt || (cnt == a.cnt && num > a.num);
    }
}cur;

int line[MAXN][MAXM], station[MAXN][MAXM];//line[i][j]表示线路i的j站的前一站所在线,station[i][j]表示线路i的j站的前一站
int tot, fir[MAXM];
int s, e, l, ll;
priority_queue q;
set<int>::iterator it;
mapset<int> > mp;//当前无向弧所在的线路

void init() {
    tot = 0;
    memset(fir, -1, sizeof(fir));
}

void add(int s, int e) {//加入无向边
    edge[tot].e = e;
    edge[tot].nxt = fir[s];
    fir[s] = tot++;

    edge[tot].e = s;
    edge[tot].nxt = fir[e];
    fir[e] = tot++;
}

int bfs(const int STA, const int DES) {
    memset(line, -1, sizeof(line));
    while(!q.empty()) {
        q.pop();
    }
    q.push(Node(STA, 0, -2, 0));
    while(!q.empty()) {
        cur = q.top();
        q.pop();
        if(cur.station == DES) {
            l = cur.line;
            e = cur.station;
            return cur.cnt;
        }
        for(int i = fir[cur.station]; i != -1; i = edge[i].nxt) {
            e = edge[i].e;
            const set<int> &lin = mp[Arc(cur.station, e)];
            for(it = lin.begin(); it != lin.end(); ++it) {
                if(line[*it][e] == -1) {
                    line[*it][e] = cur.line;
                    station[*it][e] = cur.station;
                    q.push(Node(e, cur.cnt + 1, *it, cur.num + ((*it) == cur.line ? 0 : 1)));
                }
            }
        }
    }
    return -1;
}

int n, m, k, sta, des;
int ans_s[MAXM], ans_e[MAXM], ans_l[MAXM], ans_cnt, ans_num;

int main() {
    while(1 == scanf("%d", &n)) {
        init();
        mp.clear();
        for(int i = 1; i <= n; ++i) {
            scanf("%d%d", &m, &s);
            while(--m > 0) {
                scanf("%d", &e);
                if(mp[Arc(s, e)].size() == 0) {//只有无向弧没有出现时,才加入
                    add(s, e);
                }
                mp[Arc(s, e)].insert(i);
                s = e;
            }
        }
        scanf("%d", &k);
        while(k-- > 0) {
            scanf("%d%d", &sta, &des);
            memset(line, -1, sizeof(line));
            ans_cnt = bfs(sta, des);
            if(ans_cnt != -1) {
                printf("%d\n", ans_cnt);
                if(ans_cnt == 0) {
                    printf("Go by the line of company #%d from %04d to %04d.\n", *mp[Arc(sta, edge[fir[sta]].e)].begin(), sta, des);
                    continue;
                }
                ans_l[0] = -1;
                ans_num = 1;
                while(e != sta) {//找到每一段走过的区间及走的线路
                    ans_l[ans_num] = l;
                    ans_s[ans_num] = station[l][e];
                    ans_e[ans_num++] = e;

                    ll = l;
                    l = line[l][e];
                    e = station[ll][e];
                }
                s = sta;
                for(int i = ans_num - 1; i > 0; --i) {
                    if(ans_l[i] != ans_l[i - 1]) {
                        printf("Go by the line of company #%d from %04d to %04d.\n", ans_l[i], s, ans_e[i]);
                        s = ans_s[i - 1];
                    }
                }
            }
            else {
                printf("Sorry, no line is available.\n");
            }
        }
    }
    return 0;
}

后来再读题时才注意到 每个运营区间只承包给一家公司换乘点涉及的不同公司的线路都不超过5条,瞬间变得特别简单。

代码

#include 
#include 
#include 
#include 

using namespace std;

const int MAXN = 101;
const int MAXM = 10001;

struct Edge {
    int e, l, nxt;
}edge[MAXM << 1];

struct Node {
    int station, cnt, line, num;//station表示当前站,cnt表示经过的站数,line表示当前所在的线路,num表示经过的线路数

    Node() {}
    Node(int sstation, int ccnt, int lline, int nnum): station(sstation), cnt(ccnt), line(lline), num(nnum) {}

    bool operator < (const Node& a) const {
        return cnt > a.cnt || (cnt == a.cnt && num > a.num);
    }
}cur;

int line[MAXN][MAXM], station[MAXN][MAXM];//line[i][j]表示线路i的j站的前一站所在线,station[i][j]表示线路i的j站的前一站
int tot, fir[MAXM];
int s, e, l, ll;
priority_queue q;

void init() {
    tot = 0;
    memset(fir, -1, sizeof(fir));
}

void add(int s, int e, int l) {//加入无向边
    edge[tot].e = e;
    edge[tot].l = l;
    edge[tot].nxt = fir[s];
    fir[s] = tot++;

    edge[tot].e = s;
    edge[tot].l = l;
    edge[tot].nxt = fir[e];
    fir[e] = tot++;
}

int bfs(const int STA, const int DES) {
    memset(line, -1, sizeof(line));
    while(!q.empty()) {
        q.pop();
    }
    q.push(Node(STA, 0, -2, 0));
    while(!q.empty()) {
        cur = q.top();
        q.pop();
        if(cur.station == DES) {
            l = cur.line;
            e = cur.station;
            return cur.cnt;
        }
        for(int i = fir[cur.station]; i != -1; i = edge[i].nxt) {
            e = edge[i].e;
            l = edge[i].l;
            if(line[l][e] == -1) {
                line[l][e] = cur.line;
                station[l][e] = cur.station;
                q.push(Node(e, cur.cnt + 1, l, cur.num + (l == cur.line ? 0 : 1)));
            }
        }
    }
    return -1;
}

int n, m, k, sta, des;
int ans_s[MAXM], ans_e[MAXM], ans_l[MAXM], ans_cnt, ans_num;

int main() {
    while(1 == scanf("%d", &n)) {
        init();
        for(int i = 1; i <= n; ++i) {
            scanf("%d%d", &m, &s);
            while(--m > 0) {
                scanf("%d", &e);
                add(s, e, i);
                s = e;
            }
        }
        scanf("%d", &k);
        while(k-- > 0) {
            scanf("%d%d", &sta, &des);
            ans_cnt = bfs(sta, des);
            if(ans_cnt != -1) {
                printf("%d\n", ans_cnt);
                if(ans_cnt == 0) {
                    printf("Go by the line of company #%d from %04d to %04d.\n", edge[fir[sta]].l, sta, des);
                    continue;
                }
                ans_l[0] = -1;
                ans_num = 1;
                while(e != sta) {//找到每一段走过的区间及走的线路
                    ans_l[ans_num] = l;
                    ans_s[ans_num] = station[l][e];
                    ans_e[ans_num++] = e;

                    ll = l;
                    l = line[l][e];
                    e = station[ll][e];
                }
                s = sta;
                for(int i = ans_num - 1; i > 0; --i) {
                    if(ans_l[i] != ans_l[i - 1]) {
                        printf("Go by the line of company #%d from %04d to %04d.\n", ans_l[i], s, ans_e[i]);
                        s = ans_s[i - 1];
                    }
                }
            }
            else {
                printf("Sorry, no line is available.\n");
            }
        }
    }
    return 0;
}

L3-3. 球队“食物链”

题目链接

题意

某国的足球联赛中有N支参赛球队,编号从1至N。联赛采用主客场双循环赛制,参赛球队两两之间在双方主场各赛一场。

联赛战罢,结果已经尘埃落定。此时,联赛主席突发奇想,希望从中找出一条包含所有球队的“食物链”,来说明联赛的精彩程度。“食物链”为一个1至N的排列{ T1 T2 … TN },满足:球队T1战胜过球队T2,球队T2战胜过球队T3,……,球队T(N-1)战胜过球队TN,球队TN战胜过球队T1。

现在主席请你从联赛结果中找出“食物链”。若存在多条“食物链”,请找出字典序最小的。

注:排列{ a1 a2 …aN }在字典序上小于排列{ b1 b2 … bN },当且仅当存在整数K(1 <= K <= N),满足:aK < bK且对于任意小于K的正整数i,ai=bi。
输入格式
输入第一行给出一个整数N(2 <= N <= 20),为参赛球队数。随后N行,每行N个字符,给出了NxN的联赛结果表,其中第i行第j列的字符为球队i在主场对阵球队j的比赛结果:“W”表示球队i战胜球队j,“L”表示球队i负于球队j,“D”表示两队打平,“-”表示无效(当i=j时)。输入中无多余空格。
输出格式
按题目要求找到“食物链”T1 T2 … TN,将这N个数依次输出在一行上,数字间以1个空格分隔,行的首尾不得有多余空格。若不存在“食物链”,输出“No Solution”。
输入样例1
5
-LWDW
W-LDW
WW-LW
DWW-W
DDLW-
输出样例1
1 3 5 4 2
输入样例2
5
-WDDW
D-DWL
DD-DW
DDW-D
DDDD-
输出样例2
No Solution

思路 - 状压DFS

题目实际上是求字典序最小的哈密顿回路,解法依旧是 DFS + 剪枝,剪枝很容易想: mp[i][state]=true 表示已在路径上的点为 state ,此时遍历点 i 时无解。太依赖 STL 了,直接 map + set 各种花式超时,早已忘了效率很低。。。
220 并不会超空间,所以可以用数组 + 状态压缩代替,时间立刻降至 60ms

代码

#include 
#include 
#include 
#include 
#include 

using namespace std;

const int MAXN = 23;

int n, ans[MAXN];
char s[MAXN][MAXN];
bool vis[MAXN];
bool mp[MAXN][(1 << 20) | 1];

bool dfs(int u, int depth, int state) {
    if(depth == n) {
        return s[u][1] == 'W' || s[1][u] == 'L';//如果u在主场赢1或者在客场赢1则符合题意
    }
    for(int i = 1; i <= n; ++i) {
        if(!vis[i] && (s[u][i] == 'W' || s[i][u] == 'L') && !mp[i][state]) {//如果u在主场赢i或者在客场赢i则符合题意
            ans[depth] = i;
            vis[i] = true;
            if(dfs(i, depth + 1, state | (1 << (i - 1)))) {
                return true;
            }
            mp[i][state] = true;
            vis[i] = false;
        }
    }
    return false;
}

int main() {
    while(1 == scanf("%d", &n)) {
        for(int i = 1; i <= n; ++i) {
            scanf("%s", s[i] + 1);
        }
        memset(vis, false, sizeof(vis));
        memset(mp, false, sizeof(mp));
        vis[1] = true;
        ans[0] = 1;
        if(dfs(1, 1, 1)) {
            for(int i = 0; i < n; ++i) {
                printf("%d%c", ans[i], i == n - 1 ? '\n' : ' ');
            }
        }
        else {
            printf("No Solution\n");
        }
    }
    return 0;
}

你可能感兴趣的:(模拟,搜索)