DOJ Round #1 解题报告

来源:互联网 发布:php数据库教程 编辑:程序博客网 时间:2024/05/17 00:18

欧姆的车

问题描述
小车正穿行在落基山脉蜿蜒曲折的盘山公路上,乔治·西蒙·欧姆正静静地望着窗外。
此时他正被伽伐尼电路引来的讦难弄的疲惫不堪,希望能借一次出行来舒缓情绪。
你坐在他的旁边,静静地望着他,感受着天才所遭受的不公对待。
正在这时,对面小孩正在摆弄的东西引起了欧姆的注意。是一块块电阻,几乎都没有什么差别。他时而摆摆弄弄,时而写写算算,看得出来他在很认真的思考。
欧姆不禁来了兴趣,询问了起来。“我是在用这几个相同的电阻,阻值当成1,尝试着串并联出一个阻值为2611的电阻。。。不过好像有些困难呢”
欧姆沉吟片刻,对孩子说道:“8个电阻便足以摆出来了。最少只要8个。”
孩子眼睛一亮:“好厉害啊!那我随便出一个分数,你能算出来吗?”
欧姆此刻心烦意乱,哪有心思去想这个?于是就把这个简单的问题扔给了你。
意即对于一个分数,你需要找到最少的1阻值的电阻数,使得可以串并联出一个阻值恰为分数值的大电阻。
你埋头苦干,而欧姆自己仍旧去欣赏窗外的艳媚骄阳。
然而他发现,自己似乎无法继续欣赏了,因为总有些东西照着他眼睛睁不开。
他循光看去,原来对面的孩子又拿出了一个三棱镜玩弄了起来。
他再也没办法忍受了,一把夺过了三棱镜,趁到站的功夫,下了车,朝空中使尽全力地扔了过去。。。
远处闪出了一丝电光。。。也许是要下雨了?
“可以,这很欧姆——水题一过,暴风雨就要来了。”
输入格式
输入仅一行,两个非负整数 a(0a<260),b(0<b<260),其中 a,b 分别表示分数的分子和分母,中间用空格隔开
输出格式
输出仅一个数,即最少使用的1阻值的电阻个数
样例输入
26 11
样例输出
8
数据范围和提示
样例解释:8个电阻的方案如右: 3 个电阻并联,然后和1个电阻串联,再依次和2个电阻并联,最后和2个电阻串联,经计算阻值为2611,且7个电阻不能达到此目的
数据范围:
对于 5% 的数据满足 a=0
另有 5% 的数据满足 a=1,ab
另有 5% 的数据满足 b=1,ab
另有 5% 的数据满足 a=b
另有 30% 的数据满足 a10,b10
对于 100% 的数据满足 0a<260,0<b<260
温馨提示:此题可能用到 long long 类型,请使用 %lld 占位符输入输出或使用 cin,cout

看上去是一道物理题,然而是一道规律/脑洞题。
考场上并没有想出来什么好的做法,于是写了个50分的暴力递推。递推的方式是每次加一个电阻,枚举并联和串联两种方式。
正解如下。
考虑一个连分数

[a0;a1,a2,a3,,an]=a0+1a1+1a2+1a3=ab

可以表示an个电阻串联后与an1个电阻串联的电路并联,然后与an2个电阻串联的电路并联,……然后与a1个电阻串联的电路并联,最后与a0个电阻串联,其最终所得的电阻。
可以证明,这种构造方式一定是符合要求的方案中所用电阻最少的。
我们记获取阻值为ab的电阻所需最少电阻数为f(a,b)
容易得到如下递归式:

f(a,b)abf(b,amodb)+ab(ba)(ba)

显然该算法与欧几里得算法的渐进复杂度相同,为O(logN)

#include <iostream>using namespace std;typedef unsigned long long ULL;ULL a, b;ULL gcd(ULL x, ULL y) {    return y == 0 ? 0 : x / y + gcd(y, x % y);}int main() {    cin >> a >> b;    cout << gcd(a, b) << endl;    return 0;}

牛顿的树

