- yeyou26 的博客
临时博客
- 2024-1-24 23:14:05 @
递增子序列
题意:给定n个数,问长度为m的递增子序列数量
构造dp数组
dp[i][j]表示以第i个元素结尾的长度为j的上升子序列个数
构造转移方程
dp[i][j]=∑a[k]<a[i]&&k<i (dp[k][j-1])
考虑朴素做法
显然只需枚举i,j,k即可完成本题
但显然时间复杂度O(n^2m) 不能接受
考虑数据结构优化DP
不难发现,转移方程实际上对a数组进行了顺序对求和,由顺序对容易想到树状数组,所以考虑使用树状数组省去对k的枚举使时间复杂度降低至O(nmlogn)发现可以通过此题,遂实施
具体实施
#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define mod 123456789
const int MAXN=10010;
const int MAXM=105;
int n,m;
struct Array //数组 但是原地离散化
{
ll initvalue;
int id;
int lsvalue;
}a[MAXN];
bool cmpinitvalue(Array x,Array y)
{
return (x.initvalue<y.initvalue);
}
bool cmpid(Array x,Array y)
{
return (x.id<y.id);
}
struct BinaryIndexTree //把BIT封装起来
{
ll b[MAXN];
void clear()
{
memset(b,0,sizeof(b));
}
int lowbit(int k)
{
return k&(-k);
}
void Modify(int k,ll v)
{
for(int i=k;i<=10000;i+=((-i)&(i))) b[i]=(b[i]+v)%mod;
return ;
}
ll Query(int k)
{
ll ans=0;
while(k>0)
{
ans+=b[k];
ans%=mod;
k-=lowbit(k);
}
return ans%mod;
}
}Bit[MAXM];//开M个BIT,相比书上更好理解
void init()//离散化
{
for(int j=1;j<=m;j++) Bit[j].clear();
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i].initvalue);
a[i].id=i;
}
sort(a+1,a+1+n,cmpinitvalue);
int idx=0;
a[0].initvalue=-1;
for(int i=1;i<=n;i++)
{
if(a[i].initvalue==a[i-1].initvalue) a[i].lsvalue=idx;
else a[i].lsvalue=++idx;
}
sort(a+1,a+1+n,cmpid);
}
void work()//区间求和
{
for(int i=1;i<=n;i++)
{
Bit[1].Modify(a[i].lsvalue,1);
for(int j=2;j<=m;j++)
{
ll temp=Bit[j-1].Query(a[i].lsvalue-1);
if(!temp) break;
Bit[j].Modify(a[i].lsvalue,temp%mod);
}
}
cout<<Bit[m].Query(n)%mod;
}
int main()
{
// freopen("working.in","r",stdin);
// freopen("working.out","w",stdout);
while(scanf("%d%d",&n,&m)!=EOF)
{
init();
work();
cout<<endl;
}
return 0;
}
基站选址
题意:给定n个在直线上的村庄,需要建立不超过k个基站
在第i个村庄建立基站费用为Ci
如果第i个村庄不超过Si的距离内有基站,这个村庄被覆盖了
如果第i个村庄没有被覆盖,则需要补偿这个村庄Wi费用
构造dp数组
f[i][j]=min(f[k][j-1]+pay[k][i])
其中设f[i][j]表示在前i个村庄内,第j个基站建在i处的最小费用(不考虑i~n的赔偿费用)
pay[k][i]表示第k个村庄到第i个村庄的赔偿费用之和
考虑朴素做法
空间复杂度O(n^2)
时间复杂度O(kn^2)
不可接受
优化空间
那么我们观察上面这个方程,可以发现类似于背包问题,可以直接滚掉一维。改造方程:
f[i]=min(f[k]+pay[k][i])
优化时间
主要的时间消耗在了哪里?自然是怎么快速计算pay[k][i]了
那么我们思考:
对于每一个村庄,都有一个范围内需要建立基站,否则就要赔偿,那么我们设第i个村庄的范围为[L,R],如果正在考虑R处建不建基站,那么有下列情况:
1、不在R处设立基站,那么对于村庄i来说,上一个基站在[1,L−1]这个区间的话,就要赔偿村庄i了,因为[L,R]这个区间没有建基站,那么我们就要快速的在[1,L−1]中区间加村庄i的赔偿费用了,我们就以线段树为例啦
2、在R处建立基站,那么也就相当于最后一个基站设立在[1,R−1]这个区间中,找一个费用最小值来转移嘛,还是线段树
所以我们要开一个线段树来维护f+pay的最小值,要有区间加法和区间查询的操作
st[i]表示第i个村庄对应区间的左端点L
ed[i]表示第i个村庄对应区间的右端点R
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 20007
#define int long long
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
struct Edge
{
int to,nxt;
}edge[N<<2];
struct Tree
{
int date,mark;
}tr[N<<2];
int n,k,cnt;
int dis[N],val[N],range[N],pay[N],st[N],ed[N],head[N],f[N];
void Add(int u,int v)
{
edge[++cnt]=(Edge){v,head[u]};
head[u]=cnt;
}
void Get()
{
for(int i=1;i<=n;++i)
{
st[i]=lower_bound(dis+1,dis+1+n,dis[i]-range[i])-dis;
ed[i]=lower_bound(dis+1,dis+1+n,dis[i]+range[i])-dis;
if(dis[ed[i]]>dis[i]+range[i])
--ed[i];
Add(ed[i],i);
}
}
void Pushup(int rt)
{
tr[rt].date=min(tr[rt<<1].date,tr[rt<<1|1].date);
}
void Build(int rt,int l,int r)
{
tr[rt].mark=0;
if(l==r)
{
tr[rt].date=f[l];
return;
}
int mid=l+((r-l)>>1);
Build(rt<<1,l,mid);
Build(rt<<1|1,mid+1,r);
Pushup(rt);
}
void Pushdown(int rt)
{
if(tr[rt].mark)
{
tr[rt<<1].date+=tr[rt].mark;
tr[rt<<1|1].date+=tr[rt].mark;
tr[rt<<1].mark+=tr[rt].mark;
tr[rt<<1|1].mark+=tr[rt].mark;
tr[rt].mark=0;
}
}
int Search(int rt,int l,int r,int L,int R)
{
// cout<<"l:"<<l<<" r:"<<r<<" L:"<<L<<" R:"<<R<<endl;
if(L>R)
return inf;
if(L<=l&&r<=R)
return tr[rt].date;
int mid=l+((r-l)>>1);
Pushdown(rt);
int num=inf;
if(L<=mid)
num=min(num,Search(rt<<1,l,mid,L,R));
if(mid<R)
num=min(num,Search(rt<<1|1,mid+1,r,L,R));
return num;
}
void Update(int rt,int l,int r,int L,int R,int c)
{
// cout<<"l:"<<l<<" r:"<<r<<endl;
if(L>R)
return;
if(L<=l&&r<=R)
{
tr[rt].date+=c;
tr[rt].mark+=c;
return;
}
Pushdown(rt);
int mid=l+((r-l)>>1);
if(L<=mid)
Update(rt<<1,l,mid,L,R,c);
if(mid<R)
Update(rt<<1|1,mid+1,r,L,R,c);
Pushup(rt);
}
void Dp()
{
int now=0;
for(int j=1;j<=n;++j)
{
f[j]=now+val[j];
for(int p=head[j];p;p=edge[p].nxt)
{
int v=edge[p].to;
now+=pay[v];
}
}
int ans=f[n];
// for(int i=1;i<=n;++i)
// {
// printf("%d ",f[i]);
// }
for(int i=2;i<=k;++i)
{
Build(1,1,n);
for(int j=1;j<=n;++j)
{
f[j]=Search(1,1,n,1,j-1)+val[j];
// cout<<f[j]<<" ";
for(int p=head[j];p;p=edge[p].nxt)
{
int v=edge[p].to;
Update(1,1,n,1,st[v]-1,pay[v]);
}
}
ans=min(ans,f[n]);//cout<<"ans:"<<ans<<endl;
}
printf("%lld",ans);
}
void Init()
{
scanf("%lld%lld",&n,&k);
for(int i=2;i<=n;++i)
scanf("%lld",&dis[i]);
for(int i=1;i<=n;++i)
scanf("%lld",&val[i]);
for(int i=1;i<=n;++i)
scanf("%lld",&range[i]);
for(int i=1;i<=n;++i)
scanf("%lld",&pay[i]);
++n;++k;
dis[n]=pay[n]=inf;
}
signed main()
{
Init();
Get();
Dp();
return 0;
}