编程之美大赛初赛的一点整理

本文整理了编程之美大赛初赛的题目,涉及竞价、相似字符串、仙剑游戏迷宫问题和管道系统的解题思路和代码。通过对每个问题的详细描述、输入输出数据范围的分析,展示了如何在竞赛中采取最优策略解决问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

水水的过了初赛,整理一下两场初赛的题目。

//===================================格叽格叽========================================================


竞价


描述

Alice和Bob都要向同一个商人购买钻石。商人手中有 N 颗钻石,他会将它们一颗颗地卖给他们,Alice和Bob通过竞价的方式来决定钻石的归属。具体的过程如下:商人首先指定其中一个人开始报价,之后两人轮流报价,要求是一定要比对方报的价格更高。任何时候,如果一个人不愿出价或者出不起价钱时,可以宣布弃权,则对手以最后一次报的价格将钻石买下。当然,如果两人都没钱,商人是不会卖钻石的。首次报价至少为 1,并且只能报整数的价钱。

Alice和Bob特别爱攀比,因此他们都希望能比对方买到更多的钻石。Alice和Bob各自带了 CA 和 CB 的钱用于竞拍钻石。此外,Alice和商人有很不错的私人关系,因此商人总是会让Alice先报价。现在请问,在Alice和Bob都用最优策略的情况下,谁能买到更多钻石?假设双方都知道对方手中的现金数量,以及商人将要拍卖的钻石数量 N。

输入

输入文件包含多组测试数据。

第一行,给出一个整数T,为数据组数。接下来依次给出每组测试数据。

每组数据为三个用空格隔开的整数 N,CA,CB,表示钻石的数量,以及双方带的现金数量。

输出

对于每组测试数据,输出一行"Case #X: Y",其中X表示测试数据编号,Y的取值为{-1, 0, 1},-1表示Alice买到的钻石会比Bob少,0表示两人能买到一样多,1表示Alice能买到更多钻石。所有数据按读入顺序从1开始编号。

数据范围

1 ≤ T ≤ 1000

小数据:0 ≤ N ≤ 10; 0 < CA, CB ≤ 10

大数据:0 ≤ N ≤ 105; 0 < CA, CB ≤ 106


思路及代码

比赛的时候写了一个动态规划,复杂度很高只能过小数据,之后看了大神的分析,原来这题是有o(1) 的方法的。
虽然觉得这样推倒,不算很严谨,但是其中分情况讨论的思路还是值得借鉴的。
大神思路
这一题想了很久,思路基本都是对的,可惜最后还是放弃了没提交,现在看了别人的代码,理清了一下思路。n是宝石数目,a是Alice的钱币数目,b是bob的钱币数目。

基本就是去找规律,每次要获取宝石,Alice和Bob都必须付出至少1个钱币的代价。获胜的条件是获取超过宝石数目n的一半的宝石(n/2 + 1),考虑到Alice在最小代价下能获取的宝石数目是a,那么Alice在此时最后到底能否获胜呢?这个取决于n的大小,于是可以根据n的大小分成以下三种情况来考虑:

1.n > 2 * a;即n至少为2 * a + 1。自然会想到比较a和b的大小:

当b > a时,即b至少为a + 1,此时Bob必然获胜,因为即便前面a个宝石全部让给Alice,Bob依然可以获取剩下的至少a+1个宝石;

当b < a时,Alice必然获取,同上,因为即便前面b个宝石全部让给bob,Alice依然可以获取a个宝石,始终比bob的宝石多;

当b = a时,两人买到的宝石一样多。

2.n == 2 * a时,同上考虑:

当b < a时,Alice获胜;

当 b >= 2 * a + 2时,bob获胜,因为想要获胜就必须获取a + 1个宝石,因为Alice有a个宝石,Alice的最优策略是每次至少出1个钱币,所以bob每次必须用2个钱币才能获取一个宝石,所以即b至少为2*(a+1)时bob获胜;

当b >= a && b < 2*a + 2时,两人最终获取的宝石一样多;

