《算法竞赛进阶指南》——二叉堆,总结与练习

二叉堆

超市

选择是否卖出一件商品时,要考虑时间和价钱,如果时间合适,那么可以直接加入,但是如果时间不合适,我们就需要判断,在这之前选择的东西,有没有哪一种商品的价格比当前的价格低,如果有,就选出最少的那一个替换掉,否则就不选择这一个商品。
首先按照时间顺序递增排序,这样可以使得容易过期的被尽量选到,然后按照上面的思路,把已经选择的用一个小根堆维护。

#include<bits/stdc++.h>
using namespace std;

const int N = 10010;
typedef pair<int,int> PII;

PII a[N];//first:商品的价格,second:商品的过期时间
bool cmp(PII x,PII y)
{
    return x.second < y.second;
}
int main()
{
    int n;
    while(cin >> n)
    {
        priority_queue<PII,vector<PII>,greater<PII > > q;
        long long res = 0;
        for(int i = 1;i <= n;i ++)
            scanf("%d%d",&a[i].first,&a[i].second);
        sort(a + 1,a + n + 1,cmp);
        for(int i = 1;i <= n;i ++)
        {
            if(a[i].second <= q.size())//如果过期时间小于已经选择的商品,就要进行判断
            {
                if(a[i].first > q.top().first)
                {
                    res -= q.top().first;//统计最终答案
                    q.pop();
                    q.push(a[i]);
                    res += a[i].first;
                }
            }
            else
            {
                q.push(a[i]);
                res += a[i].first;
            }
        }
        cout << res << endl;
    }
    return 0;
}


序列

这个题要依次合并,先讨论第一次合并,然后剩下的照搬。
假设要合并的是a和b两个序列。
先对a进行从小到大排序,然后以b序列的值为基准, b i b_i bi 加上 a j a_j aj,然后用一个小根堆来储存所有的值,选择了一个最小值之后,就可以加入 b i b_i bi + a j + 1 a_{j + 1} aj+1。因为这样做的话存在最优结果(好难解释啊)。

#include<bits/stdc++.h>
using namespace std;
const int N = 2001,M = 1001;
typedef pair<int ,int >PII;
int a[N];
int temp[N];
int c[N];
int n,m;
void merge()
{
    priority_queue<PII,vector<PII>,greater<PII> >heap;
    for(int i = 1;i <= n;i ++)
        heap.push({temp[i] + a[1],1});
    for(int i = 1;i <= n;i ++)
    {
        c[i] = heap.top().first;
        int id = heap.top().second;
        int sum = heap.top().first - a[id];
        heap.pop();
        heap.push({sum + a[id + 1],id + 1});
    }
    for(int i = 1;i <= n;i ++)
        a[i] = c[i];
}
int main()
{
    int T;
    cin >> T;
    while(T --)
    {
        cin >> m >> n;
        for(int i = 1;i <= n;i ++)
        {
            scanf("%d",&a[i]);
        }
        sort(a + 1,a + n + 1);//只需要对第一排的a进行排序
            
            
        for(int i = 2;i <= m;i ++)
        {
            for(int j = 1;j <= n;j ++)
                scanf("%d",&temp[j]);
            merge();//排序
            
        }
        for(int i = 1;i <= n;i ++)
            printf("%d ",a[i]);
        printf("\n");
    }
    return 0;
}




数据备份