问题描述
牛顿潇洒美少年,举觞白眼望青天,皎如玉树临风前~~
像往常一样地,艾萨克爵士正站在他家的树前思考人生,突然,一个三棱镜(砸到了他的脑袋上,牛顿,卒,享年23岁)从他面前飞过,牛顿的眼中闪过了奇异的光芒(神说:要有光),他默默地捡起了三棱镜,只听biu地一声,一条光谱射了出来(光谱上的巴尔末,帕邢,莱曼等人表示不知所措),牛顿觉得树上的某些枝干不好看,于是他决定通过架设很多三棱镜来遮盖树上的一些边,但是他很懒,所以他决定先思考一下再来架设。
首先给出树的节点数 n,牛顿的树与他现在的智商成正比,所以 n300000
接下来 n1 行,每行两个整数 u,v,表示树上的一条边
由于你不可能既知道牛顿的智商,又通晓他的心思(牛顿不确定性原理),所以这是一棵无根树(废话)。
于是有一个整数 m,表示牛顿的思考次数,他最多可以在 1 秒钟内思考 300000
以下 m 行数据格式如下
首先由一个数 k 表示类型
k=1 时,有两个整数 u,v,牛顿通过架设一个三棱镜遮住树边 u,v,于是 u,v 这条边从树上消失(保证这条边之前存在)。
k=2 时,撤销上一次放置的三棱镜。
k=3 时,牛顿想知道树上的两个节点 u,v 是否连通。
而你,是R国的某只程序员,你国的尤里X已经成功地读取到了牛顿的思考过程,你需要复现 k=3 的结果,以供你国进行分析
输入格式
输入文件的第一行为一个整数 n ,表示树的节点数
接下来的 n1 行,每行整数 u,v ,表示 u,v 有一条连边
接下来一行有一个整数 m ,表示操作与询问数
接下来的 m 行,每行首先给出一个整数 k
对于 k=1,给出两个整数 u,v ,表示架设三棱镜摧毁 u,v 这条边,保证这条边之前存在
对于 k=2,表示撤销上一次的三棱镜,并保证上一次的三棱镜存在
对于 k=3,给出两个整数 u,v,询问 u,v 两点是否连通
输出格式
对于每个 k=3 的操作,若 u,v 连通则输出 true,否则输出 false
样例输入
5
1 2
2 3
3 4
4 5
10
1 5 4
1 4 3
3 5 2
1 2 1
3 1 1
3 3 1
1 3 2
3 1 3
3 1 5
3 2 2
样例输出
false
true
false
false
false
true
数据范围和提示
对于 20% 的数据, n1000,m1000
对于另外 50% 的数据, k2
对于 100% 的数据, n300000,m300000,1u,vn

裸数据结构题,大意是维护一个森林 / 一些不相交集集合 的 连通性 / 从属关系。
对于一半的数据,只有拆分操作,没有联通操作。我们可以将操作倒序,于是就变成了一道轻松愉快的并查集水题。
对于全部数据,如果看作森林处理,可以写LCT或者树链剖分。然而,这道题具有一定的特殊性,即联通操作的对象一定是最近一次拆分操作的对象,针对这个性质,我们也可以写可持久化并查集。
这里上LCT代码。复杂度均摊O(NlogN)

#include <stack>#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 300030;int n, m, fa[N], child[N][2], mark[N];#define L(x) (child[x][0])#define R(x) (child[x][1])#define kd(x) (R(fa[x]) == x)#define isRoot(x) (L(fa[x]) != x && R(fa[x]) != x)#define setChild(f, c, t) (fa[child[f][t] = c] = f)inline void push(int x) {    if (mark[x]) {        mark[x] = 0;        mark[L(x)] ^= 1;        mark[R(x)] ^= 1;        swap(L(x), R(x));    }}stack<int> ss;void pushDown(int x) {    ss.push(x);    while (! isRoot(x)) {        ss.push(x = fa[x]);    }    while (! ss.empty()) {        push(ss.top());        ss.pop();    }}//这里本来写的是递归形式//然而本地测试爆栈了(也许是我自己本地栈空间不够大)//然而递归交到doj上并没有爆栈inline void rotate(int x) {    int y = fa[x], t = kd(x);    setChild(y, child[x][t ^ 1], t);    if (isRoot(y)) fa[x] = fa[y];    else setChild(fa[y], x, kd(y));    setChild(x, y, t ^ 1);}inline void splay(int x) {    pushDown(x);    while (! isRoot(x)) {        if (! isRoot(fa[x])) {            if (kd(x) == kd(fa[x]))                rotate(fa[x]);            else rotate(x);        }        rotate(x);    }}inline void access(int x) {    int tmp = 0;    while (x) {        splay(x);        R(x) = tmp;        tmp = x;        x = fa[x];    }}inline void makeRoot(int x) {    access(x);    splay(x);    mark[x] ^= 1;}inline void link(int u, int v) {    makeRoot(u);    fa[u] = v;}inline void cut(int u, int v) {    makeRoot(u);    access(v);    splay(v);    L(v) = fa[u] = 0;}inline int findRoot(int x) {    access(x);    splay(x);    int tmp = x;    while (L(tmp)) tmp = L(tmp);    return tmp;}typedef pair<int, int> Pi;stack<Pi> S;int main() {    cin >> n;    int k, u, v;    for (int i = 1; i < n; i++) {        scanf("%d%d", &u, &v);        link(u, v);    } cin >> m;    while (m--) {        scanf("%d", &k);        if (k == 2) {            link(S.top().first, S.top().second);            S.pop();        } else {            scanf("%d%d", &u, &v);            if (k == 1) {                cut(u, v);                S.push(make_pair(u, v));            } else {                puts(findRoot(u) == findRoot(v) ? "true" : "false");            }        }    }    return 0;}