3.当n < 2 * a时,此时情况最复杂,我当时就是被这里的细节给吓晕了,根据n的奇偶性分成两种情况:

当n是奇数时,获胜需要的宝石数目是winner = n/2 + 1,因为a > n / 2,所以a的数目至少是等于winner,Alice的想要获胜(即拿到winner个宝石)的最优策略是每次用d = a / winner的钱币来获取一个宝石,bob必胜的情况是每次都付出比Alice的报价多1个(即d + 1个),所以bob至少需要(d + 1) * winner才能保证必胜,所以当b >= (d+1)*winner时,bob必胜;否则的话,Alice必胜。

当n是偶数时,获胜的宝石数目winner2  = n / 2 + 1,平局(即两人拿到的宝石一样多)需要宝石数目是winner1 = n / 2,同样为了拿到winner2个宝石Alice每次付出d2  = a / winner2,为了拿到winner1个宝石Alice每次付出d1 = a / winner1,bob必胜的策略是每次付出(d1 + 1)拿到winner2个宝石,即当b >= (d1+1)*winner2时,bob必胜;Alice必胜的时候则是b< (d2 + 1) * winner1,即bob无法获得至少一半的宝石;当b>= (d2+1) * winner1 && b < (d1+1)*winner2时,两人获取的宝石一样多。

问题分析清楚之后,代码就很好写了,而且小数据和大数据都可以顺利AC!

大神代码
#include<iostream>
 using namespace std;
 int main() {
     int T;
     cin >> T;
     int n, a, b;
     int k = 0;
     while(T--) {
         cin >> n;
         cin >> a;
         cin >> b;
         int result;
         cout << "Case #";
         cout << ++k;
         cout << ": ";
         if (n > 2 * a) {
             if (b > a)
                 result = -1;
             if (b == a)
                 result = 0;
             if (b < a)
                 result = 1;
             cout << result << endl;
             continue;
         }
         if (n == 2 * a) {
             if (b < a)
                 result = 1;
             if (b >= a && b <= 2 * a + 1) 
                 result = 0;
             if (b > 2 * a + 1)
                 result = -1;
             cout << result << endl;
             continue;
         }
         if (n < 2 * a) {
             if (n % 2 == 0) {
                 int temp1 = n / 2;
                 int temp2 = n / 2 + 1;
                 int d1 = a / temp1;
                 int d2 = a / temp2;
                 int smaller = (d2 + 1) * temp1;
                 int bigger = (d1 + 1) * temp2;
                 if (b < smaller)
                     result = 1;
                 if (b >= bigger)
                     result = -1;
                 if (b >= smaller && b <bigger)
                     result = 0;
             }
             else {
                 int temp = n / 2 + 1;
                 int d = a / temp;
                 int bigger = (d + 1) * temp;
                 if (b >= bigger)
                     result = -1;
                 else 
                     result = 1;
             }
             cout << result << endl;
         }
     }
     return 0;
 }

相似字符串

描述

对于两个长度相等的字符串,我们定义其距离为对应位置不同的字符数量,同时我们认为距离越近的字符串越相似。例如,“0123”和“0000”的距离为 3,“0123”和“0213”的距离则为 2,所以与“0000”相比,“0213”和“0123”最相似。

现在给定两个字符串 S1 和 S2,其中 S2 的长度不大于 S1。请在 S1 中寻找一个与 S2 长度相同的子串,使得距离最小。

输入

输入包括多组数据。第一行是整数 T,表示有多少组测试数据。每组测试数据恰好占两行,第一行为字符串 S1,第二行为 S2。所有字符串都只包括“0”到“9”的字符。

输出

对于每组测试数据,单独输出一行“Case #c: d”。其中,c 表示测试数据的编号(从 1 开始),d 表示找到的子串的最小距离。

数据范围

1 ≤ T ≤ 100

小数据:字符串长度不超过 1000

大数据:字符串长度不超过 50000

思路及代码

