A. 奶牛问题
【题目描述】
小 A 有 n n n 头奶牛沿着一维的栅栏吃草,第 i i i 头奶牛在目标点 x i x_i xi,它的身高是 h i h_i hi。
当一头奶牛左边 D D D 距离内且右边 D D D 距离内均有身高至少是它的两倍的奶牛,它就会觉得拥挤。
请计算觉得拥挤的奶牛的数量。
【输入格式】
第一行两个整数 N , D N,D N,D。
接下来 N N N行,每行两个整数 x i , h i x_i,h_i xi,hi。
【输出格式】
一行一个整数,表示觉得拥挤的奶牛的数量。
【数据范围与提示】
对于 100 % 100\% 100% 的数据, 1 ≤ N ≤ 1 0 6 1 \leq N \leq 10^6 1≤N≤106, 1 ≤ x i , h i ≤ 1 0 9 1 \leq x_i,h_i \leq 10^9 1≤xi,hi≤109, 1 ≤ D ≤ 1 0 9 1 \leq D \leq 10^9 1≤D≤109,保证 x i x_i xi 互不相同。
想法
(为数不多的模板题),跑两边单调队列,判断
D
D
D 内有没有大于两倍
x
x
x 点(具体看代码了)
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node{
int x,h;
}dis[1000001];
int q[1000001],l,r,n,d,ans;
bool vis1[1000001],vis2[1000001];
bool cmp(node a,node b){
return a.x<b.x;
}
int main(){
//freopen("cow.in","r",stdin);
//freopen("cow.out","w",stdout);
scanf("%d%d",&n,&d);
memset(vis1,0,sizeof(vis1));
memset(vis2,0,sizeof(vis2));
for(int i=1;i<=n;++i)
scanf("%d%d",&dis[i].x,&dis[i].h);
sort(dis+1,dis+1+n,cmp);
l=1,r=0;
for(int i=1;i<=n;++i){
while(r>=l&&dis[q[r]].h<dis[i].h) r-=1;
q[++r]=i;
while(dis[i].x-dis[q[l]].x>d) l+=1;
if(dis[q[l]].h>=dis[i].h*2) vis1[i]=1;
}
l=n,r=n+1;
for(int i=n;i>=1;--i){
while(l>=r&&dis[q[r]].h<dis[i].h) r+=1;
q[--r]=i;
while(dis[q[l]].x-dis[i].x>d) l-=1;
if(dis[q[l]].h>=dis[i].h*2) vis2[i]=1;
}
for(int i=1;i<=n;++i)
if(vis1[i]&&vis2[i]) ans+=1;
printf("%d\n",ans);
return 0;
}
B. 打保龄球
【题目描述】
小 B 有 n n n 个排成一排的球瓶,每个球瓶上面有一个数字,表示击中它的得分。
小 B 还有 k k k 个保龄球,每个球可以击中 w w w 个球瓶宽度的区域。
有的球瓶的数字为负数,可以利用“被击倒的球瓶留下的空白位置”或者“原先球瓶左边或右边本来的空白位置”去尽可能地避免这些负分球。
小 B 想要知道这 k k k 个保龄球能打出的最大得分是多少。
【输入格式】
输入有多组测试数据。
第一行 t t t 表示测试数据的组数。
每个测试数据第一行 3 3 3 个整数 n , k , w n,k,w n,k,w,分别表示球瓶数量、保龄球的数量、保龄球的击打宽度。
接下来一行 n n n 个整数 a 1 , a 2 , … , a n a_1,a_2,…,a_n a1,a2,…,an,按顺序表示对应球瓶的分值。
【输出格式】
每组测试数据一行一个整数,表示最大得分。
想法
可以设数组
d
p
[
i
]
[
k
]
[
0
/
1
]
dp[i][k][0/1]
dp[i][k][0/1],表示当前击打了
k
k
k 个球,末尾为
i
i
i时,是否向前击打过。
为0时:直接从上一层继承过来,即
d
p
[
i
]
[
k
]
[
0
]
=
m
a
x
(
d
p
[
i
−
1
]
[
k
]
[
0
]
,
d
p
[
i
−
1
]
[
k
]
[
1
]
)
dp[i][k][0]=max(dp[i-1][k][0],dp[i-1][k][1])
dp[i][k][0]=max(dp[i−1][k][0],dp[i−1][k][1])
为1时:枚举与前一个球的范围重合的大小
x
x
x,用前缀和
s
u
m
sum
sum 存储
a
a
a 的和,那么式子为
d
p
[
i
]
[
k
]
[
1
]
=
m
a
x
(
d
p
[
i
−
x
]
[
k
−
1
]
[
1
]
+
s
u
m
[
i
]
−
s
u
m
[
x
]
)
dp[i][k][1]=max(dp[i-x][k-1][1]+sum[i]-sum[x])
dp[i][k][1]=max(dp[i−x][k−1][1]+sum[i]−sum[x]),(
0
≤
x
≤
w
0 \leq x \leq w
0≤x≤w)
注意:当
x
=
w
x=w
x=w 时,由于与前一个球范围直接重叠,所以可以将
d
p
[
i
−
x
]
[
k
−
1
]
[
0
]
dp[i-x][k-1][0]
dp[i−x][k−1][0] 加入更新范围。
现在的时间复杂度为
O
(
t
n
k
w
)
O(tnkw)
O(tnkw),会出事,所以用单调队列优化1的那个部分,时间复杂度为
O
(
t
n
k
)
O(tnk)
O(tnk) (应该是这样的吧……)
代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int inf=1e9+7;
int t,n,m,w,a[10001],sum[20001];
int dp[20001][501][2],ans;
int q[20001],l,r;
int main(){
//freopen("bowling.in","r",stdin);
//freopen("bowling.out","w",stdout);
scanf("%d",&t);
while(t--){
memset(dp,0,sizeof(dp));
memset(sum,0,sizeof(sum));
ans=0;
scanf("%d%d%d",&n,&m,&w);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
for(int i=1;i<=n;++i)
sum[i]=sum[i-1]+a[i];
for(int i=n+1;i<=n+w-1;++i)
sum[i]=sum[n];
for(int i=1;i<=w+n-1;++i)
for(int j=1;j<=m;++j)
dp[i][j][0]=dp[i][j][1]=-inf;
for(int i=1;i<=w+n-1;++i)//初始化
dp[i][0][1]=-inf;
for(int k=1;k<=m;++k){
l=1,r=1;q[r]=0;
for(int i=1;i<=n+w-1;++i){
dp[i][k][0]=max(dp[i][k][0],max(dp[i-1][k][0],dp[i-1][k][1]));
while(l<=r&&dp[q[r]][k-1][1]-sum[q[r]]<dp[i][k-1][1]-sum[i]) r-=1;//单调队列优化dp
q[++r]=i;
while(l<=r&&q[r]-q[l]>w) l+=1;
dp[i][k][1]=max(dp[i][k][1],dp[q[l]][k-1][1]-sum[q[l]]+sum[i]);
int x=max(i-w,0);
dp[i][k][1]=max(dp[i][k][1],dp[x][k-1][0]-sum[x]+sum[i]);
ans=max(ans,max(dp[i][k][0],dp[i][k][1]));
}
}
printf("%d\n",ans);
}
return 0;
}