最短的子区间(学长的题)

问题描述
(不要问我“最短”是谁
给你一个关键字集合
(key1,key2,...,keym)
和一个单词序列
(word1,word2,...,wordn)
请在这个单词序列中找到最短的子序列能够包含所有的关键字 (可以忽略关键字顺序)
序列最短即这个序列中包含的单词数最少
输入格式
有多组数据,每组数据占三行
对于每组数据:
第一行包含两个整数 n,m, 分别代表关键字集合的大小和单词序列的长度
第二行包含 m 个用空格隔开的关键字
第三行包含 n 个用空格隔开的单词
输入数据以 EOF 作为结束标志
注意: 每个关键字和单词的长度都小于 100, 关键字保证互不相同
输出格式
对于第 X 组测试数据:
如果存在这样的最短子序列, 则输出类似于 Case X: XX
如果不存在则只需要输出 Case X: -1
X 是从 1 开始的整数, XX 是最短子序列的长度
样例输入
5 2
hello world
hell everyone this is good
10 3
sun apple tree
what is the sun tell apple tree sun call apple
样例输出
Case 1: -1
Case 2: 3
数据范围和提示
对于所有数据:1n105,1m100

首先,字符串匹配是必不可少的,对于该题这种多模板字符串匹配,我们可以考虑AC自动机(其实可以当作一般的Trie),当然也可以用hash。
匹配结束后,我么可以得到单词序列中每个单词所匹配的关键字序号。然后使用尺取法,维护一个始终向右移动的区间,不断更新包含所有关键字的子序列的最短长度。
总复杂度平均情况下O(ln)(l即单词长度)
(起初我在hash中使用19260817这个模数,由于模数太大,初始化时间过长,且浪费空间,TLE,【暴力膜不可取】)

#include <cstdio>#include <cstring>#include <iostream>#include <algorithm>using namespace std;const int N = 100010, M = 111;const int mod = 19260817 / 9;struct node{    int pos, next;}lib[M];int n, m, mapPre[mod], nodeCnt;char s1[M][M], tmp[M];inline void add(int val, int pos) {    lib[nodeCnt] = {pos, mapPre[val]};    mapPre[val] = nodeCnt++;}inline void initHashMap() {    nodeCnt = 0;    memset(mapPre, 0xff, sizeof(mapPre));}inline int getHash(const char s[]) {    int val = 0;        for (int i = 0; i < strlen(s); i++) {        val *= 26;        val %= mod;        val += s[i] - 'a';        val %= mod;    }    return val;}inline void insert(const char s[], int pos) {    int hashVal = getHash(s);    add(hashVal, pos);}int cover[N];inline int init() {    if (! ~ scanf("%d%d", &n, &m)) return 0;    initHashMap();    memset(cover, 0xff, sizeof(cover));    for (int i = 1; i <= m; i++) {        scanf("%s", s1[i]);        insert(s1[i], i);    }    for (int i = 1; i <= n; i++) {        scanf("%s", tmp);        int val = getHash(tmp);        if (mapPre[val] != -1) for (int j = mapPre[val]; j != -1; j = lib[j].next) {            if (strcmp(s1[lib[j].pos], tmp) == 0) {                cover[i] = lib[j].pos;                break;            }        }    }    return 1;}int book[M];int coverCnt;inline void mainWork() {    int ans = n + 1;    memset(book, 0, sizeof(book));    coverCnt = 0;    for (int i = 1, j = 1; i <= n; ) {        if (coverCnt < m && j > n) break;        if (coverCnt < m && j <= n) {            if (cover[j] > 0) {                book[cover[j]]++;                if (book[cover[j]] == 1)                    coverCnt++;            } j++;        } else {            if (coverCnt == m) ans = min(ans, j - i);            if (cover[i] > 0) {                book[cover[i]]--;                if (book[cover[i]] == 0)                    coverCnt--;            }            i++;        }    }    cout << ((ans == n + 1) ? -1 : ans) << endl;}//这里写得太丑,然而懒得修改了int cs;int main() {    while (init()) {        printf("Case %d: ", ++cs);        mainWork();    }    return 0;}
0 0