ybtoj Au 数据结构优化 DP

前言中的前言

  1. 由于本人过菜,有些题解会咕掉,请原谅这个蒟蒻

  2. 由于本人过菜,不知道什么时候就 AFO 了,想给这个机房留下点什么……

  3. 如果想看高效进阶的题解,建议出门左拐,去云落那里看看,保证是全网最全最好的,但不要对云落的博客好奇,更不要看云落的一言 和 云落的合集:黑夜刀己,白日爱人

  4. 如果解决了题解里本蒟蒻的问题,将会获得任意奖励一份(不一定是物质哦),就比如这篇题解就有一个问题

正片开始!

好耶!!!(泪目)

题面

题目传送门

前言

数据结构优化 dp T1,水题

正文

dpi,j 为以第 i 个元素结尾的长度为 j 的严格上升序列个数

转移 dpi,j=ak<aidpk,j1

显然 k 可以用树状数组维护

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long

const int N=10005;
const int mod=123456789;
int n,m;
int lowbit(int x){return x&-x;}
int c[N][105],a[N],b[N];
void update(int x,int d,int k){
	for(;x<=N-5;x+=lowbit(x)){
		c[x][k]=(c[x][k]+d)%mod;
	}
}
int query(int x,int k){
	int res=0;
	for(;x;x-=lowbit(x)){
		res=(res+c[x][k])%mod;
	}
	return res;
}
signed main(){
	while(cin>>n>>m){
		memset(c,0,sizeof(c));
		for(int i=1;i<=n;i++){
			cin>>a[i];
			b[i]=a[i];
		}
		sort(b+1,b+1+n);
		int size=unique(b+1,b+1+n)-b-1;
		for(int i=1;i<=n;i++){
			a[i]=lower_bound(b+1,b+1+size,a[i])-b;
		}
		
		for(int i=1;i<=n;i++){
			update(a[i],1,1);
			for(int j=2;j<=m;j++){
				int tmp=0;
				tmp=query(a[i]-1,j-1);
				if(!tmp)break;
				update(a[i],tmp,j);
			}
		}
		cout<<query(N-5,m)%mod<<endl;
	}
	return 0;
}

后记

云落秒了

题面

题目传送门

前言

数据结构优化 dp T2,应用题

正文

其实这题直接看题解就好了(看不懂就多看几遍)

fi,j 表示在第 i 个村庄修建第 j 个基站的最小费用

转移:

fi,j=min(fk,j1+cstk,i)+ci(j1k<i)

cstk,i 就是不能被 ki 覆盖的村庄要赔付的费用

滚动一下

问题是快速的求 cst

对于任意一个村庄 i ,记它所能被覆盖的左右边界 sti,edi 这个是可以预处理的,然后在用邻接表记录 ed 值为 i 的村庄有哪些(不要问为什么,往下看)

这样当我们推导 i+1 时,若从村庄 1stk1(edk=i) 转移过来则必定要赔偿村庄 k 的费用,我们就可以考虑用线段树来维护 fk+cstk,i 的值,即在区间 [1,st[k]1] 加上村庄 k 的费用,而转移即在区间 [1,i1]fk+cstk,i 的最小值,总复杂度为 O(nlogn×k)

