火车进站
实际上就是一个卡特兰数,但是总算知道了公式是怎么推导出来的了。
卡特兰数以前知道的公式好像是递推推出来的:
f
(
n
)
=
f
(
1
)
∗
f
(
n
−
1
)
+
f
(
2
)
∗
f
(
n
−
2
)
.
.
.
+
f
(
n
−
2
)
∗
f
(
2
)
+
f
(
n
−
1
)
∗
f
(
1
)
f(n) =f(1)*f(n-1)+f(2)*f(n-2)...+f(n-2) * f(2) + f(n-1) * f(1)
f(n)=f(1)∗f(n−1)+f(2)∗f(n−2)...+f(n−2)∗f(2)+f(n−1)∗f(1)
然后它可以用来计算下类问题:
针对每个步骤有两种状态,且有一种状态的数量始终大于等于另一种,且最终两种状态数量相等。
然后比较常用的公式是:
蚯蚓
题目链接
解析:
用 Δ 来储存偏移量(长度增加量),队列中储存相对长度。每次挑选出最长的一段,加上Δ,切割完以后,就把切割后的长度减去一个Δ+q后放入队列中,Δ+=q(因为切割之后的不会增加长度,相对其它的蚯蚓减少了q)。
但是这样会超时。
先来一段证明:
设x1 > x2
x1切割后
x
3
x_3
x3=
⌊
x
1
∗
p
⌋
\lfloor x _1 *p \rfloor
⌊x1∗p⌋;
x
4
x_4
x4=
x
1
x_1
x1-
⌊
x
1
∗
p
⌋
\lfloor x _1 *p \rfloor
⌊x1∗p⌋;
再次切割若切割x2:
x
3
x_3
x3=
⌊
x
1
∗
p
⌋
\lfloor x _1 *p \rfloor
⌊x1∗p⌋+
q
q
q;
x
4
x_4
x4=
x
1
x_1
x1-
⌊
x
1
∗
p
⌋
\lfloor x _1 *p \rfloor
⌊x1∗p⌋+
q
q
q;
x
5
x_5
x5=
⌊
(
x
2
+
q
)
∗
p
⌋
\lfloor (x _2+q) *p \rfloor
⌊(x2+q)∗p⌋;
x
6
x_6
x6=
x
2
x_2
x2-
⌊
(
x
2
+
\lfloor (x _2+
⌊(x2+q
)
∗
p
⌋
) *p \rfloor
)∗p⌋;
x
3
x_3
x3=
⌊
x
1
∗
p
+
q
⌋
\lfloor x _1 *p +q\rfloor
⌊x1∗p+q⌋
证明:若
x
3
x_3
x3>
x
5
x_5
x5:
x
1
∗
p
+
q
>
x
2
∗
p
+
q
∗
p
x_1*p+q>x_2*p+q*p
x1∗p+q>x2∗p+q∗p
p
∗
q
<
q
p*q<q
p∗q<q
所以成立。
x 4 > x 6 x_4>x_6 x4>x6的证明过程省略。
所以把最开始的长度从大到小排好序,每条蚯蚓切割之后的长度也是分别递减。
所以开三个队列,一个储存原长,一个存
⌊
p
x
⌋
\lfloor px\rfloor
⌊px⌋另一个存
x
−
⌊
p
x
⌋
x-\lfloor px\rfloor
x−⌊px⌋。
代码:
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int n,m,q,u,v,t;
double p;
int a[100010];
queue<int >h,h1,h2;
int delta;
int get_max()
{
int len = INT_MIN;
int flag;
if(!h.empty() && h.front() > len ) flag = 0,len = h.front();
if(!h1.empty() && h1.front() > len ) flag = 1,len = h1.front();
if(!h2.empty() && h2.front() > len ) flag = 2,len = h2.front();
if(flag == 0) h.pop();
if(flag == 1) h1.pop();
if(flag == 2) h2.pop();
return len;
}
int main()
{
cin>>n>>m>>q>>u>>v>>t;
p = (u * 1.0) / v;
for(int i = 1;i <= n;i ++)
scanf("%d",&a[i]);
sort(a + 1,a + n + 1);
for(int i = n;i >= 1;-- i)
h.push(a[i]);
for(int i = 1;i <= m;i ++)
{
LL len = get_max();
len += delta;
if( i % t == 0) printf("%lld ",len);
int px = len * p;
delta += q;
int hh1 = px - delta;
int hh2 = len - px - delta;
h1.push(hh1);
h2.push(hh2);
}
puts(" ");
for(int i = 1;i <= n + m;i ++)
{
LL len = get_max() + delta;
if(i % t == 0) printf("%lld ",len);
}
puts(" ");
}
直方图中最大矩形
单调栈问题
解析:找一个栈存一个单调递增序列。
当遇到一个数比栈顶元素大,直接加入,否则弹出栈顶,直到栈顶小于当前元素为止。弹出的同时记录宽度,同时更新答案(或者从左边扫描一遍,再从右边扫描一遍,计算对于每个矩形而言左右两边能够扩展的最大长度,然后再计算)
#include<bits/stdc++.h>
using namespace std;
const int N=100010;
typedef long long LL;
typedef pair<LL,int > PII;
LL a[N];
int rt[N],lt[N];
int n;
int main()
{
while(cin >> n && n)
{
stack<PII > r,l;
r.push(make_pair(-1,0));
l.push(make_pair(-1,n + 1));
for(int i = 1;i <= n;i ++)
{
scanf("%lld",&a[i]);
while(a[i] <= r.top().first)
r.pop();
rt[i] = r.top().second;
r.push(make_pair(a[i],i));
}
for(int i = n;i >= 1;i --)
{
while(a[i] <= l.top().first)
l.pop();
lt[i] = l.top().second;
l.push(make_pair(a[i],i));
}
long long ans=0;
for(int i = 1;i <= n; i ++)
{
ans = max(ans,(LL)( lt[i] - rt[i] - 1 ) * a[i]);
}
cout << ans << endl;
}
return 0;
}
小组队列
题目
开一些队列来储存此时每个小组对应的人,再来一个储存目前还有哪些小组。
#include<bits/stdc++.h>
using namespace std;
const int N=1000009;
int belong[N];
int cnt,n;
queue<int >a[1010];
queue<int >group;
bool g[1010];
int main()
{
while(cin>>n && n)
{
for(int i = 1;i <= n;i ++)
while(!a[i].empty()) a[i].pop();
while(!group.empty()) group.pop();
cnt++;
printf("Scenario #%d\n",cnt);
for(int i = 1;i <= n;++ i)
{
int num,x;
cin>>num;
for(int j = 1;j <= num;++ j)
scanf("%d",&x),belong[x] = i;
}
string s;
int x;
while(cin>>s && s!= "STOP")
{
if(s == "ENQUEUE")
{
scanf("%d",&x);
int number = belong[x];
if(a[number].empty()) group.push(number);
a[number].push(x);
}
else
{
int number=group.front();
printf("%d\n",a[number].front());
a[number].pop();
if(a[number].empty())
group.pop();
}
}
printf("\n");
}
return 0;
}
最大子序和
题目
单调队列。
找一个双端队列储存递增的前缀和(每一段子序和用前缀和相减)。
如果下一个元素小于当前队列队尾,就一直弹出队尾元素(因为对于之后的计算而言,在前面的元素如果大于后面的元素,它的竞争力就一定不如后面新加进来的元素),然后把这个元素加进去,如果队头元素的位置与现在所在位置的距离超过了m - 1.就弹出队头元素。
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N=300009;
int q[N];
LL sum[N];
int ans=INT_MIN;
int n,m;
int main()
{
scanf("%d%d",&n,&m);
for(int i = 1;i <= n;i ++)
{
scanf("%lld",&sum[i]);
sum[i] += sum[i - 1];
}
long long res = INT_MIN;
int hh = 0,tt = 0;
for(int i = 1;i <= n;i ++)
{
if(i - q[hh] > m) hh++;
res = max(res,sum[i] - sum[q[hh]]);
while(hh <= tt && sum[q[tt]] >= sum[i]) tt--;
q[++ tt] = i;
}
cout<<res;
return 0;
}
双端队列
题目
这道题倒推一下,先把所有数连同它们的序号一起按照元素大小递增排序。
然后由于利用双端队列,所以在每一个双端队列中,因为元素的序号,里面的元素的序号一定是一个单谷函数(先递减再递增)
因为要求最少的双端队列,所以我们就要要求双端队列个数最少。
然后处理方法看代码
#include<bits/stdc++.h>
using namespace std;
const int N=200010;
pair<int ,int > a[N];
int main()
{
int n;
scanf("%d",&n);
for(int i = 1;i <= n;i ++)
scanf("%d",&a[i].first),a[i].second = i;//读入数据时pair储存大小和序号
sort(a + 1,a + n + 1);//排序
int last = INT_MAX,flag=-1;//last用来表示循环时上一个数的值,flag表示之前是递增的还是递减的
int ans = 1;
for(int i = 1;i <= n;)
{
int j = i;
int v = a[i].first;//j表示从i到j的区间元素大小相同,v表示元素大小
while(a[j + 1].first == v && j + 1 <= n) j ++;//计算j
int p_max = a[j].second, p_min = a[i].second;//找到i到j中序号最大和最小的
if(flag == -1)//如果上一次是递减
{
if(p_max < last) last = p_min;//如果i到j的最大值都小于last,那么整个这一段都可以放进这个递减序列
else last = p_max, flag = 1;//否则就置为递增序列
}//
else
{
if(p_min > last) last = p_max;//如果最小值都比last大,那么整个这一段就都可以放进递增序列
else last = p_min,ans ++,flag = -1;//否则重新开一段,变成递减序列
}//这里也是差不多的
i = j + 1;//更新i
}
cout<<ans;
return 0;
}