栈
编辑器
题意
给你n个指令,I x表示从光标处插入一个数x,D是删除光标前的一个数,L是光标左移,R是光标右移,Q k是询问,要求返回 光标前 的所有数里 前k 个前缀和的 最大值
比如
思路
用两个栈来维护,这两个栈对顶着放,光标前的为左栈,光标后的为右栈(大概现这样理解,其实删除操作会破坏这种规定如果是用数组当成栈)这里用两个数组来充当堆栈,同样处理为了处理前k 个前缀和的 最大值,我们也开一个 堆栈维护(f[k]表示 前k 个前缀的最大值就是s1,s2…sk的最大值),插入操作 就是 在第一个栈顶插入,然后对于求最大值的堆栈,就是 这个堆栈的本来的最后一个数,和前缀和Sn 比较 n 是现在的左栈个数,去最大值,然后删除 就直接删除左栈栈顶 ,我们这里用数组右边界 减减就可以,然后 光标左移 就是把 左栈的栈顶给右栈栈顶,右移就是把右栈栈顶 放到左栈去 但别往了维护求前k 个前缀和 最大值的栈
代码
#include<iostream>
#include<algorithm>
#include<limits.h>
using namespace std;
const int N=1000010;
int stkl[N],stkr[N],tl,tr;
int s[N],f[N];
void push_stkl(int x)
{
stkl[++tl]=x;
s[tl]=s[tl-1]+x;
f[tl]=max(f[tl-1],s[tl-1]+x);
}
int main()
{
f[0]=INT_MIN;
int n;
cin>>n;
while(n--)
{
char str[2];
scanf("%s",str);
if(*str=='I')
{
int x;
cin>>x;
push_stkl(x);
}
else if(*str=='D')
{
if(tl>0)tl--;
}
else if(*str=='L')
{
if(tl>0) stkr[++tr]=stkl[tl--];
}
else if(*str=='R')
{
if(tr>0) push_stkl(stkr[tr--]);
}
else
{
int x;
cin>>x;
cout<<f[x]<<endl;
}
}
return 0;
}
火车进栈
题意
给你n 个数1 2 3 4 5 … n模拟进栈 只能是 小数优先 模拟 所有出栈的序列 出20个 按照字典序 出
比如 n=3
输出
123
132
213
231
321
思路
n 的范围很小 最大20.所以dfs,设置三个状态 ,一个是 已经出栈的,一个是 还在堆栈里的,一个是还未入栈的,下一步只能进行两种操作,一种是把状态2也就是还没有出栈的放到状态1让他们出栈,还有一种就是把状态3还没有进栈的放到状态2 让他们进栈,然后由于输出要按字典序 也就是先输出的数就最小的,因为 小的数必须先进栈所以 状态2 的数一定是比状态3 的数小的,因此我们先执行 操作1 ,再执行操作2,这样就可以保证是按字典序输出,把状态1 设置成vector ,2是堆栈,3 其实就是一个数
代码
#include<iostream>
#include<stack>
#include<vector>
using namespace std;
vector<int> state1;
stack<int> state2;
int state3=1,cnt=20;
int n;
void dfs()
{
if(!cnt) return;
if(state1.size()==n)
{
for(int i=0;i<state1.size();i++) cout<<state1[i];
cout<<endl;
cnt--;
return;
}
if(state2.size())
{
state1.push_back(state2.top());
state2.pop();
dfs();
state2.push(state1.back());
state1.pop_back();
}
if(state3<=n)
{
state2.push(state3);
state3++;
dfs();
state2.pop();
state3--;
}
}
int main()
{
cin>>n;
dfs();
return 0;
}
火车进出栈
题意
和上一题题意一样就是问的不同 而且n 比之前大了很多,问 出栈的方案数为多少
思路
用+表示进栈用-表示出栈 我们发现所有 前缀 +号的数量>= -号数量的方案都是合法的,那么 总的为C2n n (组合数)减去不合法的就是合法的
所以要求不合法的,
我们画个图
+往后走 -往上走,可以发现 所有合法的都是在y=x 的曲线 点可以在线上但是绝对不会超过线,且终点一定是n,n 因为n 个减号 n 个加号吗 ,然后我们还能发现 所有 不合法的路线肯定会经过直线 y=x+1,最后终点也是到达(n,n),从第一个经过红线的点 把这条直线 根据y=x+1 对称过去 可以发现终点肯定对称到了(n-1,n+1),所以 不合格的就转换成了 所有 从起点 到n-1,n+1的路线数,数量为 c2n (n-1)组合数
所以答案 就是 c2n(n)-C2n(n-1)也就是C2n(n)/n+1
直方图中的最大矩形
题意
给你n 个宽为1的纸条,但是高度 不一样 给出n 高度,都并排再一起,让你求组成的最大面积的矩形的面积
如图
思路
枚举每一个矩形,对于每一个矩形他的高就是他的高度,但是他的宽度不一定,他的宽度取决于他左边第一个比他小的矩形以及他右边第一个比他小的矩形的位置,我们称之为左边界和右边界,知道了这两个量 每个矩形所能组成的最大面积就是 hi*(r-l-1)所以我们只需要枚举更新最大值就行,问题是怎么求每个矩形的左边界和右边界
这里我们用到了单调栈
就是每次放进去下标之前,先判断当前栈顶的数是不是大于当前下标所对应的矩形的高度,如果大于等于 我们就往下找,一直找到小于的下标,这就是当前矩形的边界,同时,我们要把这个矩形的下标放进栈里,同时我们要删除之前判断的高度大于他的,因为这些已经没用了,因为我们把当前操作的放了进去 因为他 比他前面的某些矩形要矮,那么下一个矩形找的时候如果他都不行了他前面那些比他高 的肯定也不行 所以直接删了
重复这些操作 求左边界和右边界
代码
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=100010;
typedef long long LL;
int h[N],l[N],r[N],q[N];
int n;
void getleft()
{
int tt=0;
h[0]=-1;//这样就不用处理边界问题
for(int i=1;i<=n;i++)
{
while(h[q[tt]]>=h[i]) tt--;
l[i]=q[tt];
q[++tt]=i;//q[tt]是记录数组下标
}
}
void getright()
{
int tt=0;
h[n+1]=-1;
q[tt]=n+1;
for(int i=n;i>=1;i--)
{
while(h[q[tt]]>=h[i]) tt--;
r[i]=q[tt];
q[++tt]=i;
}
}
int main()
{
while(cin>>n,n)
{
memset(l,0,sizeof l);
memset(r,0,sizeof r);
memset(q,0,sizeof q);
for(int i=1;i<=n;i++)
cin>>h[i];
getleft();
getright();
LL res=0;
for(int i=1;i<=n;i++)
res=max(res,(r[i]-l[i]-1ll)*h[i]) ;
cout<<res<<endl;
}
return 0;
}
小组队列
题意
思路
每一组人都分别用一个队列维护,组间关系用一个 队列来维护 存放的是小组的组号,
放人对队列的维护:
找出放进去人的 组号 然后看这个组号对应的是不是空的如果是空的说明没有把这个小组排队 就得把 小组的组好放进组间关系的队列,然后再把这个数压入该小组的队列,如果不是空的就直接把这个数压入该小组的队列
删人对队列的维护
求出组间关系队列的队首 先就是站在最前面的小组,然后输出该小组的队首然后pop掉,然后判断以下这个小组是否空了,如果空了就把组间关系的对首也pop掉,
这里还涉及到了一个技巧 就是求每个人 所在的id
输号的时候 就
teamid【x】=i;
代码
#include<iostream>
#include<algorithm>
#include<queue>
#include<string>
using namespace std;
const int N=1010,M=1000010;
int teamid[M];
int main()
{
int n;
int C=1;
while(cin>>n,n)
{
printf("Scenario #%d\n",C++);
for(int i=0;i<n;i++)
{
int cnt;
cin>>cnt;
while(cnt--)
{
int x;
cin>>x;
teamid[x]=i;
}
}
queue<int>team;
queue<int>p[N];
string comand;
while(cin>>comand,comand!="STOP")
{
if(comand=="ENQUEUE")
{
int x;
cin>>x;
int tid=teamid[x];
if(p[tid].empty()) team.push(tid);
p[tid].push(x);
}
else
{
int tid=team.front();
cout<<p[tid].front()<<endl;
p[tid].pop();
if(p[tid].empty()) team.pop();
}
}
cout<<endl;
}
return 0;
}
hash
字符串哈希定义
大概就理解成把一串小写字母看成一串数字 a-z对应1到26 把他们看成一个 p 进制数 p 一般看成是 131,然后他们转换成10进制,再模上q,q 一般是 2的64次方,所一把哈希值设置成 unsigned longlong 就可以不用模了,然后求字符串的前缀哈希值 比如字符转abd ,h[1]就是a的哈希值,h[2]就是a,b 的哈希值,h【3】就是abd 的哈希值,不难看出 h【i+1】=h[i]*131(全都往上提一位)+str[i+1]-‘a’+1;
然后求某一段的哈希值时 就是 h[l到r]=h[r]-h[l-1]*131的(r-l+1)次方【这里我们用p[r-l+1]表示】
代码
#include <iostream>
#include <cstring>
using namespace std;
const int N=100010,base=131;
typedef unsigned long long ULL;
ULL h[N],p[N];
char s[N];
int main()
{
scanf("%s",s+1);
int n=strlen(s+1);
p[0]=1;
for(int i=1;i<=n;i++)
{
h[i]=h[i-1]*base+s[i]-'a'+1;
p[i]=p[i-1]*base;
}
for(int i=1;i<=n;i++)
cout<<h[i]<<endl;
return 0;
}
☆ 最长回文子串
题意
给一串字符串 求最长的回文子串的长度
思路
正着求一遍哈哈希值,然后再倒着求一遍哈希值,然后分别以每个字母为中心 二分半径 求满足要求的最大值,这里有一些技巧
第一就是 二分半径
然后 把所有回文都变成奇数个数的回文,这样就方便枚举中点,把回文空里都插上一个数 还是回文,而且不论原来是奇数还是偶数长度都会变成奇数,但是更新答案的时候不要忘了还原 还原也是有技巧的,代码里涉及了
代码
#include <iostream>
#include <string>
#include <algorithm>
#include <cstring>
using namespace std;
typedef unsigned long long ULL;
const int N=2000010,base=131;
ULL hl[N],hr[N],p[N];
char s[N];
int n;
ULL get(ULL h[],int l,int r)
{
return h[r]-h[l-1]*p[r-l+1];
}
int main()
{
int num=0;
while(scanf("%s",s+1),strcmp(s+1,"END"))
{
int n=strlen(s+1);
p[0]=1;
for(int i=2*n;i>0;i-=2)
{
s[i]=s[i/2];
s[i-1]='z'+1;
}//cha shu
n*=2;
for(int i=1,j=n;i<=n;i++,j--)
{
p[i]=p[i-1]*base;
hl[i]=hl[i-1]*base+s[i]-'a'+1;
hr[i]=hr[i-1]*base+s[j]-'a'+1;
}
int res=0;
for(int i=1;i<=n;i++)
{
int l=0,r=min(i-1,n-i);
while(l<r)
{
int mid=l+r+1>>1;
if(get(hl,i-mid,i-1)!=get(hr,n-(i+ mid)+1,n-(i+1)+1)) r=mid-1;
else
l=mid;
}
if(s[i-l]<='z') res=max(res,l+1);
else
res=max(res,l);//gengxin 最大值时要去掉插的数,分两种情况a#b#a he #a#b#a#一种是边界是插的数一种边界不是差的数
}
printf("Case %d: %d\n",++num,res);
}
return 0;
}
trie
前缀统计
题意
先输入n 个字符串 然后再输入m 次,每次输一个字符串,然后判断 之前输入的n 个字符串 有多少是 这个字符串的前缀子串
思路
用字典树,将输入的单词变成字典树 ,然后找就行了,存的时候是每个字符串 每个字符串的存,从上往下
如图
代码
#include <iostream>
#include <cstring>
using namespace std;
const int N=1000010,M=500000;
char str[N];
int son[M][26],cnt[N],idx;
int n,m;
void insert()
{
int p=0;
for(int i=0;str[i];i++)
{
int &s=son[p][str[i]-'a'];
if(!s) s=++idx;
p=s;
}
cnt[p]++;//记录已改点为节点的个数
}
int query()
{
int p=0;
int res=0;
for(int i=0;str[i];i++)
{
int &s=son[p][str[i]-'a'];
if(!s) break;//肯定不是子串了直接退出
p=s;
res+=cnt[p];
}
return res;
}
int main()
{
cin>>n>>m;
while(n--)
{
scanf("%s",str);
insert();
}
while(m--)
{
scanf("%s",str);
cout<<query()<<endl;
}
return 0;
}
最大异或和
题意
给n 个数,然后问 随便取两个不相等的数 最大的异或和是多少 ,即问 max(ai^aj)
思路
数据是10的五次放所以不能直接暴力,要对其进行优化,如果对于每一个数ai 我们都能找到其对应的 aj 使他们俩的异或和最大我们就完成了优化,就遍历一遍 找到异或后的最大值就行,关键在于如何找到
每个ai 对应的 aj 因为是异或吗 异或就是 相对应的二进制位数不相同就是1
相同就是0,我们就可以 把所有数的二进制 放到trie 里,然后读数的时候每次都往不同的分支读,如图
代码
#include <iostream>
#include <algorithm>
using namespace std;
const int N=100010,M=3000000;
int son[M][2],idx;
int a[N];
void insert(int x)
{
int p=0;//起到指针作用
for(int i=30;i>=0;i--)
{
int &s=son[p][x>>i&1];
if(!s) s=++idx;//如果没有 就建立一个节点
p=s;//再指向最新的节点
}
}
int query(int x)
{
int res=0,p=0;
for(int i=30;i>=0;i--)
{
int s=x>>i&1;
if(son[p][!s])//如果与他相反的那一位分支存在
{
res+=1<<i;//那么这一位异或的结果肯定是1
p=son[p][!s];
}
else
p=son[p][s];//mei有只能往下 结果异或的这一位是0 不用写了
}
return res;
}
int main()
{
int n;
int res=0;
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>a[i];
insert(a[i]);
}
for(int i=1;i<=n;i++)
{
res=max(res,query(a[i]));
}
cout<<res<<endl;
return 0;
}
二叉堆
超市
题意
给你n 种商品,然后每个商品给你两种属性,一种是利润 一种是日期,p 和d d的意思是该商品必须在前d 天卖出,d 天后 该商品便无法出售,每天只能出售一个商品,问获得的最佳利润
思路
因为,商品的利润全是正的,所以我们当然想卖的商品越多越好,所以我们当然会想着出售日期短的商品 ,所以我们先把所有商品按照日期排序,然后假设我们 要出售 第i 个商品时,无非有两种情况 假设我们已经把第i 个商品给卖了 ,我们需要判断 已经卖了的商品数是否小于等于第i 个商品的日期,如果是 那么我们就可以卖,如果卖的商品数>第i 个商品的日期,(如果大于的话 肯定是大1,因为我们是一个商品一个商品卖,并且按照日期排好序的)那么我们就删除一个已经卖了的 利润最小的那个,就行
所以用到了小根堆
代码
#include <iostream>
#include <queue>
#include <vector>
#include <algorithm>
using namespace std;
const int N=10010;
priority_queue<int,vector<int>,greater<int>> heap;
struct node
{
int p,d;
}a[N];
bool cmp(node x,node y)
{
return x.d<y.d;
}
int main()
{
int n;
while(cin>>n)
{
for(int i=1;i<=n;i++)
{
cin>>a[i].p>>a[i].d;
}
sort(a+1,a+1+n,cmp);
for(int i=1;i<=n;i++)
{
heap.push(a[i].p);
if(heap.size()>a[i].d) heap.pop();
}
int res=0;
while(heap.size())
{
res+=heap.top();
heap.pop();
}
cout<<res<<endl;
}
return 0;
}