《算法竞赛进阶指南》1.8总结与练习(3)

158. 项链

有一天,达达捡了一条价值连城的宝石项链,但是,一个严重的问题是,他并不知道项链的主人是谁!
在得知此事后,很多人向达达发来了很多邮件,都说项链是自己的,要求他归还(显然其中最多只有一个人说了真话)。
达达要求每个人都写了一段关于自己项链的描述: 项链上的宝石用数字0至9来标示。
一个对于项链的表示就是从项链的某个宝石开始,顺指针绕一圈,沿途记下经过的宝石,比如项链: 0-1-2-3 ,它的可能的四种表示是0123、1230、2301、3012。
达达现在心急如焚,于是他找到了你,希望你能够编写一个程序,判断两个给定的描述是否代表同一个项链(注意,项链是不会翻转的)。
也就是说给定两个项链的表示,判断他们是否可能是一条项链。

输入格式
输入文件只有两行,每行一个由字符0至9构成的字符串,描述一个项链的表示(保证项链的长度是相等的)。

输出格式
如果两个对项链的描述不可能代表同一个项链,那么输出’No’,否则的话,第一行输出一个’Yes’,第二行输出该项链的字典序最小的表示。

数据范围
设项链的长度为L,1≤L≤1000000

输入样例:
2234342423
2423223434

输出样例:
Yes
2234342423

#include <iostream>
#include <algorithm>
#include <string.h>

using namespace std;

const int N = 2000010;

int n;
char a[N], b[N];

int get_min(char s[]) //返回这个字符串的最小表示
{
    int i = 0, j = 1; //i和j分别指向前两个
    while(i <= n && j <= n)
    {
        int k = 0;
        while(k < n && s[i + k] == s[j + k]) k++; //相等 k 往后移
        if(k == n) break; //整个串所有字母相等
        if(s[i + k] > s[j + k]) i += k + 1; //i变化
        else j += k + 1;
        if(i == j) i++; //两个指针指向同一位置,把其中一个指针 +1
    }
    int res = min(i, j); //循环结束 i,j有一个大于n 小于n的是答案
    s[res + n] = '\0'; //字符数组表示字符串 结尾用 \0 表示
    
    return res;
}

int main()
{
    scanf("%s%s", a, b);
    
    n = strlen(a); //求字符串长度
    
    memcpy(a + n, a, n); //复制 从数组a复制k个元素到数组b 使用memcpy函数包含头文件string.h  memcpy(b,a,sizeof(int)*k)
    memcpy(b + n, b, n);
    
    int ap = get_min(a), bp = get_min(b); //最小表示
    
    // cout <<  ap << endl;
    // cout <<  bp << endl;
    
    if(strcmp(a + ap, b + bp)) puts("No"); //判断两个字符串是不是相同
    else
    {
        puts("Yes");
        puts(a + ap);
    }
    return 0;
}

159. 奶牛矩阵

每天早上,农夫约翰的奶牛们被挤奶的时候,都会站成一个R行C列的方阵。
现在在每个奶牛的身上标注表示其品种的大写字母,则所有奶牛共同构成了一个R行C列的字符矩阵。
现在给定由所有奶牛构成的矩阵,求它的最小覆盖子矩阵的面积是多少。
如果一个子矩阵无限复制扩张之后得到的矩阵能包含原来的矩阵,则称该子矩阵为覆盖子矩阵。

输入格式
第1行:输入两个用空格隔开的整数,R和C。
第2..R+1行:描绘由奶牛构成的R行C列的矩阵,每行C个字符,字符之间没有空格。

输出格式
输出最小覆盖子矩阵的面积。(每个字符的面积为1)

数据范围
1≤R≤10000,
1≤C≤75

输入样例:
2 5
ABABA
ABABA

输出样例:
2

提示
样例中给出的矩阵的最小覆盖子矩阵为AB,面积为2。

#include <iostream>
#include <algorithm>
#include <string.h>

using namespace std;

const int N = 10010, M = 80;

int n, m;
char str[N][M];
int ne[N]; //next数组
bool st[M]; //循环节长度 宽度 是否满足

