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

154. 滑动窗口

给定一个大小为n≤10^6的数组。
有一个大小为k的滑动窗口,它从数组的最左边移动到最右边。
您只能在窗口中看到k个数字。
每次滑动窗口向右移动一个位置。
以下是一个例子:
该数组为[1 3 -1 -3 5 3 6 7],k为3。

窗口位置 				最小值 	最大值
[1 3 -1] -3 5 3 6 7 	-1 		-3
1 [3 -1 -3] 5 3 6 7 	-3 		 3
1 3 [-1 -3 5] 3 6 7 	-3 		 5
1 3 -1 [-3 5 3] 6 7 	-3 		 5
1 3 -1 -3 [5 3 6] 7 	 3 		 6
1 3 -1 -3 5 [3 6 7] 	 3	 	 7

您的任务是确定滑动窗口位于每个位置时,窗口中的最大值和最小值。

输入格式
输入包含两行。
第一行包含两个整数n和k,分别代表数组长度和滑动窗口的长度。
第二行有n个整数,代表数组的具体数值。
同行数据之间用空格隔开。

输出格式
输出包含两个。
第一行输出,从左至右,每个位置滑动窗口中的最小值。
第二行输出,从左至右,每个位置滑动窗口中的最大值。

输入样例:
8 3
1 3 -1 -3 5 3 6 7

输出样例:
-1 -3 -3 -3 3 3
3 3 5 5 6 7

#include <iostream>
#include <algorithm>

using namespace std;

const int N = 1000010;

int a[N], q[N]; //a原数组 q单调队列

int n, k;
int main()
{
    scanf("%d%d", &n, &k);
    for(int i = 0; i < n; i++) scanf("%d", &a[i]);
    
    int hh = 0, tt = -1; //队头0 队尾 -1
    for(int i = 0; i < n; i++)
    {
        if(hh <= tt && q[hh] < i - k + 1) hh ++; //当前队头不在窗口里面 往后移动
        while(hh <= tt && a[q[tt]] >= a[i]) tt--; //队尾元素比当前元素大,删掉
        q[++ tt] = i; //把当前数加入单调队列
        
        if(i >= k - 1) printf("%d ", a[q[hh]]); //最小值 把单调队列 队头 输出
    }
    puts("");
    
    //下面输出最大值
    hh = 0, tt = -1;
    for(int i = 0; i < n; i++)
    {
        if(hh <= tt && q[hh] < i - k + 1) hh ++; //当前队头不在窗口里面 往后移动
        while(hh <= tt && a[q[tt]] <= a[i]) tt--; //队尾元素比当前元素小,删掉
        q[++ tt] = i; //把当前数加入单调队列
        
        if(i >= k - 1) printf("%d ", a[q[hh]]); //最大值 把单调队列 队头 输出
    }
    puts("");
    
    return 0;
} 

155. 内存分配

内存是计算机重要的资源之一,程序运行的过程中必须对内存进行分配。
经典的内存分配过程是这样进行的:

1、 内存以内存单元为基本单位,每个内存单元用一个固定的整数作为标识,称为地址。地址从0开始连续排列,地址相邻的内存单元被认为是逻辑上连续的。我们把从地址i开始的s个连续的内存单元称为首地址为i长度为s的地址片。

2、 运行过程中有若干进程需要占用内存,对于每个进程有一个申请时刻T,需要内存单元数M及运行时间P。在运行时间P内(即T时刻开始,T+P时刻结束),这M个被占用的内存单元不能再被其他进程使用。

3、假设在T时刻有一个进程申请M个单元,且运行时间为P,则:

1.若T时刻内存中存在长度为M的空闲地址片,则系统将这M个空闲单元分配给该进程。若存在多个长度为M个空闲地址片,则系统将首地址最小的那个空闲地址片分配给该进程。

2.如果T时刻不存在长度为M的空闲地址片,则该进程被放入一个等待队列。对于处于等待队列队头的进程,只要在任一时刻,存在长度为M的空闲地址片,系统马上将该进程取出队列,并为它分配内存单元。注意,在进行内存分配处理过程中,处于等待队列队头的进程的处理优先级最高,队列中的其它进程不能先于队头进程被处理。

现在给出一系列描述进程的数据,请编写一程序模拟系统分配内存的过程。