这道题在比赛的时候也是只水过了小数据,后来发现以后总比较牛比的思路
是这样
现在假设 s1="010203040506070809",s2="404",首先对 s1 进行预处理,标记出每个字符出现的索引,得到下面的表格:
字符 出现的索引
'0' 0,2,4,6,8,10,12,14,16
'1' 1
'2' 3
'3' 5
'4' 7
'5' 9
'6' 11
'7' 13
'8' 15
'9' 17

然后在读取 s2 的每个字符时,根据表格在一个长为 n 的 int 数组 ans 上进行标记 s2[0] 出现的位置。例如现在 s2[0] = '4',表格中字符 '4' 对应的索引只有 7,那么令 ans[7-0]++;然后读取 s2[1]='0',表格中字符 '0' 对应的索引有 0,2,4,6,8,10,12,14 和 16,那么就令 ans[0-1]、ans[2-1]、ans[4-1]、ans[6-1]、ans[8-1]、ans[10-1]、ans[12-1]、ans[14-1] 和 ans[16-1] 均加一;最后读取 s2[2] = '4',令 ans[7-2]++。
最后得到的 ans 数组如下所示:
ans = {0, 1, 0, 1, 0, 2, 0, 2, 0, 1, 0, 1, 0 ,1, 0, 1};
那么这个 ans 表示什么呢?ans[i] 表示 s1_{i \ldots i+n} 与 s2_{0 \ldots n} 中完全相同的字符的个数,最后只要统计 ans 中最大的那个,就表示距离最小。

View Code 
 //source here
 #include<iostream>
 #include<string>
 using namespace std;
 int num[10][60000],ans[60000];
 int main()
 {
     int T,n,i,k,j,s,l1,l2,anss,t;
     string s1,s2;
     cin>>T;
     t=1;
     while (T>=t)
     {
         ++t;
         cin>>s1;cin>>s2;
         l1=s1.length(); l2=s2.length();
         for (i=0;i<10;++i) num[i][0]=0;
         for (i=0;i<l1;++i)
         {
             k=s1[i]-'0';
             ++num[k][0];
             num[k][num[k][0]]=i;
             ans[i]=0;
         }
         for (i=0;i<l2;++i)
         {
             k=s2[i]-'0';
             for (j=1;j<=num[k][0];++j)
             {
                 if (num[k][j]-i>=0)
                 ans[num[k][j]-i]++;
             }
         }
         anss=0;
         for (i=0;i<l1-l2+1;++i)
             if (ans[i]>anss) anss=ans[i];
         anss=l2-anss;
         cout<<"Case #"<<t-1<<": "<<anss<<endl;
     }
     return 0;
 }



仙剑5前传之璇光殿


描述

仙剑是一款经典的RPG游戏,最近又推出了仙剑5前传。Alice身为忠实的仙剑粉丝,当然是在第一时间就开始玩了。迷宫以及各类小游戏是仙剑系列的一大传统,这次也不例外。而且还增加了称号系统,玩家如果在满足一定条件下通过迷宫或是完成小游戏,都可以获得相应的称号奖励。Alice虽然智商也不算太低,顺利的完成游戏还是没什么问题的,但是某些称号对于她来说好像就比较困难,所以她来找你帮忙。

在这个迷宫中,有很多魔法台需要关闭,也有很多宝箱可以捡。你最主要的目标就是关闭所有的魔法台,一旦所有魔法台都被关闭后,立刻通关。这个迷宫中有两个称号可以获得,一个要求拣到所有的宝箱,另一个要求在一定时间内完成。

为了让迷宫变得更加复杂,迷宫中还有两种特殊的法阵。第一种法阵是加速阵,可以瞬间使玩家的移动速度提高5m/s(初始速度为30m/s)。第二种是五灵阵,玩家必须按照五灵相克相生的关系来选择开启适当的阵法才可顺利通过。若玩家顺利通过五灵阵,则也可获得5m/s的速度提升,反之,用户则会被困在原地5秒钟。幸运的是,Alice在网上找到了攻略,所以在通过五灵阵时,她只需要查看一下即可保证顺利通过。不过介于她的思考速度,她需要3秒钟来查看攻略。