这道题简化一下就是,有许多点,选择k对相邻的点连线使所连线段最短,且被选择过的点不能再被选择了,。
简单的思路就是每次选的时候肯定要选最短的啊,
但是如果最优线段选到了已经选过的点怎么办?
那么就意味着原来的某一个线段要拆掉。
那么拆掉就会少一条线,少掉的这条线怎么办?
然后有一个玄幻的定理:如果是选到了会破坏几条连续的线段的那种线段,就把这几个线段都拆掉,换上另外几段连续的线段(好难描述啊)
证明一下:如果事先选择了 d i d_i di这条线段,也就意味着 d i − 1 和 d i + 1 d_{i - 1}和d_{i + 1} di1di+1这两条不能选了。但是我们通过神奇的操作发现如果要选两条的话,需要选到 d i − 1 d_{i - 1} di1这条线段,那么第二条就一定会选到 d i + 1 d_{i + 1} di+1
为什么呢,因为如果第二条不是 d i + 1 d_{i + 1} di+1的话,就是另外一条跟 d i − 1 d_{i - 1} di1 d i d_i di无关的线段m,既然是无关,那么也可以同时选择 d i d_i di和m,因为在选择第一条线段的时候很明显 d i d_i di的值小于 d i − 1 d_{i - 1} di1,所以这样的话还不如选择 d i d_i di和m。
后面的情况也可以照此推导。
神奇的操作:但是我们怎么判断到底是选 d i d_i di和另外一条线段还是选择 d i − 1 和 d i + 1 d_{i - 1}和d_{i + 1} di1di+1。推导一下可以很容易的发现,只要 d i − 1 和 d i + 1 d_{i - 1}和d_{i + 1} di1di+1的值加起来小于 d i d_i di和另外一条线段(最小的那条),也就是 d i − 1 + d i + 1 − d i < m d_{i - 1} + d_{i + 1} -d_i < m di1+di+1di<m就行了。
因为要找最小的,所以用一个小根堆来存所有的线段长度

所以我们每次选择了一条线段的时候,就把它左右两边的线段移除, 但是为了下次选择,我们要向小根堆加入 d i − 1 + d i + 1 − d i d_{i - 1} + d_{i + 1} -d_i di1+di+1di 并且把原来 d i d_i di的值也换成这个,这样便于之后的计算(相当于把这三条线段合并成一条)
如果之后的选择会破坏这个线段的话,也可以很容易地计算(这个推一下就知道了
推一下就知道了:假设有a,b,c,d,e这五条线段,之前我们选择了b和d,ans中加上了c和(b + d - c)此时中间三条线段的值合并之后,值被更新成为(a + e -(b + d - c) ),并且我们把(a + e -(b + d - c) )的值已经加入了小根堆,这个时候发现这个值最小,因为原来的答案中加入的是(b + d - c)还有一个c,如果再把这个(a + e -(b + d - c) )加上就会变成(a + e + c),就跟我们选择的三条线段一样了。
所以可以认为每次合并之后会成为一个新的线段,然后加入原来的小根堆中进行计算,而且对最终的计算结果没有影响。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL,int> PLI;
const int N = 100009;
LL d[N];
int l[N],r[N];

void erase_node(int x)
{
    l[r[x]] = l[x];
    r[l[x]] = r[x];
}
int main()
{
    int n,k;
    cin >> n >> k;
    for(int i = 0;i < n;i ++)
        scanf("%lld", &d[i]);
    for(int i = n - 1;i >= 1;-- i)
        d[i] -= d[i - 1];
    d[n] = d[0] = 1e15;
    
    set<PLI> S;
    
    for(int i = 0;i <= n;i ++)
    {
        if(i >= 1 && i < n)
        S.insert({d[i],i});
        l[i] = i - 1;
        r[i] = i + 1;
    }
    
    LL res = 0;
    for(int i = 1;i <= k;i ++)
    {
        auto t = S.begin();
        S.erase(t);
        LL v = t->first;
        int le = l[t->second],ri = r[t->second],p = t->second;
        S.erase({d[le],le});
        S.erase({d[ri],ri});
        erase_node(le);
        erase_node(ri);
        res += v;
        d[p] = d[le] + d[ri] - d[p];
        S.insert({d[p],p});
    }
    cout << res << endl;
    return 0;
}


生日礼物

这个题首先要转化一下,然后就会变成跟上一道题一样的思路。
但是怎么转化呢,可以这样看,把每一段连续的正数或者负数加起来,看成一个整体,因为是选连续的序列,所以能尽量选正数就选啊,能尽量不选负数就不选啊。
然后首先我们计算一下正数的区间假设有cnt个,如果cnt小于等于规定序列k,就都选上啊,反之我们就必须要考虑一下要不要合并一些区间。
然后就会出现两种情况:
1.不合并区间,也就是说要放弃掉一些区间(肯定挑最小的放弃啊)
2.合并区间,那么就要加上中间的那一个负数,相当于减去一个正数。
这个就有点像上面的了,假设有三条线段a,b,c。
如果我想选择c的话,就必须要加上a,并且还要减掉b。
所以这道题就可以变成,把线段的绝对值放进小根堆。每次我要用所有正数的和减掉一根线段,这根线段必须要小。而每次如果选择合并线段的话,就把这根线段的值加上左右两边的值(因为一定符号不一样,所以加就可以了),如果选择的这一段对应着负数,则代表是将左右两边的正数合并,否则就代表放弃掉一段正数。

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

using namespace std;

typedef pair<int, int> PII;

const int N = 100010;

int n, m;
int a[N], l[N], r[N];
bool st[N];//判断一条线段是否删去了

void remove(int p)
{
    // 从链表中删去

    l[r[p]] = l[p];
    r[l[p]] = r[p];

    // 从heap里删去
    st[p] = true;
}

int main()
{
    cin >> n >> m;

    int k = 1;
    for (int i = 0; i < n; i ++ )
    {
        int x;
        cin >> x;
        if ((long long)a[k] * x < 0) a[ ++ k] = x;//统计正数和负数区间
        else a[k] += x;
    }

    n = k;

    int cnt = 0, res = 0;
    for (int i = 1; i <= n; i ++ )
        if (a[i] > 0)
        {
            cnt ++ ;
            res += a[i];//累加正数的和
        }

    priority_queue<PII, vector<PII>, greater<PII>> heap;//

    for (int i = 1; i <= n; i ++ )
    {
        l[i] = i - 1;
        r[i] = i + 1;

        heap.push({abs(a[i]), i});
    }

    while (cnt > m)
    {
        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)//如果最左端或最右端的的数是负数,就可以不用考虑了。
        {
            cnt -- ;
            res -= v;

            int left = l[p], right = r[p];
            a[p] += a[left] + a[right];//重新更新线段的值

            heap.push({abs(a[p]), p});//记得是把绝对值放进去
            remove(left);//擦掉双向链表
            remove(right);
        }//
    }

    cout << res << endl;

    return 0;
}