代码

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e4+6;
const int inf=1e18;
int ls(int p){return p<<1;}
int rs(int p){return p<<1|1;}
int n,k,d[N],c[N],s[N],w[N],st[N],ed[N],f[N],tree[N<<2],tag[N<<2];
struct node{int to,nxt;}e[N];
int head[N],cnt;
void add(int u,int v){
	e[++cnt].to=v;
	e[cnt].nxt=head[u];
	head[u]=cnt;
}
void pushup(int p){tree[p]=min(tree[ls(p)],tree[rs(p)]);}
void build(int p,int pl,int pr){
	tree[p]=0,tag[p]=0;				
	if(pl==pr){
		tree[p]=f[pl];
		return;
	}
	int mid=(pl+pr)>>1;
	build(ls(p),pl,mid);
	build(rs(p),mid+1,pr);
	pushup(p);
	//cout<<tree[p]<<" ";
}
void addtag(int p,int d){
	tree[p]+=d;
	tag[p]+=d;
}
void pushdown(int p){
	if(tag[p]){
		addtag(ls(p),tag[p]);
		addtag(rs(p),tag[p]);
		tag[p]=0;
	}
}
void update(int p,int pl,int pr,int L,int R,int d){
	if(L>R)return;
	if(pl>=L&&pr<=R){
		addtag(p,d);
		return;
	}
	pushdown(p);
	int mid=(pl+pr)>>1;
	//cout<<mid<<" ";
	if(L<=mid)update(ls(p),pl,mid,L,R,d);
	if(R>mid)update(rs(p),mid+1,pr,L,R,d);
	pushup(p);
}
int query(int p,int pl,int pr,int L,int R){
	if(L>R)return inf;
	if(pl>=L&&pr<=R){
		return tree[p];
	}
	pushdown(p);
	int mid=(pl+pr)>>1,res=inf;
	if(L<=mid)res=min(res,query(ls(p),pl,mid,L,R));
	if(R>mid)res=min(res,query(rs(p),mid+1,pr,L,R));
	return res;
}
signed main(){
	cin>>n>>k;
	for(int i=2;i<=n;i++)cin>>d[i];
	for(int i=1;i<=n;i++)cin>>c[i];
	for(int i=1;i<=n;i++)cin>>s[i];
	for(int i=1;i<=n;i++)cin>>w[i];
	n++,k++;
	d[n]=inf,w[n]=inf;
	for(int i=1;i<=n;i++){
		st[i]=lower_bound(d+1,d+1+n,d[i]-s[i])-d;
		ed[i]=upper_bound(d+1,d+1+n,d[i]+s[i])-d-1;
		add(ed[i],i);
		//cout<<st[i]<<" "<<ed[i]<<endl;
	}
	int now=0;
	for(int i=1;i<=n;i++){
		f[i]=now+c[i];
		for(int j=head[i];j;j=e[j].nxt){
			int v=e[j].to;
			now+=w[v];
		}
		//cout<<"*"<<f[i]<<endl;
	}
	int ans=inf;
	for(int j=2;j<=k;j++){
		build(1,1,n);
	//	cout<<endl<<j<<endl;
		for(int i=1;i<=n;i++){
			f[i]=query(1,1,n,1,i-1)+c[i];
			for(int k=head[i];k;k=e[k].nxt){
				int v=e[k].to;	
				update(1,1,n,1,st[v]-1,w[v]);
			}
			//cout<<f[i]<<" ";
		}
		ans=min(f[n],ans);
	}
	cout<<ans;
	return 0;
}

后记

T2 就紫了?

题面

题目传送门

前言

数据结构优化 dp T3,水题

正文

本蒟蒻原先写了一个 4 个树状数组的写法,然后调不出来……

dpi,j,0/1 表示到点 i,有 j 条折线,末尾上升/下降的方案数

转移:

dpi,j,0=dpk,j,0+dpk,j1,1 yk>yi

dpi,j,1=dpk,j,1+dpk,j1,0 yk<yi

这个直接优化即可(不要像本蒟蒻一样写 4 个……)

代码

#include <bits/stdc++.h>
using namespace std;
const int N=5e5+4;
const int mod=1e5+7;
int n,k;
struct node{int x,y;}e[N];
bool cmp(node a,node b){return a.x<b.x;}
int dp[N][12][2],tree[N][12][2];
int lowbit(int x){return x&-x;}
void add(int x,int k,int o,int d){
	for(;x<=N;x+=lowbit(x))tree[x][k][o]=(d+tree[x][k][o])%mod;
}
int query(int x,int k,int o){
	int res=0;
	for(;x;x-=lowbit(x))res=(res+tree[x][k][o])%mod;
	return res;
}
int main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>e[i].x>>e[i].y;
	}
	sort(e+1,e+n+1,cmp);
	for(int i=1;i<=n;i++){
		dp[i][0][0]=dp[i][0][1]=1;
		add(e[i].y,0,0,1);
		add(e[i].y,0,1,1);
		for(int j=1;j<=k;j++){
			dp[i][j][0]=(query(N,j,0)-query(e[i].y,j,0)+mod)%mod+(query(N,j-1,1)-query(e[i].y,j-1,1)+mod)%mod;
			dp[i][j][1]=(query(e[i].y-1,j,1)+query(e[i].y-1,j-1,0))%mod;
			add(e[i].y,j,0,dp[i][j][0]);
			add(e[i].y,j,1,dp[i][j][1]);
		}
	}
	int kazuha=0;
	for(int i=1;i<=n;i++)kazuha=(kazuha+dp[i][k][0]+dp[i][k][1])%mod;
	cout<<kazuha;
	return 0;
}