Alice想知道如果她想要拣到所有的宝箱,那么最快多长时间能够通过这个迷宫呢?


输入

输入数据的第一行包含一个整数 T,表示数据组数。

接下来有 T 组数据,每组数据中:

第一行包含两个整数 N, M,表示迷宫中节点个数以及边数。节点由 1 到 N 标号。

接下来的 M 行每行包含三个整数 a, b, len,表示节点 a 和节点 b 之间有一条长度为 len 米的路。

接下来的一行包含一个整数 NM,表示魔法台的个数。下一行 NM 个整数,表示魔法台所在的节点编号。

接下来的一行包含一个整数 NT,表示宝箱的个数。下一行 NT 个整数,表示宝箱所在的节点编号。

接下来的一行包含一个整数 NS,表示加速阵的个数。下一行 NS 个整数,表示加速阵所在的节点编号。

接下来的一行包含一个整数 NR,表示五灵阵的个数。下一行 NR 个整数,表示五灵阵所在的节点编号。

最后一行包含一个整数 S,表示玩家的出发节点编号。


输出

对于每组数据,输出一行"Case #X: Y",其中 X 为数据组数编号,从 1 开始,Y 为通过该迷宫的最短时间,四舍五入保留5位小数。

数据范围

1 ≤ T ≤ 10

0 ≤ len ≤ 1000

小数据:

1 ≤ N ≤ 30

0 ≤ M ≤ 200

1 ≤ NM ≤ 3

0 ≤ NT, NS, NR ≤ 3

大数据:

1 ≤ N ≤ 100

0 ≤ M ≤ 2000

1 ≤ NM ≤ 4

0 ≤ NT, NS, NR ≤ 4

思路及代码

这题其实就是一个状态压缩的dp,比赛时候没有看出来非常可惜。
因为大神代码太长了,懒得分析- -!,先暂且贴在这里。
#include <algorithm>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;

double f[100][1 << 16];
int certain[100][1 << 16];
int magic[100], chest[100], speedup[100], five[100];
int is_magic[100], is_chest[100], is_speedup[100], is_five[100];
int nmagic, nchest, nspeedup, nfive, start;
int g[100][100], gg[1 << 4][100][100], n, m;
int bitcount[1 << 4];

void compute_gg(int state) {
    memcpy(gg[state], g, sizeof g);
    for (int k = 0; k < n; ++k) {
        if (is_five[k]) {
            int id = -1;
            for (int i = 0; i < nfive; ++i) {
                if (five[i] == k) {
                    id = i;
                    break;
                }
            }
            if (!(1 << id & state)) {
                continue;
            }
        }
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < n; ++j) {
                gg[state][i][j] = min(gg[state][i][j], gg[state][i][k] + gg[state][k][j]);
            }
        }
    }
}

void cleanup() {
    memset(is_magic, 0, sizeof is_magic);
    memset(is_chest, 0, sizeof is_chest);
    memset(is_speedup, 0, sizeof is_speedup);
    memset(is_five, 0, sizeof is_five);
    memset(g, 0x3f, sizeof g);
    for (int i = 0; i < 100; ++i) {
        g[i][i] = 0;
    }
    memset(certain, 0, sizeof certain);
    for (int i = 0; i < 100; ++i) {
        fill(f[i], f[i] + (1 << 16), 1e100);
    }
}

void read_list(int array[], int state[], int &len) {
    int tmp;
    scanf("%d", &len);
    for (int i = 0; i < len; ++i) {
        scanf("%d", &array[i]);
        --array[i];
        state[array[i]] = 1;
    }
}

struct State {
    int v, s;
    double dist;

    State() {}
    State(int v, int s, double dist) : v(v), s(s), dist(dist) {}

    bool operator<(const State &s) const {
        return dist > s.dist;
    }
};

inline int assemble(int smagic, int schest, int sspeedup, int sfive) {
    return sfive << 12 | sspeedup << 8 | schest << 4 | smagic;
}