荷马史诗

这是一个多杈的哈夫曼树,这个就是要把小的数尽量放在下面,但是可能元素个数不能被k整除,所以由于最优子策略,要尽量把上层填满,深度最深的数量不够的话可以用零来补充。
以及这道题还要求深度尽量小,所以合并如果有相同的数的时候要选择深度比较小的先合并。

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef pair<LL,int> PLI;
int n,k;
priority_queue<PLI,vector<PLI>,greater<PLI> >heap;


int main()
{
    cin >> n >> k;
    for(int i = 1;i <= n;i ++)
    {
        LL x;
        cin >> x;
        heap.push({x,0});
    }
    while( (n - 1) % (k - 1) ) heap.push({0,0}),n ++;
    
    LL res = 0;
    while(heap.size() > 1)
    {
        LL s = 0;
        int width = 0;
        for(int i = 1;i <= k;i ++)
        {
            auto t = heap.top();
            heap.pop();
            s += t.first;
            width = max(width,t.second);
            
        }
        heap.push({s,width + 1});
        res += s;
    }
    cout << res << endl << heap.top().second;
    return 0;
}

其实说着是二叉堆,二叉堆更多的时候是工具吧,实际上还是一些贪心的策略和算法。

总结与练习

城市游戏

单调栈:这个就是遍历每一行,把有F的地方看成这个的高度,上面被R截断的部分就不管了,每一排扫一遍,然后统计最大的就是了。