后记

被教练叫去写 oj 的题……,但我还是要先写完

题面

题目传送门

前言

数据结构优化 dp T4,应用题

正文

dpi 表示前 i 个馅饼,一定取了 i 的最大得分和

dpi=maxfj+vi (|pipj|2(titj))

把限制展开得

2tipi2tjpj (pi>pj)

2ti+pi2tj+pj (pipj)

会发现满足其中一个式子,另一个也满足

不妨让两个都满足

将一个式子排序,另一个用树状数组维护

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e5+5;
#define int long long
int w,n,b[N];
struct node{int l,r,v;}e[N];
bool cmp(node a,node b){
	return a.l<b.l;
}
int c[N];
int lowbit(int x){return x&-x;}
void add(int x,int d){
	for(;x<=N;x+=lowbit(x))c[x]=max(d,c[x]);
}
int query(int x){
	int res=0;
	for(;x;x-=lowbit(x))res=max(res,c[x]);
	return res;
}
signed main(){
	cin>>w>>n;
	for(int i=1;i<=n;i++){
		int t,p;
		cin>>t>>p>>e[i].v;
		e[i].l=2*t-p,e[i].r=2*t+p;
		b[i]=e[i].r;
	}
	sort(b+1,b+1+n);
	int op=unique(b+1,b+1+n)-b-1;
	for(int i=1;i<=n;i++){
		e[i].r=lower_bound(b+1,b+1+op,e[i].r)-b;
	}
	sort(e+1,e+1+n,cmp);
	for(int i=1;i<=n;i++){
		int res=query(e[i].r);
		add(e[i].r,res+e[i].v);
	}
	cout<<query(op);
	return 0;
}

后记

貌似不用回家了

题面

题目传送门

前言

数据结构优化 dp T5,应用题

本蒟蒻不会单 log

正文

fi,j 表示考虑到第 i 个玉米,已经拔高了 j 次。

fi,j=max(fi,j+1)(hi+jhi+j)

然后树状数组维护 fi,j

代码

#include <bits/stdc++.h>
using namespace std;
const int N=1e4+4;
int n,k,h[N],X,Y,ans,c[505][N];
int lowbit(int x){return x&-x;}
// void update(int x,int y,int d){
// 	cout<<x<<" "<<y<<" "<<d<<" "<<X<<" "<<Y<<" "<<endl;
// 	for(;x<=X;x+=lowbit(x)){
// 		for(;y<=Y;y+=lowbit(y)){
// 			c[x][y]=max(c[x][y],d);
// 			cout<<lowbit(x)<<" "<<y<<endl;
// 		}
// 	}
// }
void update(int x,int y,int d) {
	//cout<<x<<" "<<y<<" "<<val<<endl;
  for (int i = x; i <= X; i += lowbit(i)) {
    for (int j = y; j <= Y; j += lowbit(j)) {
      c[i][j]=max(c[i][j],d);
	  //cout<<i<<" "<<j<<endl;
    }
  }
}
// int query(int x,int y){
// 	int res=0;
// 	for(;x;x-=lowbit(x)){
// 		for(;y;y-=lowbit(y)){
// 			res=max(c[x][y],res);
// 		}
// 	}	
// 	return res;
// }
int query(int x, int y) {
  int ret = 0;
  for (int i = x; i; i -= lowbit(i)) {
    for (int j = y; j; j -= lowbit(j)) {
      ret=max(ret, c[i][j]);
    }
  }
  return ret;
}
int main(){
	cin>>n>>k;
	for(int i=1;i<=n;i++){
		cin>>h[i];
		Y=max(Y,h[i]);
	}
	X=k+1,Y+=k;//cout<<X<<" "<<Y;
	for(int i=1;i<=n;i++){
		for(int j=k;~j;j--){
			int x=query(j+1,h[i]+j)+1;
			//cout<<x<<endl;
			ans=max(ans,x);
			update(j+1,h[i]+j,x);
		}
	}
	cout<<ans;
	return 0;
}