int main() {
    bitcount[0] = 0;
    for (int i = 1; i < 1 << 4; ++i) {
        bitcount[i] = bitcount[i & (i - 1)] + 1;
    }

    int tott;
    scanf("%d", &tott);
    for (int curt = 0; curt < tott; ++curt) {
        cleanup();
        scanf("%d%d", &n, &m);
        for (int i = 0; i < m; ++i) {
            int a, b, l;
            scanf("%d%d%d", &a, &b, &l);
            --a; --b;
            g[a][b] = g[b][a] = l;
        }
        read_list(magic, is_magic, nmagic);
        read_list(chest, is_chest, nchest);
        read_list(speedup, is_speedup, nspeedup);
        read_list(five, is_five, nfive);
        scanf("%d", &start);
        --start;

        for (int i = 0; i < (1 << nfive); ++i) {
            compute_gg(i);
        }

        priority_queue<State> q;
        if (is_five[start]) {
            int id = -1;
            for (int i = 0; i < nfive; ++i) {
                if (five[i] == start) {
                    id = i;
                    break;
                }
            }
            int inits = 1 << (12 + id);
            f[start][inits] = 3.0;
            q.push(State(start, inits, 3.0));
        } else {
            f[start][0] = 0;
            q.push(State(start, 0, 0));
        }
        double best = 1e100;
        while (q.size() > 0) {
            State s = q.top(); q.pop();
            while (q.size() > 0 && certain[s.v][s.s]) {
                s = q.top();
                q.pop();
            }
            if (certain[s.v][s.s]) break;
            certain[s.v][s.s] = 1;

            int smagic = s.s & 15;
            int schest = s.s >> 4 & 15;
            int sspeedup = s.s >> 8 & 15;
            int sfive = s.s >> 12 & 15;

            if (smagic == (1 << nmagic) - 1 && schest == (1 << nchest) - 1) {
                best = min(best, s.dist);
                continue;
            }

            double speed = 30.0 + (bitcount[sspeedup] + bitcount[sfive]) * 5.0;

            // go to a magic
            for (int i = 0; i < nmagic; ++i) {
                if (1 << i & smagic) continue;
                int v = magic[i];
                if ((smagic | 1 << i) == (1 << nmagic) - 1 && schest != (1 << nchest) - 1) {
                    continue;
                }
                int state = assemble(smagic | 1 << i, schest, sspeedup, sfive);
                if (s.dist + gg[sfive][s.v][v] / speed < f[v][state]) {
                    f[v][state] = s.dist + gg[sfive][s.v][v] / speed;
                    q.push(State(v, state, f[v][state]));
                }
            }

            // go to chest
            for (int i = 0; i < nchest; ++i) {
                if (1 << i & schest) continue;
                int v = chest[i];
                int state = assemble(smagic, schest | 1 << i, sspeedup, sfive);
                if (s.dist + gg[sfive][s.v][v] / speed < f[v][state]) {
                    f[v][state] = s.dist + gg[sfive][s.v][v] / speed;
                    q.push(State(v, state, f[v][state]));
                }
            }

            // go to speedup
            for (int i = 0; i < nspeedup; ++i) {
                if (1 << i & sspeedup) continue;
                int v = speedup[i];
                int state = assemble(smagic, schest, sspeedup | 1 << i, sfive);
                if (s.dist + gg[sfive][s.v][v] / speed < f[v][state]) {
                    f[v][state] = s.dist + gg[sfive][s.v][v] / speed;
                    q.push(State(v, state, f[v][state]));
                }
            }

            // go to five
            for (int i = 0; i < nfive; ++i) {
                if (1 << i & sfive) continue;
                int v = five[i];
                int state = assemble(smagic, schest, sspeedup, sfive | 1 << i);
                if (s.dist + gg[sfive][s.v][v] / speed + 3.0 < f[v][state]) {
                    f[v][state] = s.dist + gg[sfive][s.v][v] / speed + 3.0;
                    q.push(State(v, state, f[v][state]));
                }
            }
        }
        printf("Case #%d: %.5f\n", curt + 1, best);
    }
    return 0;
}