#include<bits/stdc++.h>
using namespace std;
const int N = 1009;
int a[N][N];
int w[N];
int main()
{
	int n,m;
	cin >> n >> m;
	char x;
	for(int i = 1;i <= n;i ++)
	{
		for(int j = 1;j <= m;j ++)
		{
			cin>>x;
			if(x =='F')
				a[i][j] = 1;
		}
	}
	long long ans = 0;
	for(int i = 1;i <= n;i ++)
	{
		stack<int >q;
		q.push(0);
		a[i][m + 1] = 0;
		long long res = 0;
		for(int j = 1;j <= m + 1;j ++)
		{
			if(a[i][j]) a[i][j] += a[i - 1][j];
			if(a[i][j] > q.top())
			{
				q.push(a[i][j]);
				w[q.size()] = 1;
			}
			else
			{
				int width = 0;
				while(q.top() > a[i][j])
				{
					width += w[q.size()];
					res =max(res,(long long)width * q.top());
					q.pop();
				}
				q.push(a[i][j]);
				w[q.size()] = width + 1;
			}
		}
		ans = max(ans,res);
	}
	cout<<3 * ans;
	return 0;
}
双栈排序

这个题好麻烦啊,又有一个玄学操作。
玄学操作:对于任意 i < j < k i < j < k i<j<k,如果 a [ k ] < a [ i ] < a [ j ] a[k] < a[i] < a[j] a[k]<a[i]<a[j],那么 i 和 j i 和 j ij就一定不在同一个栈里面。
然后利用这个性质可以把所有数分成两部分,把不矛盾的加入同一个栈,如果有重复的,那么就判定为失败。然后在进行第一个序列的优先选择。

#include<bits/stdc++.h>
using namespace std;
const int N = 1010;

int n,a[N],f[N];
int color[N];
bool g[N][N];
bool dfs(int u,int c)
{
    color[u] = c;//对点进行阵营染色
    for(int i = 1;i <= n;i ++)
    {
        if(g[u][i])//如果这两个点存在路径,即他们属于两个阵营
        {
            if(color[i] == c) return 0;//如果这个点已经被染过色并且属于同一阵营,那么也失败
            if(color[i] == -1 && !dfs(i,!c)) return 0;//如果这个点没有被染过色且染色失败
        }
    }
    return 1;
}
int main()
{
    
    cin >> n;
    for(int i = 1;i <= n;i ++) 
        cin >> a[i];
    f[n + 1] = n + 1;
    memset(g,0,sizeof(g));
    for(int i = n;i;i --)
        f[i] = min(f[i + 1],a[i]);//记录最小值方便之后比较
    // for(int i = 1;i <= n;i ++)
    //     cout<<f[i]<<" ";
    //     cout<<endl;
    for(int i = 1;i <= n - 1;i ++)
        for(int j = i + 1;j <= n;j ++)
        if(a[i] < a[j] && a[i] > f[j + 1])
            g[i][j] = g[j][i] = true;//建立路径,每条路径连接染色不同的两个点
    memset(color,-1,sizeof(color));//染色
    
    // for(int i = 1;i <= n;i ++)
    // {
    //     for(int j = 1;j <= n;j ++)
    //     cout<<g[i][j]<<" ";
    //     cout << endl;
    // }
    bool flag = true;
    for(int i = 1;i <= n;i ++)
    {
        if(color[i] == -1 && !dfs(i,0))//如果这个点没有被染色且染色失败说明不符合
        {
            flag = false;break;
        }
    }
    // for(int i = 1;i <= n;i ++)
    //     cout<< color[i] << " ";
    if(!flag)
    {
        cout << 0 << endl;
        return 0;
    }
    stack<int >s1,s2;
    
    int now = 1;
    for(int i = 1;i <= n;i ++)
    {
        if(color[i] == 0)
        {
            s1.push(a[i]);
            cout<<"a ";
        }
        else
        {
            s2.push(a[i]);
            cout<<"c ";
        }
        bool print = 1;
        while(print)//如果还有相等的就继续弹出,否则就进行输入操作
        {
            print = 0;
            if(s1.size() && s1.top() == now)
            {
                s1.pop();
                cout<<"b ";
                now ++;
                print = 1;
            }
            else if(s2.size() && s2.top() == now)
            {
                s2.pop();
                cout<<"d ";
                now ++;
                print = 1;
            }
        }
    }
    return 0;
}

滑动窗口

找两个单调队列就行了。

