1. 洛谷 P4644 USACO05DEC Cleaning Shift/AT_dp_w Intervals
之前写过,加入这个系列。
(贪心、线段树优化)洛谷 P4644 USACO05DEC Cleaning Shift/AT_dp_w Intervals 题解
2. 洛谷 P2344 USACO11FEB Generic Cow Protests
题意
Farmer John 的 N N N 头奶牛( 1 ≤ N ≤ 1 0 5 1 \leq N \leq 10^5 1≤N≤105)排成一列,正在进行一场抗议活动。第 i i i 头奶牛的理智度为 a i a_i ai( − 1 0 4 ≤ a i ≤ 1 0 4 -10^4 \leq a_i \leq 10^4 −104≤ai≤104)。
FJ 希望奶牛在抗议时保持理性,为此,他打算将所有的奶牛隔离成若干个小组,每个小组内的奶牛的理智度总和都要不小于零。
由于奶牛是按直线排列的,所以一个小组内的奶牛位置必须是连续的。请帮助 FJ 计算一下,满足条件的分组方案有多少种。
输出满足条件的分组方案对 1 0 9 + 9 10^9+9 109+9 取模的结果。
思路
先考虑朴素的 dp,设
f
i
f_i
fi 表示,对
1
∼
i
1\sim i
1∼i 分好组后,
i
i
i 处于最新一组的最后一个,总的方案数。那么有:
f
i
=
∑
j
<
i
f
j
,
(
∑
k
=
j
+
1
i
a
k
≥
0
)
f_i=\sum_{j<i}f_j,\left(\sum_{k=j+1}^ia_k\ge0\right)
fi=j<i∑fj,
k=j+1∑iak≥0
不妨预处理一个前缀和, s i = ∑ j = 1 i a j s_i=\sum_{j=1}^ia_j si=∑j=1iaj,那么转移条件就是 s i ≥ s j s_i\ge s_j si≥sj。
可是这不就要 Θ ( n 2 ) \Theta(n^2) Θ(n2) 了吗?考虑优化,我们发现,只有 s i ≥ s j s_i\ge s_j si≥sj 时才能转移,那么每次转移的时候,查询状态 j ∈ [ 1 , i ) j\in[1,i) j∈[1,i) 中前缀和 s j ∈ [ min { s } , s i ] s_j\in[\min\{s\},s_i] sj∈[min{s},si] 的合法状态 j j j,所对应的 f j f_j fj 总和。
这其实相当于维护区间
[
min
{
s
}
,
s
i
]
[\min\{s\},s_i]
[min{s},si] 的状态和,可以扔上线段树维护。
f
i
=
query
(
min
{
s
}
,
s
i
)
f_i=\textrm{query}(\min\{s\},s_i)
fi=query(min{s},si)
然后更新和为
s
i
s_i
si 处的状态:
modify
(
s
i
,
f
i
)
\textrm{modify}(s_i,f_i)
modify(si,fi)
答案就是 f n f_n fn 了。
不过鉴于前缀和值域较大,考虑离散化。
记得对 1 0 9 + 9 10^9+9 109+9 取模。
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ls u<<1
#define rs u<<1|1
const ll N=1e5+9,mod=1e9+9;
ll n,a[N],nn;
ll s[N],ss[N],b[N];
struct SegT
{
struct node
{
ll sum;
}T[N<<2];
void pushup(ll u)
{
T[u].sum=(T[ls].sum+T[rs].sum)%mod;
}
void modify(ll u,ll l,ll r,ll x,ll k)
{
if(l==r)
{
T[u].sum=(T[u].sum+k)%mod;
return;
}
ll mid=(l+r)>>1;
if(x<=mid)modify(ls,l,mid,x,k);
if(x>mid)modify(rs,mid+1,r,x,k);
pushup(u);
}
ll query(ll u,ll l,ll r,ll ql,ll qr)
{
if(qr<l||r<ql)return 0;
if(ql<=l&&r<=qr)return T[u].sum;
ll mid=(l+r)>>1,ret=0;
if(ql<=mid)ret=(ret+query(ls,l,mid,ql,qr))%mod;
if(qr>mid)ret=(ret+query(rs,mid+1,r,ql,qr))%mod;
return ret;
}
}A;
int main()
{
scanf("%lld",&n);
a[1]=0;
n++;
for(int i=2;i<=n;i++)
scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
ss[i]=s[i]=s[i-1]+a[i];
sort(ss+1,ss+n+1);
nn=unique(ss+1,ss+n+1)-ss-1;
for(int i=1;i<=n;i++)
b[i]=lower_bound(ss+1,ss+nn+1,s[i])-ss;
A.modify(1,1,n,b[1],1);
ll ret=0;
for(int i=2;i<=n;i++)
{
ret=A.query(1,1,n,1,b[i]);
A.modify(1,1,n,b[i],ret);
}
printf("%lld",ret);
return 0;
}
3. CF474E Pillars
题意
有 n n n 根柱子从左往右排成一行,第 i i i 根柱子的高度是 a i a_i ai。
一只猴子一开始在第 1 1 1 根柱子,他每次可以向右边跳,但是有限制:若猴子当前在第 j j j 根柱子,可以跳到第 i i i 根柱子,仅当 i > j , ∣ a i − a j ∣ ≥ d i>j,|a_i-a_j|\ge d i>j,∣ai−aj∣≥d。
问猴子最多可以跳到多少根柱子?并输出其中一种可行的跳跃方案。
1 ≤ n ≤ 1 0 5 , 0 ≤ d ≤ 1 0 9 , 1 ≤ a i ≤ 1 0 15 1\le n\le 10^5,0\le d\le 10^9,1\le a_i\le 10^{15} 1≤n≤105,0≤d≤109,1≤ai≤1015
思路
设
f
i
f_i
fi 表示跳到了
i
i
i,最多的柱子数。那么有:
f
i
=
max
j
<
i
{
f
j
}
+
1
,
(
∣
a
i
−
a
j
∣
≥
d
)
f_i=\max_{j<i}\{f_j\}+1,(|a_i-a_j|\ge d)
fi=j<imax{fj}+1,(∣ai−aj∣≥d)
显然超时,考虑优化。其实和上一题类似,就是在一堆符合条件的状态里面,挑一个最大的。
让我们看到条件:
∣
a
i
−
a
j
∣
≥
d
|a_i-a_j|\ge d
∣ai−aj∣≥d,则若
a
j
∈
[
min
{
a
}
,
a
i
−
d
]
∪
[
a
i
+
d
,
max
{
a
}
]
a_j\in[\min\{a\},a_i-d]\cup [a_i+d,\max\{a\}]
aj∈[min{a},ai−d]∪[ai+d,max{a}],那么
j
j
j 就是一个合法的状态,我们发现
j
j
j 就在两段区间里面,就是区间查询最大值了,扔上线段树,查询对应区间的状态
f
j
f_j
fj 最大值。
f
i
=
max
(
query
(
min
{
a
}
,
a
i
−
d
)
,
query
(
a
i
+
d
,
max
{
a
}
)
)
f_i=\max(\textrm{query}(\min\{a\},a_i-d),\textrm{query}(a_i+d,\max\{a\}))
fi=max(query(min{a},ai−d),query(ai+d,max{a}))
发现 a i a_i ai 和 d d d 值域较大,记得离散化。
至于方案,记录转移点然后 dfs 倒序输出即可。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ls u<<1
#define rs u<<1|1
const ll N=3e5+9,inf=0x7f7f7f7f;
ll n,d,a[N];
ll aa[N],nn;
ll f[N],fat[N];
ll tot,oup[N];
void print(ll x)
{
if(!x)return;
oup[++tot]=x;
print(fat[x]);
}
struct node
{
ll ma,id;
};
node nmx(node x,node y)
{
if(x.ma>y.ma)return x;
return y;
}
struct SegT
{
node T[N<<2];
void pushup(ll u)
{
T[u]=nmx(T[ls],T[rs]);
}
void modify(ll u,ll l,ll r,ll x,ll k,ll id)
{
if(l==r)
{
if(k>T[u].ma)
{
T[u].ma=k;
T[u].id=id;
}
return;
}
ll mid=(l+r)>>1;
if(x<=mid)modify(ls,l,mid,x,k,id);
if(x>mid)modify(rs,mid+1,r,x,k,id);
pushup(u);
}
node query(ll u,ll l,ll r,ll ql,ll qr)
{
if(ql<=l&&r<=qr)return T[u];
ll mid=(l+r)>>1;
node ret;
ret.ma=-inf;
if(ql<=mid)ret=nmx(ret,query(ls,l,mid,ql,qr));
if(qr>mid)ret=nmx(ret,query(rs,mid+1,r,ql,qr));
return ret;
}
}A;
int main()
{
scanf("%lld%lld",&n,&d);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
aa[i]=a[i];
aa[i+n]=a[i+n]=a[i]-d;
aa[i+2*n]=a[i+2*n]=a[i]+d;
}
sort(aa+1,aa+3*n+1);
ll nn=unique(aa+1,aa+3*n+1)-aa-1;
for(int i=1;i<=3*n;i++)
a[i]=lower_bound(aa+1,aa+nn+1,a[i])-aa;
ll ans=0,id;
f[1]=1;
A.modify(1,1,nn,a[1],f[1],1);
for(int i=2;i<=n;i++)
{
/* ll x=lower_bound(aa+1,aa+nn+1,a[i])-aa;
ll L=lower_bound(aa+1,aa+nn+1,a[i]-d)-aa;
ll R=lower_bound(aa+1,aa+nn+1,a[i]+d)-aa;*/
ll x=a[i],L=a[i+n],R=a[i+2*n];
node s=A.query(1,1,nn,1,L),t=A.query(1,1,nn,R,nn);
node c=nmx(s,t);
f[i]=c.ma+1;
fat[i]=c.id;
//cout<<i<<" "<<c.id<<"\n";
A.modify(1,1,nn,a[i],f[i],i);
if(f[i]>ans)
{
ans=f[i];
id=i;
}
}
// cout<<id<<endl;
printf("%lld\n",ans);
print(id);
for(int i=tot;i>=1;i--)
cout<<oup[i]<<" ";
return 0;
}
4. 洛谷 P1848 USACO12OPEN Bookshelf
题意
思路
设
f
i
f_i
fi 表示以
i
i
i 为末尾,对
j
∼
i
j\sim i
j∼i 新开一层书架的最小答案。那么有:
f
i
=
min
j
=
1
i
{
f
j
−
1
+
max
k
=
j
i
h
k
}
(
∑
k
=
j
i
w
i
≤
L
)
f_i=\min_{j=1}^i\left\{f_{j-1}+\max_{k=j}^ih_k\right\}\left(\sum_{k=j}^iw_i\le L\right)
fi=j=1mini{fj−1+k=jmaxihk}
k=j∑iwi≤L
对于限制条件,由于前缀和单调,我们可以用双指针 Θ ( n ) \Theta(n) Θ(n) 地预处理出满足条件的 j j j 的下界。
显然是 Θ ( n 2 ) \Theta(n^2) Θ(n2) 的。
但是转移式子里面又有 min \min min 又有 max \max max 着实难搞。
找 f j f_j fj 显然是线段树区间查询最小值,如果我们找到一个 f j f_j fj 之后先不管,直接叠上 h i h_i hi 扔上线段树,那就考虑在后面的遍历中修改那一坨 max \max max。
我们可以用单调栈维护 max k = j + 1 i h k \max_{k=j+1}^ih_k maxk=j+1ihk,如果栈顶被弹出,那就在线段树上直接修改即可。
我的写法是,在 1 ∼ i 1\sim i 1∼i 找合法的状态,向 i + 1 i+1 i+1 转移,叠加的是 h i + 1 h_{i+1} hi+1。
代码
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define ls u<<1
#define rs u<<1|1
const ll N=1e5+9,inf=3e14;
ll n,L,h[N],w[N];
ll las[N];
stack<ll>stk;
struct SegT
{
struct node
{
ll mi;
ll tag;
}T[N<<2];
void pushup(ll u)
{
T[u].mi=min(T[ls].mi,T[rs].mi);
}
void pushdown(ll u)
{
if(T[u].tag)
{
T[ls].mi+=T[u].tag;
T[ls].tag+=T[u].tag;
T[rs].mi+=T[u].tag;
T[rs].tag+=T[u].tag;
T[u].tag=0;
}
}
void modify(ll u,ll l,ll r,ll ql,ll qr,ll k)
{
if(ql<=l&&r<=qr)
{
T[u].mi+=k;
T[u].tag+=k;
return;
}
pushdown(u);
ll mid=(l+r)>>1;
if(ql<=mid)modify(ls,l,mid,ql,qr,k);
if(qr>mid)modify(rs,mid+1,r,ql,qr,k);
pushup(u);
}
ll query(ll u,ll l,ll r,ll ql,ll qr)
{
if(ql<=l&&r<=qr)return T[u].mi;
pushdown(u);
ll mid=(l+r)>>1,ret=inf;
if(ql<=mid)ret=min(ret,query(ls,l,mid,ql,qr));
if(qr>mid)ret=min(ret,query(rs,mid+1,r,ql,qr));
return ret;
}
}A;
int main()
{
// freopen("P1848_4.in","r",stdin);
scanf("%lld%lld",&n,&L);
for(int i=1;i<=n;i++)
scanf("%lld%lld",&h[i],&w[i]);
ll mas=0,j=0;
for(int i=1;i<=n;i++)
{
mas+=w[i];
while(mas>L)
{
j++;
mas-=w[j];
}
las[i]=j+1;
}
/* for(int i=1;i<=n;i++)
cout<<las[i]<<"~"<<i<<endl;*/
A.modify(1,1,n+1,1,1,h[1]);
h[0]=inf;
stk.push(0);
ll ret;
for(int i=1;i<=n;i++)
{
// cout<<i<<":";
while(!stk.empty()&&h[i]>h[stk.top()])
{
ll x=stk.top();
stk.pop();
// cout<<stk.top()<<"~"<<x<<" "<<h[i]<<"<="<<h[x]<<"\n";
A.modify(1,1,n+1,stk.top()+1,x,h[i]-h[x]);
}
// cout<<"\n"<<i<<"的top:"<<stk.top()<<endl;
stk.push(i);
ret=A.query(1,1,n+1,las[i],i);
A.modify(1,1,n+1,i+1,i+1,ret+h[i+1]);
}
printf("%lld",ret);
return 0;
}