管道系统

描述

你正在参与一项输油管道的项目,此项目的目标是架设一组输油管道系统,使得从源头到目的地输送的石油流量最大。如果直接从源头架设管道到目的地,一旦管道有损坏,将无法输送石油。为了增强管道系统抵抗破坏的能力,你决定设置 N - 2 个中间节点,从而把管道分散开来。

源头和目的地可以视为特殊的节点,他们的编号分别为 1 和 N,而其他的中间节点的标号为 2 到 N - 1。每条管道有特定的流量限制 c,石油可以以不超过 c 的速率在管道内流动。两个节点之间至多由一条管道连接,也就是说这两个节点之间单位时间内流动的石油不超过 c。

现在你们已经选定了 N 个节点,并且制定好了建设计划。你想知道,每当一条管道建造完成,从节点 1 到节点 N 最大流量能够增加多少。

输入

输入包括多组数据。第一行是整数 T,表示有多少组测试数据。每组测试数据的第一行包括两个整数 N 和 M,表示节点的数量和将要建造的管道数量。接下来 M 行按建造顺序描述每条管道,每行包括三个整数 i, j, c,表示要在节点 i 和 j 之间建造一条容量为 c 的管道。

输出

对于每组测试数据,先输出一行“Case #c:”,其中 c 为测试数据编号(从 1 开始)。紧接着按建造顺序输出使得总流量增加的管道编号和相应的增加量,以一个空格隔开。

数据范围

1 ≤ T ≤ 100

0 ≤ M ≤ N(N - 1)/2

1 ≤ v ≠ u ≤ N

1 ≤ c ≤ 100

小数据:2 ≤ N ≤ 20

大数据:2 ≤ N ≤ 200

思路及代码

这一题乍一看是n遍最大流,不过o(n^4) 应该会在大数据超时,但又仔细一想,每次添加一条边,最多生成一条增广链,
所以对每一条边,进行一次广搜,判断是否有新的增广链,其实就是一次求最大流的过程,复杂度为o(n^3),勉强可以接受。
但是有些人说过不了,先留坑以后写代码测试,贴上大神代码。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <vector>
#include <queue>
#define N 5005
using namespace std;
int n , m , s , t;
struct arc
{
  int x , f , re;
};
vector<arc> e[N];

void _addarc(int x ,int y ,int z)
{
  e[x].push_back((arc){y,z,e[y].size()});
  e[y].push_back((arc){x,z,e[x].size() - 1});
}
int d[N],cur[N];
bool f[N];

bool BFS()
{
  memset(f, 0 ,sizeof(f));
  queue<int> q;
  q.push(s) , d[s] = 0 , f[s] = 1;
  while (!q.empty())
  {
    int x = q.front() ; q.pop();
    for (int i = 0 ;i < e[x].size() ;i ++)
    {
      arc& a = e[x][i];
      if (!f[a.x] && a.f)
        f[a.x] = 1 , d[a.x] = d[x] + 1 , q.push(a.x);
    }
  }
  return f[t];
}

int DFS(int x , int flow)
{
  if (x == t || !flow) return flow;
  int sum = 0 , u;
  for (int& i = cur[x] ; i < e[x].size() ;i ++)
  {
    arc& a = e[x][i];
    if (d[a.x] == d[x] + 1 && (u = DFS(a.x , min(flow , a.f))) > 0 )
    {
      a.f -= u ,  e[a.x][a.re].f += u;
      sum += u , flow -= u;
      if (!flow) break;
    }
  }
  return sum;
}
int ans;
void dinic(int k)
{
  ans = 0;
  while (BFS())
  {
    memset(cur,0,sizeof(cur));
    ans += DFS(s , 1 << 30);
  }
  if (ans)
    printf("%d %d\n" , k , ans);
}

void init()
{
  for (int i = 1 ; i <= n ; ++ i)
    e[i].clear();
}