#include<bits/stdc++.h>
using namespace std;
int n,k;
const int N = 1000009;
deque<pair<int,int > >l_max,l_min;
int maxx[N],minn[N];
int main()
{
    cin >> n >> k;
    int x;
    l_max.push_back({INT_MAX,0});
    l_min.push_back({INT_MIN,0});
    for(int i = 1;i <= n;i ++)
    {
        cin >> x;
        while(l_max.size() && l_max.front().second < i - k + 1) l_max.pop_front();
        while(l_min.size() && l_min.front().second < i - k + 1) l_min.pop_front();
        while(x >= l_max.back().first && l_max.size()) l_max.pop_back();
        l_max.push_back({x,i});
        while(x <= l_min.back().first && l_min.size()) l_min.pop_back();
        l_min.push_back({x,i});
        if(i >= k)
        {
            
            minn[i] = l_min.front().first;
            maxx[i] = l_max.front().first;
        }
    }
    for(int i = k;i <= n;i ++)
        printf("%d ",minn[i]);
    cout<<endl;
    for(int i = k;i <= n;i ++)
        printf("%d ",maxx[i]);
    return 0;
}
内存分配

这是一道模拟题,有点复杂。反正就是模拟就行了

#include<bits/stdc++.h>
using namespace std;

typedef pair<int,int > PII;
queue<PII >wait;//first: 内存长度 second:占用时间 
set<PII>runs;//first :起始长度 second:内存长度 
priority_queue<PII,vector<PII>,greater<PII> > endts;//first:释放时间 second 起始长度 
int n,t,m,p;
int ttm,cnt;

bool give(int t,int m,int p)
{
	for(auto it = runs.begin();it != runs.end();it ++)
	{
		auto jt = it;
		jt ++;
		if(jt != runs.end())
		{
			if(m <= jt->first - it->first - it->second)
			{
				runs.insert({it->first + it->second,m});
				endts.push({t + p,it->first + it->second});
				return 1;
			}
		}
	}
	return 0;
}
void finish(int time)
{
	while(endts.size() && endts.top().first <= time)
	{
		int f = endts.top().first;
		while(endts.size() && endts.top().first == f)
		{
			
			int s = endts.top().second;
			endts.pop();
			auto it = runs.lower_bound({s,0});
			runs.erase(it);
		}
		ttm = f;
		while(wait.size())
		{
			if(give(ttm,wait.front().first,wait.front().second))
			{
				wait.pop();
			}
			else
			break;
		}
	}
	
}


int main()
{
	cin >> n;
	runs.insert({-1,1});
	runs.insert({n,1});
	while(cin >> t >> m >> p ,t || m || p)
	{
		finish(t);
		if(!give(t,m,p))
		{
			cnt ++;
			wait.push({m,p});
		}
	}
	finish(2e9);
	cout << ttm << endl << cnt;
	return 0;
}

 
矩形

这个很简单,我都能写出来的题一般都是入门级别
但是还是有一点就是,如果直接把每一个矩形算出来会有点超时。所以要特殊处理一下。
每次读入一行时把这一行的hash值算出来,然后依次枚举每一列包含了b列的区间。
然后一排一排的累加,当累加的行数大于a行时,就减去前面累加的(这里的数学需要推一下)。

//1.读入2.计算每个矩形并统计数量(计算时将每个矩形按照从左到右从上到下排列成一个数字hash)3计算每个给出的矩形,遍历找相似

#include<bits/stdc++.h>
using namespace std;
const int N = 1001;
typedef unsigned long long ULL;
ULL h[N][N],p[N * N];
ULL md;
int n,m,a,b;

ULL get(ULL f[],int l,int r)
{
    return f[r] - f[l - 1] * p[r - l + 1];
}
int main()
{
    cin>>n>>m>>a>>b;
    char str[N];
    p[0] = 1;
    int sum = 0;
    for(int i = 1;i <= n * m;i ++)
        p[i] = p[i - 1] * 131;
    for(int i = 1;i <= n;i ++)
    {
        scanf("%s",str);
        for(int j = 1;j <= m;j ++)
        {
            h[i][j] = h[i][j - 1] * 131 + str[j - 1] - '0';
        }    
    }
    
    
    int t;
    cin >> t;
    
    unordered_set<ULL>S;
    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];
            if(j >= a) S.insert(s);
            
        }
        
    }
    while(t --)
    {
        sum = 0;md = 0;
        for(int i = 1;i <= a;i ++)
        {
            scanf("%s",str);
            for(int j = 1;j <= b;j ++)
            {
                md =md * 131 + (str[j - 1] - '0');
            } 
        }

        if(S.count(md)) printf("1\n");
        else printf("0\n");
    }
}

