log型数据结构优化DP解题报告(uoj)
交作业用
不难看出状态同最长公共子序列,但由于上升条件限制,加一个限制:
\(f_{i,j}\)表示\(a_{1...i}\)匹配\(b_{1...j}\)且\(a_i\)必须做结尾的最长公共上升子序列长度
转移方程为
\(f_{i,j} = f_{i,j-1}\) (if \(a_i \neq b_j\))
\(f_{i,j} = \max_{k=1}^{i-1}{f_{k,j-1}+1}\) (且\(a_k \le a_i\)) (if \(a_i = b_j\))
时间复杂度\(O(n^3)\)
但由于\(j\)很少变动的良好性质,可先枚举\(j\)再枚举\(i\),并将\(a_k \le a_i\)条件改为\(a_k \le b_j\),因为\(a_i = b_j\)
开个变量记录当前max即可
时间复杂度\(O(n^2)\)
#include<bits/stdc++.h>
using namespace std;
#define fr(i,a,b) for(int i=a;i<=b;i++)
#define N 2022
int T,n,m,a[N],b[N],f[N][N];
int main(){
scanf("%d",&T);
while(T--){
scanf("%d",&n);
fr(i,1,n)scanf("%d",&a[i]);
scanf("%d",&m);
fr(i,1,m)scanf("%d",&b[i]);
fr(i,1,n)f[1][i]=a[i]==b[1];
fr(i,2,m){
int mx=0;
fr(j,1,n){
f[i][j]=(b[i]==a[j]?max(mx+1,f[i-1][j]):f[i-1][j]);
if(a[j]<=b[i])mx=max(mx,f[i-1][j]);
}
}
int mx=0;
fr(i,1,n)mx=max(mx,f[m][i]);
printf("%d\n",mx);
}
return 0;
}
按套路,\(N\)开一维,\(M\)开一维,仍考虑通过强制钦定来维护递增性质
\(f_{i,j}\)表示\(a_{1...i}\)中以\(a_i\)为结尾长度为\(j\)的严格递增子序列有几个
则\(f_{i,j}=\sum_{k=1}^{i-1}f_{k,j-1}\) (且\(a_k < a_i\))
仍然\(j\)少变,考虑先\(j\)后\(i\),拿树状数组维护前缀和即可
#include<bits/stdc++.h>
using namespace std;
#define fr(i,a,b) for(int i=a;i<=b;i++)
#define N 1005
#define mod 1000000007
int cases,n,m,a[N],te[N],cnt,f[N][N],t[N];
void discrete(){
fr(i,1,n)te[i]=a[i];
sort(te+1,te+n+1);
cnt=unique(te+1,te+n+1)-(te+1);
}
int qu(int x){
return lower_bound(te+1,te+cnt+1,x)-te;
}
int lb(int x){
return x&(-x);
}
void add(int x,int y){
for(;x<N;x+=lb(x))t[x]=(t[x]+y)%mod;
}
int ask(int x){
int res=0;
for(;x;x-=lb(x))res=(res+t[x])%mod;
return res;
}
int main(){
scanf("%d",&cases);
fr(css,1,cases){
scanf("%d%d",&n,&m);
fr(i,1,n)scanf("%d",&a[i]);
discrete();
fr(i,1,n)a[i]=qu(a[i]);
fr(i,1,n)f[1][i]=1;
fr(i,2,m){
memset(t,0,sizeof t);
fr(j,1,n){
f[i][j]=ask(a[j]-1);
add(a[j],f[i-1][j]);
}
}
int ans=0;
fr(i,1,n)ans=(ans+f[m][i])%mod;
printf("Case #%d: %d\n",css,ans);
}
return 0;
}
最终目的是将最大值移到最右端,因而考虑中间状态
\(f_i\)表示将最大值移到\(i\)号位置需要的排序数
转移方程为
\(\forall i \in [1,m]\),
\(f_{r_i}=\min\{f_{r_i},\min_{j = l_i}^{r_i}f_j+1\}\)
单点修改,区间求min,线段树即可
#include<iostream>
#include<cstdio>
using namespace std;
#define fr(i,a,b) for(int i=a;i<=b;i++)
#define N 50005
#define M 500005
int n,m,L[M],R[M];
struct SGT{
int l,r,d;
}t[N<<2];
void push_up(int nd){
t[nd].d=min(t[nd<<1].d,t[nd<<1|1].d);
}
void build(int nd,int L,int R){
t[nd].l=L,t[nd].r=R;
if(L==R)t[nd].d=M;
else{
int mid=(L+R)>>1;
build(nd<<1,L,mid);
build(nd<<1|1,mid+1,R);
push_up(nd);
}
}
void update(int nd,int x,int y){
if(t[nd].l==t[nd].r)t[nd].d=min(t[nd].d,y);
else{
int mid=t[nd<<1].r;
if(x<=mid)update(nd<<1,x,y);
else update(nd<<1|1,x,y);
push_up(nd);
}
}
int ask(int nd,int L,int R){
if(t[nd].l>=L&&t[nd].r<=R)return t[nd].d;
else{
int mid=t[nd<<1].r,res=M;
if(L<=mid)res=min(res,ask(nd<<1,L,R));
if(R>mid)res=min(res,ask(nd<<1|1,L,R));
return res;
}
}
int main(){
scanf("%d%d",&n,&m);
fr(i,1,m)scanf("%d%d",&L[i],&R[i]);
build(1,1,n);
update(1,1,0);
fr(i,1,m)update(1,R[i],ask(1,L[i],R[i])+1);
printf("%d\n",ask(1,n,n));
return 0;
}
Best Cow Fences(nowcoder)
题意不明。。。
实际上就是求长度\(\geq F\)的平均值最大的区间
平均值最大,直接上二分
设\(b_i = a_i - mid\),\(s_i = \sum_{j=1}^i b_j\)
找到\(i \leq j-F\)且\(s_i<=s_j\)即可
随便什么东西维护一下
好像不需要DP
好像很卡精度
#include<iostream>
#include<cstdio>
#include<cmath>
using namespace std;
#define fr(i,a,b) for(int i=a;i<=b;i++)
#define N 100005
const double eps = 1e-8;
int n,m,a[N];
double b[N];
int cmp(double x,double y){
if(abs(x-y)<eps)return 0;
if(x<y)return -1;
return 1;
}
bool ch(double mid){
fr(i,1,n)b[i]=b[i-1]+1.0*a[i]-mid;
double mn=1e10;
fr(i,m,n){
mn=min(mn,b[i-m]);
if(cmp(b[i],mn)>=0)return 1;
}
return 0;
}
int main(){
scanf("%d%d",&n,&m);
fr(i,1,n)scanf("%d",&a[i]);
double l=0,r=2000,mid;
while(abs(r-l)>1e-7){
mid=(l+r)/2;
if(ch(mid))l=mid;
else r=mid;
}
printf("%d",int((l+0.00001)*1000));
return 0;
}
不难看出可以按左端点排序,贪心选
具体而言,从左到右扫一遍,对于每个没被覆盖的点,选一个能盖到它的线段中右端点最右的就行
一看就是堆
《DP》
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define fr(i,a,b) for(int i=a;i<=b;i++)
#define N 25005
int n,m;
struct node{
int l,r;
friend bool operator < (node A,node B){
return A.r<B.r;
}
}a[N];
priority_queue<node> q;
bool cmp(node A,node B){
return A.l<B.l;
}
int main(){
scanf("%d%d",&n,&m);
fr(i,1,n)scanf("%d%d",&a[i].l,&a[i].r);
sort(a+1,a+n+1,cmp);
int nowT=1,now=1,ans=0;
while(nowT<=m){
while(now<=n&&a[now].l<=nowT)q.push(a[now++]);
if(q.empty()||q.top().r<nowT){
puts("-1");
return 0;
}
else{
ans++;
nowT=q.top().r+1;
q.pop();
}
}
printf("%d\n",ans);
return 0;
}
总结:优化其实有迹可循,大多从维度、转移的特点入手,难点还是状态设计
log型常用数据结构:线段树、树状数组、平衡树等
其实DP与贪心也是有相似之处的