int main()
{
  int x , y , z , i, ca = 0;
  int _;cin>>_;
  while (_--)
  {
    printf("Case #%d:\n" , ++ ca);
    scanf("%d%d",&n,&m);
    s = 1 , t = n;
    for (i = 1 ; i <= m ; ++ i)
    {
      scanf("%d%d%d",&x,&y,&z);
      _addarc(x , y,  z);
      dinic(i);
    }
    init();
  }
  return 0;
}





仙剑5前传之软星包子

描述

仙剑是一款经典的RPG游戏,最近又推出了仙剑5前传。Alice身为忠实的仙剑粉丝,当然是在第一时间就开始玩了。迷宫以及各类小游戏是仙剑系列的一大传统,这次也不例外。而且还增加了称号系统,玩家如果在满足一定条件下通过迷宫或是完成小游戏,都可以获得相应的称号奖励。Alice虽然智商也不算太低,顺利的完成游戏还是没什么问题的,但是某些称号对于她来说好像比较困难,所以她来找你帮忙。

仙剑5前传中增加了软星包子这种很萌的NPC,它散落在世界各地,你每找到一个,都可以挑战他,挑战成功可以拿到一些卡片。挑战具体说来就是一个记忆力游戏,有 N 种卡片,每种两张,总共 2N 张卡片面朝下放置。玩家可以点击面朝下的卡片并将其翻面。当有两张卡片正面朝上时,如果这两张卡片是同一种,则将这两张卡片收走,否则的话,再将这两张卡片面朝下放置在原位。

Alice的记忆力很差,只能记住 K 张卡片(包括刚刚翻开的)。她的策略如下:

* 当没有卡片正面朝上时:

   * 如果她记住的卡片中有两张是同一种的,那就翻开那两张卡片;

   * 否则,她随机的翻开一张她不知道的卡片。

* 当有一张卡片正面朝上时:

   * 如果她记得另一张同样的卡片在哪里,就直接翻开那张;

   * 否则,她随机的翻开一张她不知道的卡片。

Alice想知道,她期望要点多少次才可以完成这个小游戏。


输入

输入数据的第一行包含一个整数 T,表示数据组数。

接下来有 T 组数据,每组数据中有一行,包含两个整数 N, K。

输出

对于每组数据,输出一行"Case #X: Y",其中 X 为数据组数编号,从 1 开始,Y 为期望的步数。

绝对误差在1e-6以内或相对误差在1e-8以内即可。

数据范围

1 ≤ T ≤ 10

1 ≤ K ≤ 10

小数据:1 ≤ N ≤ 100

大数据:1 ≤ N ≤ 100000

思路及代码

这道题是一道概率相关的问题,应该是用DP来做。
但是其中涉及到很多概率的计算,还在我的理解范围之外,先留个坑,把大神的代码贴上。

#include <algorithm>
#include <cstring>
#include <cstdio>

using namespace std;

double f[100001][11], g[100001][11];

void work() {
    int n, k;
	scanf("%d%d", &n, &k);
	memset(f, 0, sizeof(f));
	memset(g, 0, sizeof(g));
	for (int i = 1; i <= n; i ++) {
		if (k <= i) {
			double a = 0, b = 0, c = 0, d = 0;
			if (k >= 2) b += ((double)(k - 1) / (2 * i - k)) * (f[i - 1][k - 2] + 2);
			a += (double)(2 * i - 2 * k + 1) / (2 * i - k);
			b += (double)(2 * i - 2 * k + 1) / (2 * i - k);
			if (k == 1) {
				d += ((double)1 / (2 * i - k)) * (f[i - 1][0] + 1);
				c += (double)(2 * i - k - 1) / (2 * i - k);
				d += (double)(2 * i - k - 1) / (2 * i - k);
			} else {
				d += ((double)1 / (2 * i - k)) * (f[i - 1][k - 2] + 1);
				d += ((double)(k - 2) / (2 * i - k)) * (f[i - 1][k - 2] + 3);
				c += (double)(2 * i - k * 2 + 1) / (2 * i - k);
				d += (double)(2 * i - k * 2 + 1) / (2 * i - k);
			}
			f[i][k] = (a * d + b) / (1 - a * c);
			g[i][k] = c * f[i][k] + d;
		}
		for (int j = k - 1; j >= 0; j --) {
			if (j > i) continue;
			if (j > 0) f[i][j] += ((double)j / (2 * i - j)) * (f[i - 1][j - 1] + 2);
			f[i][j] += ((double)(2 * i - 2 * j) / (2 * i - j)) * (g[i][j + 1] + 1);
			if (j > 0) {
				g[i][j] += ((double)1 / (2 * i - j)) * (f[i - 1][j - 1] + 1);
				g[i][j] += ((double)(j - 1) / (2 * i - j)) * (f[i - 1][j - 1] + 3);
				g[i][j] += ((double)(2 * i - 2 * j) / (2 * i - j)) * (f[i][j + 1] + 1);
			}
		}
	}
	printf("%.10lf\n", f[n][0]);
}