int main()
{
    cin >> n >> m;
    
    memset(st, true, sizeof st); //初始化循环节,初始化都是可能的 (memset 按字节初始化)
    for(int i = 1; i <= n; i++)
    {
        scanf("%s", str[i]);
        for(int j = 1; j <= m; j++) //判断每个循环节是不是可能 枚举循环节长度
            if(st[j]) //前面这个循环节满足
            {
                for(int k = j; k < m; k += j) //判断当前行是不是满足 *** k += j 每次枚举下一阶段的起点
                {
                    for(int u = 0; u < j && k + u < m; u++) //比较每个循环节是不是满足
                        if(str[i][u] != str[i][k + u]) //某个字符和前面字符不相等 循环节长度为j不满足
                        {
                            st[j] = false;
                            break;
                        }
                    if(!st[j]) break;    
                }
            }
    }
    int width;
    for(int i = 1; i <= m; i++)
        if(st[i])
        {
            width = i; //Z找到最短的可行循环节长度
            break;
        }
    
    for(int i = 1; i <= n; i++) str[i][width] = 0; //每个循环节后面加 "\0" 表示一个字符串 就可以对字符串进行操作
    
    //KMP
    for(int i = 2, j = 0;i <= n; i ++)
    {
        while(j && strcmp(str[i], str[j + 1])) j = ne[j]; //str是字符串 用字符串比较函数strcmp() 相等返回0 不等 返回 1表示大于 -1表示小于
        if(!strcmp(str[i], str[j + 1])) j ++;
        ne[i] = j;
    }
    
    int high = n - ne[n]; //高度最小循环节长度
    
    cout << width * high << endl;
    return 0;
}

160. 匹配统计

阿轩在纸上写了两个字符串,分别记为A和B。
利用在数据结构与算法课上学到的知识,他很容易地求出了“字符串A从任意位置开始的后缀子串”与“字符串B”匹配的长度。
不过阿轩是一个勤学好问的同学,他向你提出了Q个问题:
在每个问题中,他给定你一个整数x,请你告诉他有多少个位置,满足“字符串A从该位置开始的后缀子串”与B匹配的长度恰好为x。
例如:A=aabcde,B=ab,则A有aabcde、abcde、bcde、cde、de、e这6个后缀子串,它们与B=ab的匹配长度分别是1、2、0、0、0、0。
因此A有4个位置与B的匹配长度恰好为0,有1个位置的匹配长度恰好为1,有1个位置的匹配长度恰好为2。

输入格式
第一行输入三个整数N,M,Q,分别表示A串长度、B串长度、问题个数。
第二行输入字符串A,第三行输入字符串B。
接下来Q行每行输入1个整数x,表示一个问题。

输出格式
输出共Q行,依次表示每个问题的答案。

数据范围
1≤N,M,Q,x≤200000

输入样例:
6 2 5
aabcde
ab
0
1
2
3
4

输出样例:
4
1
1
0
0

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 200010;

int n, m, q;
char a[N], b[N];
int ne[N];
int f[N];

int main()
{
    cin >> n >> m >> q;
    scanf("%s%s", a + 1, b + 1);
    
    for(int i = 2, j = 0; i <= m; i++) //对字符串B求KMP
    {
        while(j && b[i] != b[j + 1]) j = ne[j];
        if(b[i] == b[j + 1]) j++;
        ne[i] = j;
    }
    
    for(int i = 1, j = 0; i <= n; i++) //kmp匹配
    {
        while(j && a[i] != b[j + 1]) j = ne[j];
        if(a[i] == b[j + 1]) j++;
        f[j] ++;
    }
    //f[i] 表示匹配长度最小是i的情况下,它的这样后缀有多少个
    for(int i = m; i; i --) f[ne[i]] += f[i]; //拓扑排序 把每一点的前缀累加到这一点上去 O(n)
    
    while(q --)
    {
        int x;
        cin >> x;
        cout << f[x] - f[x + 1] << endl; //至少是x的个数 减去 至少是x+1的个数 就是x的个数
    }
    return 0;
}

161. 电话列表

给出一个电话列表,如果列表中存在其中一个号码是另一个号码的前缀这一情况,那么就称这个电话列表是不兼容的。
假设电话列表如下:

·Emergency 911
·Alice 97 625 999
·Bob 91 12 54 26

在此例中,报警电话号码(911)为Bob电话号码(91 12 54 26)的前缀,所以该列表不兼容。

输入格式
第一行输入整数t,表示测试用例数量。
对于每个测试用例,第一行输入整数n,表示电话号码数量。
接下来n行,每行输入一个电话号码,号码内数字之间无空格,电话号码不超过10位。