树形地铁系统

先解释一下题意,有n组数据,每个数据给两个树的dfs序,0表示向下走,1表示向上走,问这两个树是不是同构的。

如何判断两个树是不是同构:求出每个树的最小表示。即与这棵树同构的最小的dfs序。如果两个树同构,那么这两个树的最小表示 应该相同。
树的最小表示可以递归实现,求出所有子树的dfs序,然后从小到大排序拼接起来

这个最小表示用01串来表示,每一次将子树的最小表示进行排序,然后连接起来,然后这样递归回去。

#include<bits/stdc++.h>
using namespace std;

string dfs(string &seq,int &u)
{
    vector<string>seqs;
    u ++;
    while(seq[u] == '0') seqs.push_back(dfs(seq,u));
    u ++;
    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";//加入一个0和1来表示进入第一个和退出第一个,设置边界。

        int ua = 0,ub = 0;
        if(dfs(a,ua) == dfs(b,ub)) puts("same");
        else puts("different");
    }
    return 0;
}
项链

最小表示法,挺简单的

#include<bits/stdc++.h>
using namespace std;
const int N = 1000009;
char s1[N],s2[N];
char b[N * 2];
int n,len;

void get(char*a)
{
    for(int i = 0;i <= len;i ++)
        b[i] = a[i % n];
    int i = 0,j = 1;
    while(i < n && j < n)
    {
        int k ;
        for(k = 0;k < n && b[i + k] == b[j + k];k ++);
        if(k == n - 1) break;
        if(b[i + k] > b[j + k]) 
        {
            
            i += k + 1;
            if(i == j)
            i ++;
        }
        else 
        {
            j += k + 1;
            if(j == i)
            j ++;
        }
    }
    int s = min(i,j);
    for(int k = 0;k < n;k ++)
        a[k] = b[k + s];
}
int main()
{
    cin >> s1 >> s2;
    
    n = strlen(s1);
    len = 2 * n - 1;
    
    get(s1);
    get(s2);
    for(int i = 0;i < n;i ++)
    {
        if(s1[i] != s2[i])
        {
            cout << "No"<<endl;
            return 0;
        }
    }
    cout<<"Yes"<<endl;
    cout<<s1;
    return 0;
}
奶牛矩阵

对于这个题,需要先暴力算一下矩形的宽度,就是每排每排的遍历,依次枚举宽度,然后找到最短的存在的宽度。
然后就处理每一列的值,求next数组,然后就可以求出最小周期了

#include<bits/stdc++.h>
using namespace std;
const int N = 10010,M = 80;
int n,m;
char str[N][M];
int ne[N];
bool st[M];//
int main()
{
    cin >> n >> m;
    memset(st,1,sizeof(st));
    
    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代表除了第一段以外向后枚举到的地方
                {
                    for(int u = 0;u < j && k + u < m;u ++)
                    if(str[i][u] != str[i][k + u])//u代表循环节的第u个字母,如果跟下一个应该循环的地方不匹配。
                    {
                        st[j] = 0;//就置为false
                        break;
                    }
                    if(!st[j]) break;//
                }
        }
    }
    int width;
    for(int i = 1;i <= m;i ++)
        if(st[i])
        {
            width = i;
            break;
        }
    
    for(int i = 1;i <= n;i ++) str[i][width] = 0;
    
    for(int i = 2,j = 0;i <= n;i ++)
    {
        while(j && strcmp(str[i] , str[j + 1])) j = ne[j];
        if(!strcmp(str[i] , str[j + 1])) j ++;
        ne[i] = j;
    }

    int height = n - ne[n];    
    // cout<<height << " " << width << endl;
    cout << width * height << endl;
    return 0;
}
匹配统计