输入格式
第一行是一个数N,表示总内存单元数(即地址范围从0到N-1)。
从第二行开始每行描述一个进程的三个整数T、M、P(M <= N)。
最后一行用三个0表示结束。
数据已按T从小到大排序。
输入文件最多10000行,且所有数据都小于109。
输入文件中同一行相邻两项之间用一个或多个空格隔开。

输出格式
输出包括2行。
第一行是全部进程都运行完毕的时刻。
第二行是被放入过等待队列的进程总数。

输入样例:
10
1 3 10
2 4 3
3 4 4
4 1 4
5 3 4
0 0 0

输出样例:
12
2

提示

内存.png

/*
数据结构:
1.等待队列:(内存长度, 占用时间):queue
2.内存使用情况:(起始下标,长度)
  线性扫描、删除、插入:set
3.小根堆:(释放时间key,起始下标),priority_queue

算法流程:
新来一个请求:(T,M,P)
1.释放掉所有 释放时间 <= T 的内存,每次释放之后,都要判断等待队列的队头是否可以满足
2.判断(T,M,P)是否可以满足,如果不可以,则插入等待队列数据结构:
*/

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

using namespace std;

typedef pair<int, int> PII;

int n;
queue<PII> waits; //等待队列 (first:内存长度,second:占用内存时间)
set<PII> runs; //当前内存使用情况 (first: 起始下标, second: 长度)
priority_queue<PII, vector<PII>, greater<PII>> endts; //小根堆 (first: 释放时间, second: 起始下标)
int tm, cnt; //tm运行完毕的时刻,cnt放入过等待队列的进程总数

bool give(int t, int m, int p)
{
    for(auto it = runs.begin(); it != runs.end(); it ++)
    {
        auto jt = it; //jt 是 it的后面一个元素
        jt ++;
        if(jt != runs.end())
        {
            if(m <= jt->first - (it->first + it->second - 1) - 1) //(it->first + it->second - 1)是终止地址
            {
                int start = it->first + it->second; //起始下标
                runs.insert({start, m});
                endts.push({t + p, start});
                return true;
            }
        }
    }
    return false;
}

void finish(int t)
{
    while(endts.size() && endts.top().first <= t) //小于t 释放掉
    {
        int f = endts.top().first;
        while(endts.size() && endts.top().first == f) //同时结束多个进程
        {
            auto top = endts.top();
            endts.pop();
            auto it = runs.lower_bound({top.second, 0}); //在set找到 删掉
            runs.erase(it);
        }
        
        tm = f; //存最后一个结束的时间
        while(waits.size())
        {
            auto front = waits.front();
            if(give(f, front.first, front.second)) //front.first内存长度 front.second 内存占用时间
            {
                waits.pop();
            }
            else break;
        }
    }
}

int main()
{
    cin >> n;
    int t, m, p;
    
    runs.insert({-1, 1}), runs.insert({n, 1}); //哨兵 
    
    while(cin >> t >> m >> p, t || m || p) //最后一行用三个0表示结束。
    {
        finish(t); //1. 释放当前时间 <=t 的内存
        if(!(give(t, m, p))) //如果不可以 push进等待队列
        {
            waits.push({m, p});
            cnt ++;
        }
    }
    
    finish(2e9); //结束所有进程
    
    cout << tm << endl << cnt << endl;
    return 0;
}

156. 矩阵

给定一个M行N列的01矩阵(只包含数字0或1的矩阵),再执行Q次询问,每次询问给出一个A行B列的01矩阵,求该矩阵是否在原矩阵中出现过。

输入格式
第一行四个整数M,N,A,B。
接下来一个M行N列的01矩阵,数字之间没有空格。
接下来一个整数Q。
接下来Q个A行B列的01矩阵,数字之间没有空格。

输出格式
对于每个询问,输出1表示出现过,0表示没有出现过。

数据范围
A≤100,M,N,B≤1000,Q≤1000

输入样例:
3 3 2 2
111
000
111
3
11
00
11
11
00
11

输出样例:
1
0
1

#include <iostream>
#include <algorithm>
#include <unordered_set>

using namespace std;

typedef unsigned long long ULL;

const int N = 1010, M = N * N, P = 131;