输出格式
对于每个测试用例,如果电话列表兼容,则输出”YES”。
否则,输出”NO”。

数据范围
1≤t≤40,
1≤n≤10000

输入样例:
2
3
911
97625999
91125426
5
113
12340
123440
12345
98346

输出样例:
NO
YES

/*
Trie O(N)
1.是否存在一个串,是当前串的前缀:遍历路径中是否存在字符串结尾标志
2.当前串,是否是某一个串的前缀:遍历路径中,是否创建过新的节点(要创建新的节点才是 某一个串的前缀)
*/

#include <iostream>
#include <algorithm>
#include <cstring>

using namespace std;

const int N = 100010;

int n;
int son[N][10], idx; //Trie数  idx 表示内存池的下标
bool f[N]; //f表示结束标志
char str[10010];

bool insert(char *str) //返回是否安全
{
    int p = 0;
    bool is_math = false; //是否存在一个串 是前缀
    bool has_new_node = false; //是否创建新节点
    
    for(int i = 0; str[i]; i ++)
    {
        int u = str[i] - '0'; //把字母转换为数字
        if(!son[p][u]) //不存在这个节点 创建新的
        {
            son[p][u] = ++ idx;
            has_new_node = true;
        }
        p = son[p][u]; //移到下一节点
        if(f[p]) is_math = true; //判断当前有没有结束的标志
    }
    f[p] = true; //结尾标志 赋值true
    
    return !is_math && has_new_node; //不能存在匹配情况 创建新节点 安全
}

int main()
{
    int T;
    cin >> T;
    while(T --)
    {
        cin >> n;
        memset(son, 0, sizeof son); 
        memset(f, false, sizeof f);
        idx = 0;
        
        bool res = true; //是否存在前缀
        for(int i = 0; i < n; i ++) //读入每个字符串
        {
            cin >> str; 
            if(!insert(str)) res = false; //不存在前缀  insert(str)返回fslae 存在前缀 返回 true 安全
        }
        
        if(res) puts("YES");
        else puts("NO");
    }
    return 0;
}

162. 黑盒子

黑盒子代表一个原始的数据库。
它可以用来储存整数数组,并且它拥有一个特殊变量i。
在最开始,黑盒子是空的,并且i=0。
现在对黑盒子进行一系列的操作处理,操作包括以下两种:
1、ADD(x):表示将x加入到黑盒子中。
2、GET:使i增加1,输出黑盒子中第i小的数值(即将所有数按升序排序后的第i个数)。
下面给出一个具体例子:

序号  操作        i    盒子内数(升序排列后)             输出的值 
1    ADD(3)      0     3   
2    GET         1     3                                3 
3    ADD(1)      1     1, 3   
4    GET         2     1, 3                             3 
5    ADD(-4)     2     -4, 1, 3   
6    ADD(2)      2     -4, 1, 2, 3   
7    ADD(8)      2     -4, 1, 2, 3, 8   
8    ADD(-1000)  2     -1000, -4, 1, 2, 3, 8   
9    GET         3     -1000, -4, 1, 2, 3, 8            1 
10   GET         4     -1000, -4, 1, 2, 3, 8            2 
11   ADD(2)      4     -1000, -4, 1, 2, 2, 3, 8  

为了方便描述,下面我们定义两个序列:
1、A(1),A(2),…,A(M):这个序列由加入到黑盒子内的所有元素按加入顺序排列后得到,上例中的A序列为(3,1,-4,2,8,-1000,2)。
2、u(1),u(2),…,u(N): 这个序列的第i项表示的是第i次GET操作时,盒子内元素的数量。上例中的u序列为(1,2,6,6)。
现在请你根据给出的序列A和u求出操作过程中输出的所有数值。

输入格式
输入包括三行。
第一行包含两个整数M和N,表示A序列和u序列的长度。
第二行包含M个整数,表示A序列的每一个元素。
第三行包含N个整数,表示u序列的每一个元素。
同行每个数之间用空格隔开。

输出格式
输出操作过程中所有GET操作输出的数值。
每个数值占一行。

数据范围
|A(i)|<=2∗109,
1≤N≤M≤30000,
对于所有p(1≤p≤N), p≤u(p)≤M成立

