二叉堆
超市
选择是否卖出一件商品时,要考虑时间和价钱,如果时间合适,那么可以直接加入,但是如果时间不合适,我们就需要判断,在这之前选择的东西,有没有哪一种商品的价格比当前的价格低,如果有,就选出最少的那一个替换掉,否则就不选择这一个商品。
首先按照时间顺序递增排序,这样可以使得容易过期的被尽量选到,然后按照上面的思路,把已经选择的用一个小根堆维护。
#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}
di−1和di+1这两条不能选了。但是我们通过神奇的操作发现如果要选两条的话,需要选到
d
i
−
1
d_{i - 1}
di−1这条线段,那么第二条就一定会选到
d
i
+
1
d_{i + 1}
di+1。
为什么呢,因为如果第二条不是
d
i
+
1
d_{i + 1}
di+1的话,就是另外一条跟
d
i
−
1
d_{i - 1}
di−1和
d
i
d_i
di无关的线段m,既然是无关,那么也可以同时选择
d
i
d_i
di和m,因为在选择第一条线段的时候很明显
d
i
d_i
di的值小于
d
i
−
1
d_{i - 1}
di−1,所以这样的话还不如选择
d
i
d_i
di和m。
后面的情况也可以照此推导。
神奇的操作:但是我们怎么判断到底是选
d
i
d_i
di和另外一条线段还是选择
d
i
−
1
和
d
i
+
1
d_{i - 1}和d_{i + 1}
di−1和di+1。推导一下可以很容易的发现,只要
d
i
−
1
和
d
i
+
1
d_{i - 1}和d_{i + 1}
di−1和di+1的值加起来小于
d
i
d_i
di和另外一条线段(最小的那条),也就是
d
i
−
1
+
d
i
+
1
−
d
i
<
m
d_{i - 1} + d_{i + 1} -d_i < m
di−1+di+1−di<m就行了。
因为要找最小的,所以用一个小根堆来存所有的线段长度
所以我们每次选择了一条线段的时候,就把它左右两边的线段移除, 但是为了下次选择,我们要向小根堆加入
d
i
−
1
+
d
i
+
1
−
d
i
d_{i - 1} + d_{i + 1} -d_i
di−1+di+1−di 并且把原来
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
i和j就一定不在同一个栈里面。
然后利用这个性质可以把所有数分成两部分,把不矛盾的加入同一个栈,如果有重复的,那么就判定为失败。然后在进行第一个序列的优先选择。
#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;
}
终于写完了(吐血)。