后记

问题来咯

为什么代码中注释的写法不行,本蒟蒻觉得是等价的啊

题面

题目传送门

前言

数据结构优化 dp T6,不用数据结构优化题,人类智慧题

正文

有组合数做法捏

先讲正解

如题解所言,有三个性质

  1. 在一个波动数列中,若 ii+1 不相邻,那么我们直接交换这两个数字就可以组成一个新的波动数列

  2. 把波动数列中的每个数字 i 变成 (n+1)-i 会得到另一个波动数列,且新数列的山峰与山谷情况相反

  3. 波动序列有对称性(就是把序列颠倒过来)

dpi,j 表示选 1i 第一个数为 j 且为山峰的方案数

由于性质 2,答案要 ×2

jj1 不相邻时,由性质 1,直接让 j 变成 j1 就行

相邻时,此时问题转化成了求 i-1个数,j-1为头,但是j-1 为山谷的方案数,由性质2可知,j-1作山谷和 (i-1)-(j-1)+1 山峰的方案数相同;

现在的问题就是,此时的区间和我DP方程的区间意义不同;

山峰与山谷是相对位置关系

将 [j+1,i] 区间的每个数都减 1,这样是不改变相对大小关系的,并且此时就符合我们的方程了;

所以

dpi,j=dpi,j1+dpi1,ij+1

滚动一下!!

只有10行的代码

#include <bits/stdc++.h>
using namespace std;
long long n,p,dp[2][4205],kazuha;
int main(){
	cin>>n>>p;
	dp[0][2]=1;
	for(int i=3;i<=n;i++)for(int j=2;j<=i;j++)dp[i&1][j]=(dp[i&1][j-1]+dp[(i-1)&1][i-j+1])%p;
	for(int j=2;j<=n;j++)kazuha=(kazuha+dp[n&1][j])%p;
	cout<<kazuha*2%p;
}

组合数!!!

dpi,0/1 表示长度为i的波动山脉数量,第一位为山峰/山谷

考虑把两端用最大的数分开(恐狼?)

dpi+=dpjdpi1jCi1j

考虑加上山峰山谷的限制

最大的数放在偶数位后时,它前面的一段山峰数为偶数,又因为最大的数前的一定是山谷,所以前面的山脉的开头就一定是山峰,后面的山脉开头显然是山谷

if(j%2==0)f[i][1]+=f[j][1]*f[i-1-j][0]*C[i-1][j];

if(j&1)f[i][0]+=f[j][0]*f[i-1-j][0]*C[i-1][j];

但是会发现山峰山谷的答案其实是一样的(性质2)

那么就可以把第二维省去了

滚动一下组合数

懒得压了

#include <bits/stdc++.h>
using namespace std;
#define int long long

const int N=4205;
int n,p,f[N],c[2][N];
signed main(){
    ios::sync_with_stdio(false);
    cin>>n>>p;
    f[0]=1;f[1]=1;
    c[0][0]=1;
    c[1][0]=1;c[1][1]=1;
    for(int i=2;i<=n;i++){
    	for(int j=1;j<=i;j++)c[i&1][j]=(c[i&1^1][j-1]+c[i&1^1][j])%p;
        for(int j=0;j<=i-1;j++){
            if(j&1){
                f[i]=(f[i]+c[i&1^1][j]*f[j]%p*f[i-1-j])%p;
            }
        }
    }
    cout<<f[n]*2%p<<endl;
    return 0;
}

后记

好了好了,去写 oj

posted @   小惰惰  阅读(10)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
/* 鼠标点击求赞文字特效 */
点击右上角即可分享
微信分享提示

目录导航