输入样例:
7 4
3 1 -4 2 8 -1000 2
1 2 6 6

输出样例:
3
3
1
2

#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>

using namespace std;

const int N = 30010;

int n, m;
int a[N], b[N];

int main()
{
    cin >> n >> m;
    for(int i = 0; i < n; i++) cin >> a[i];
    for(int j = 0; j < m; j++) cin >> b[j];
    
    sort(b, b + m); //给b按小到大排序
    
    priority_queue<int> left; //大根堆
    priority_queue<int, vector<int>, greater<int>> right; //小根堆
    
    int i = 0, j = 0; //i枚举所有add操作 j枚举所有get操作
    while(i < n || j < m) //这里是或 只要有一个操作 没做完 就继续做
    {
        while(j < m && b[j] == i) //在i这个时刻 所有get的操作做一遍
        {
            //get操作
            cout << right.top() << endl; //第一次get直接返回右边堆顶
            
            left.push(right.top());
            right.pop();
            j ++;
        }
        //add操作
        
        int x = a[i];
        if(left.empty() || x >= right.top()) right.push(x); //左边为空 或者 当前插入 大于右边堆顶元素 插入右边
        else
        {
            left.push(x);
            right.push(left.top());//从左边拿一个到右边 维护左边 i-1 个元素
            left.pop();
        }
        i ++;
    }
    return 0;
}

163. 生日礼物

翰翰18岁生日的时候,达达给她看了一个神奇的序列 A1,A2,…,AN。
她被允许从中选择不超过 M 个连续的部分作为自己的生日礼物。
翰翰想要知道选择元素之和的最大值。
你能帮助她吗?

输入格式
第一行包含两个整数N,M。
第二行包含N个整数A1~AN。

输出格式
输出一个整数,表示答案。

数据范围
1≤N,M≤105,
|Ai|≤104

输入样例:
5 2
2 -3 2 -1 2

输出样例:
5

#include <iostream>
#include <algorithm>
#include <vector>
#include <queue>

using namespace std;

typedef pair<int, int> PII;

const int N = 100010;

int n, m;
int a[N], l[N], r[N]; //双链表 l r
bool st[N]; //标志是不是删除

void remove(int p) //从列表 和 堆 删掉 
{
    // 从链表中删去
    r[l[p]] = r[p]; //左边的右边 等于 右边
    l[r[p]] = l[p]; //右边的左边 等于 左边
    
     // 从heap里删去
    st[p] = true; //标志位true 表示已删掉
}

int main()
{
    cin >> n >> m;
    
    int k = 0; //一共有多少段正负
    for(int i = 0; i < n; i ++)
    {
        int x;
        cin >> x;
        if(!x) continue; //x = 0 继续
        
        if (!k || a[k] * x < 0) a[++ k] = x; //k=0 或 前面一段 与当前这段 符号相反 开一个新的段
        else a[k] += x; //否则把x加到上一段里面 
    }
    
    n = k;
    priority_queue<PII, vector<PII>, greater<PII>> heap; //需要找到左右两个端点
    
    int cnt = 0, res = 0; //cnt 表示多少个正的 和 res总和
    for(int i = 1; i <= n; i++)
        if(a[i] > 0)
        {
            cnt ++;
            res += a[i];
        }
        
    for(int i = 1; i <= n; i ++)
    {
        l[i] = i - 1;
        r[i] = i + 1;
        
        heap.push({ abs(a[i]), i });
    }
    
    while(cnt > m)  //我这sb 这里加 ; 号  找了那么久
    {
        while(st[heap.top().second]) heap.pop(); //延迟删除 删堆
        
        auto t = heap.top();
        heap.pop();
        
        int v = t.first, p = t.second; // 值 和 下标
        
        //删掉负的 要选左右两边 真的 最后边界 是负的 不能删掉
        if(l[p] != 0 && r[p] != n + 1 || a[p] > 0) //不能是左边界 右边界 或者 大于0
        {
            res -= v; 
            cnt --;
            
            int left = l[p], right = r[p];
            a[p] += a[left] + a[right]; //把左右两边合在一起
            heap.push({abs(a[p]), p}); //加入堆的时候 要加 绝对值
            
            remove(left);
            remove(right);
        }
        else remove(p);
    }
    cout << res << endl;
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/wmxnlfd/p/10958762.html
今日推荐