int main() {
	int T;
	scanf("%d", &T);
	for (int test = 0; test < T; test ++) {
		printf("Case #%d: ", test + 1);
		work();
	}

	return 0;
}



集会

描述

在一条河的一侧,分布着 N 个村庄。这些村庄平日里需要一些贸易往来,然而商人们来回走遍每一座村庄是非常辛苦的,于是他们决定每个月都在河边举行一次集会,大家都来集会上购买需要的物品。然而在集会地点的选择上,大家却有分歧,因为谁都不愿意集会的地点离自己村庄非常远。经过一番激烈的讨论之后,大家决定要将集会地点挑选在这样一个位置:它离最远的村庄的距离要尽可能的近。

我们把河看做一条足够长的直线,河岸就是平面坐标系上 y = 0 的这条线,y < 0 的区域是河水,而所有村庄都在 y ≥ 0 的区域里。现在给出所有村庄的平面坐标,你要在河岸上找到这样一个位置,使得它到所有村庄的最远距离最小。

输入

输入文件包含多组测试数据。

第一行,给出一个整数 T,为数据组数。接下来依次给出每组测试数据。

每组数据的第一行是一个整数 N,表示村庄的数量。接下来 N 行,每行有两个实数 xi 和 yi,表示每一个村庄的坐标。

输出

对于每组测试数据,输出一行"Case #X: Y",其中 X 表示测试数据编号,Y 表示集会地点的 x 坐标值,要求与正确答案的绝对误差在10-6以内。所有数据按读入顺序从 1 开始编号。

数据范围

小数据:T ≤ 100, 0 < N ≤ 50, 0 ≤ |xi|, yi ≤ 10000

大数据:T ≤ 10, 0 < N ≤ 50000, 0 ≤ |xi|, yi ≤ 10000

思路及代码

这道题是比较水的一道题,一开始我就想到了使用二分法,但是在判断搜索左区间还是右区间的时候,采用了给M加上或减去一个很小的值来判断的方法,
此方法会产生误差,使结果达不到误差要求。因为最大长度的变化是单调变化,所以只要在左区间和右区间任取对称值就好了。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector>
#include <queue>
#define N 50005
using namespace std;
#define sqr(x) ((x)*(x))

double xx[N] , yy[N];
int n;

double cal(double x)
{
  double ans = 0;
  for (int i = 1 ; i <= n ; ++ i)
    ans = max(ans , sqrt(sqr(xx[i] - x) + sqr(yy[i])));
  return ans;
}

int main()
{
  int ca = 0;
  int _;cin>>_;
  while (_--)
  {
    scanf("%d",&n);
    for (int i = 1 ; i <= n ; ++ i)
      scanf("%lf%lf" ,&xx[i] , &yy[i]);
    double l = -10000 , r = 10000 , m1 , m2;
    while (r - l >= 5e-7)
    {
      m1 = (l * 2 + r) / 3.0;
      m2 = (r * 2 + l) / 3.0;
      double v1 = cal(m1) , v2 = cal(m2);
      if (v1 < v2)
        r = m2;
      else l = m1;
    }
    printf("Case #%d: %lf\n" , ++ ca , l);
  }
  return 0;
}



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值