这个题还是要用到kmp算法。
还是先算b的next数组,然后再和a匹配。
这里的f数组不是平常理解的f数组,它储存的是长度达到i的数量(所以最后输出时要减掉达到i - 1长度的个数)
但是对于kmp算法而言。
如果a以i为终点的前缀与b的匹配长度是j,那么可以很容易地知道,i - next【j】 + 1与b的前next【j】个字母也相同。依此类推。
所以我们可以得出结论:一个匹配长度为j的情况,一定包含着匹配长度为next【j】的情况
因此在求完f数组之后,还要倒序遍历一遍b的长度,来累加那些存在着包含的情况

#include<bits/stdc++.h>
using namespace std;
const int N = 200009;
char a[N],b[N],temp[N * 2];
int ne[N],f[N];
int na,nb,t;
void get_next()
{
    ne[1] = 0;
    for(int i = 2,j = 0;i <= nb;i ++)
    {
        while(j && b[i] != b[j + 1]) j = ne[j];
        if(b[i] == b[j + 1]) j ++;
        ne[i] = j;
    }
}

void get_f()
{
    for(int i = 1,j = 0;i <= na;i ++)
    {
        while(j && (j == nb || a[i] != b[j + 1])) j = ne[j];
        if(a[i] == b[j + 1]) j ++;
        f[j] ++;
    }
}
int main()
{
    cin >> na >> nb >> t;
    scanf("%s%s",a + 1,b + 1);
    get_next();
    get_f();
    for(int i = nb;i > 0;--i) f[ne[i]] += f[i];
    for(int i = 1;i <= t;i ++)
    {
        int x;
        scanf("%d",&x);
        if(x > nb) printf("0\n");
        else
        printf("%d\n",f[x] - f[x + 1]);
    }
    return 0;
}
电话列表

很简单的一道题。

#include<bits/stdc++.h>
using namespace std;
const int N = 100109;
int tr[N][10],id;
int n,x;
int T;
string s[10010];
bool get_in(string a)
{
    bool flag = 0;
    int len = a.size();
    // cout << len <<endl;
    int p = 0;
    for(int i = 0;i < len;i ++)
    {
        if(!tr[p][a[i] - '0'])
        {
            flag = 1;
            tr[p][a[i] - '0'] = ++ id;
        }
        p = tr[p][a[i] - '0'];
    }
    return flag;
}
bool cmp(string a,string b)
{
    return a.size() > b.size();
}
int main()
{
    cin >> T;
    while(T --)
    {
        memset(tr,0,sizeof(tr));
        id = 0;
        bool flag = 1;
        cin >> n;
        for(int i = 0;i < n;i ++)
            cin >> s[i];
        sort(s,s + n,cmp);
        for(int i = 0;i < n;i ++)
        {
            if(!get_in(s[i]))
            {
                flag = 0;
                break;
            }
        }
        if(flag)
            cout << "YES" <<endl;
        else
            cout << "NO" <<endl;
    }
    return 0;
}
黑盒子

对顶堆,还是挺容易的。

#include<bits/stdc++.h>
using namespace std;
const int N = 30009;
priority_queue<int ,vector<int>,greater<int > >s2;
priority_queue<int,vector<int>,less<int > >s1;
int n,m;
int a[N],ques[N];
int main()
{
    cin >>n >> m;
    for(int i = 0;i < n;i ++)
        scanf("%d",&a[i]);
    for(int i = 0;i < m;i ++)
    {
        scanf("%d",&ques[i]);
    }
    sort(ques,ques + m);
    int i = 0,j = 0;
    while( j < m)
    {
        while(j < m && ques[j] == i)
        {
            // cout << j << "yes" <<endl;
            cout << s2.top() << endl;
            s1.push(s2.top());
            s2.pop();
            j ++;
        }
        if(i < n)
        {
            int x = a[i];
            if(s1.empty() || x >= s2.top())
            {
                s2.push(x);
            }
            else
            {
                s1.push(x);
                s2.push(s1.top());
                s1.pop();
            }
            i ++;
        }
    }
    return 0;
}

终于写完了(吐血)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值