int n, m, a, b;
ULL h[N][N], p[M]; //原字符串的hash 每一行所有前缀的hash p处理p的多少次方
char str[N];

ULL get(ULL f[], int l, int r) //求L和R之间的hash值
{
    return f[r] - f[l - 1] * p[r - l + 1];
}

int main()
{
    cin >> n >> m >> a >> b;
    p[0] = 1; //p的0次方
    for(int i = 1; i <= n * m; i++) p[i] = p[i - 1] * P; // * P
    for(int i = 1; i <= n; i++) 
    {
        scanf("%s", str + 1);//涉及前缀和的一般下标从1开始
        for(int j = 1; j <= m; j++) h[i][j] = h[i][j - 1] * P + str[j] - '0'; //每一行
    }
    
    unordered_set<ULL> S; //c++ hash表
    for(int i = b; i <= m; i++) //按列循环
    {
        ULL s = 0;
        int l = i - b + 1, r = i; //这一列 的起始下标
        for(int j = 1; j <= n; j++)
        {
            s = s * p[b] + get(h[j], l, r);
            if(j > a) s -= get(h[j - a], l, r) * p[a * b]; //当前行数大于a 把最上面一行去掉
            if(j >= a) S.insert(s);
        }
    }
    int k;
    cin >> k; //k次询问
    while(k --)
    {
        ULL s = 0;
        for(int i = 0; i < a; i++)
        {
            scanf("%s",str);
            for(int j = 0; j < b; j++) s = s * P + str[j] - '0'; // * P
        }
        
        if(S.count(s)) puts("1"); //在S里面出现过
        else puts("0");
    }
    return 0;
}

157. 树形地铁系统

一些主要城市拥有树形的地铁系统,即在任何一对车站之间,有且只有一种方式可以乘坐地铁。
此外,这些城市大多数都有一个中央车站。
想象一下,你是一名在拥有树形地铁系统的城市游玩的游客,你想探索该城市完整的地铁线路。
你从中央车站出发,随机选择一条地铁线,然后乘坐地铁行进。
每次到达一个车站,你都将选择一条尚未乘坐过的地铁线路进行乘坐。
如果不存在未乘坐过的线路,则退回到上一个车站,再做选择。
直到你将所有地铁线路都乘坐过两次(往返各一次),此时你将回到中央车站。
之后,你以一种特殊的方式回忆自己的坐车过程,你将你的完整地铁乘坐路线编码为一个二进制字符串。
其中0编码表示你乘坐地铁线路到达距离中央车站更远的一站,1编码表示你乘坐地铁线路到达距离中央车站更近的一站。

subway.jpg

输入格式
第一行输入一个正整数n,代表测试用例数量。
每个测试用例由两行组成,每行输入一个由字符“0”和“1”构成的字符串,长度最多为3000, 两个字符串都描述了一种树形地铁系统的正确探索路线。

输出格式
对于每个测试用例,如果两个字符串描述的探索路线可以视为同一个地铁系统的两种探索路线,则输出same。
否则,输出different。
每行输出一个结果。

输入样例:
2
0010011101001011
0100011011001011
0100101100100111
0011000111010101

输出样例:
same
different

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

using namespace std;

string dfs(string &seq, int &u) //u表示遍历整个字符串的下标
{
    vector<string> seqs; //vector存所有的最小表示
    u ++; //过滤掉第一个0
    while(seq[u] == '0') seqs.push_back(dfs(seq, u)); //dfs子树
    u ++; //过滤掉第一个1
    
    sort(seqs.begin(), seqs.end()); //按字典序排序
    string res = "0"; //下来
    for(auto s : seqs) res += s;
    res += '1'; //回去
    return res;
}

int main()
{
    int T;
    cin >> T;
    while(T --)
    {
        string a, b;
        cin >> a >> b; //读入放在更新之前
        a = '0' + a + '1', b = '0' + b + '1'; //不处理边界 根节点加上进来和出去的标志
        int ua = 0, ub = 0; //起始下标
        if(dfs(a, ua) == dfs(b, ub)) puts("same"); //dfs返回以当前这个点 为根 的最小表示是什么
        else puts("different");
    }
    return 0;
}
posted @ 2019-05-30 14:20  WMXNLFD  阅读(342)  评论(0编辑  收藏  举报