AT dp 26 题 题解

题单:https://www.luogu.com.cn/training/100578#problems

嘛虽然是 26 题,但是简单的题就不想写了...
就写绿题及以上的吧

E

对重量 dp,设 dp[i][v] 表示考虑到前 i 个物品,价值为 v 时的最小重量

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f,maxn=105;

int n,W,w[maxn],v[maxn];
int dp[105][100005];
signed main(){
	memset(dp, 0x3f, sizeof dp);
	scanf("%d%d",&n,&W);
	for(int i=1;i<=n;i++)scanf("%d%d",&w[i],&v[i]);
	dp[0][0] = 0;
	for(int i=1;i<=n;i++){
		for(int j=0;j+v[i]<=100000;j++){
			dp[i][j + v[i]] = min(dp[i][j + v[i]], dp[i-1][j] + w[i]);
		}
		for(int j=0;j<=100000;j++)dp[i][j] = min(dp[i][j], dp[i-1][j]);
	}
	for(int i=100000;i>=0;i--)
		if(dp[n][i]<=W)return printf("%d\n",i),0;

	return 0;
}

I

dp[i][j] 表示考虑到前 i 个,翻到正面的有 j 个的概率

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f,maxn=3005;

int n;
double p[maxn],dp[maxn][maxn];
signed main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%lf",&p[i]);
	dp[0][0] = 1.0;
	for(int i=1;i<=n;i++){
		for(int j=0;j<=i;j++){
			dp[i][j] += dp[i-1][j] * (1-p[i]);
		}
		for(int j=1;j<=i;j++){
			dp[i][j] += dp[i-1][j-1] * p[i];
		}
	}
	double res=0.0;
	for(int i=n/2+1;i<=n;i++)res+=dp[n][i];
	printf("%.10f\n",res);

	return 0;
}

J

dp[b][c][d] 表示盘子里还剩 b,c,d1,2,3 个寿司时,全拿走的期望步数
期望题一般都是逆推,dp[0][0][0]=0
考虑一般情况:dp[b][c][d]=1+andp[b][c][d]+bndp[b1][c][d]+cndp[b+1][c1][d]+dndp[b][c+1][d1],消元可得 dp[b][c][d]=bb+c+ddp[b1][c][d]+cb+c+ddp[b+1][c1][d]+db+c+ddp[b][c+1][d1]+nb+c+d

答案就是 dp[a[1]][a[2]][a[3]]

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f;

int n,b[5];
double dp[302][302][302];

signed main(){
	int r=0;
	scanf("%d",&n);
	for(int i=1,x;i<=n;i++)scanf("%d",&x),++b[x],r+=x;
	for(int tn=1;tn<=r;tn++){
		for(int i=0;i<=n;i++){
			for(int j=0;j<=n;j++){
				int k=tn-i-2*j;
				if(k%3)continue;
				k /= 3;
				if(k < 0 || k > n)continue;
				if(i)dp[i][j][k] += 1.0 * dp[i-1][j][k] * i/(i+j+k);
				if(j)dp[i][j][k] += 1.0*dp[i+1][j-1][k] * j/(i+j+k);
				if(k)dp[i][j][k] += 1.0*dp[i][j+1][k-1] * k/(i+j+k);
				dp[i][j][k] += 1.0 * n / (i+j+k);
			}
		}
	}
	printf("%.15f\n",dp[b[1]][b[2]][b[3]]);

	return 0;
}

M

O(n2) dp 显然,发现这是个前缀和的形式,因此可以前缀和优化 dp

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f,mod=1e9+7,maxn=100005;

int n,k,a[maxn];
int dp[maxn], sum[maxn];
signed main(){
	scanf("%d%d",&n,&k);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	dp[0] = sum[0] = 1;
	for(int i=1;i<=k;i++)sum[i]=1;
	for(int i=1;i<=n;i++){
		for(int j=k;j>=1;j--){
			// dp[j] += dp[j-a[i]]+..+dp[j-1]
			(dp[j] += (sum[j-1] - ((j-a[i]-1 < 0) ? 0 : sum[max(0, j-a[i]-1)]) + mod)%mod) %= mod;
		}
		for(int j=1;j<=k;j++)sum[j] = sum[j-1] + dp[j], sum[j] %= mod;
	}
	cout<<dp[k];

	return 0;
}

O

平平无奇状压dp,记搜一下即可

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn=23, mod=1e9+7;

int n,a[maxn][maxn];
int b[25][25],cnt[222];

int dp[22][(1<<21)+5];
int dfs(int x, int S){
	if(x==n+1)return 1;
	int &ddd = dp[x][S];
	if(~ddd)return ddd;
	
	int dd=0;
	for(int i=1;i<=cnt[x];i++){int it=b[x][i];if((S & (1<<(it-1)))==0){
		(dd += dfs(x+1, S ^ (1<<(it-1))));
		if(dd >= mod)dd -= mod;
	}}
	return ddd=dd;
}

signed main(){
	memset(dp,-1,sizeof dp); 
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++){
			scanf("%d",&a[i][j]);
			if(a[i][j])b[i][++cnt[i]]=j;
		}
	ll res = dfs(1, 0);
	cout<<res;

	return 0;
}

P

dp[i][0/1] 表示 i 号点黑/白的方案数,注意转移需要用乘法原理

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>
#define pb push_back

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f,maxn=2e5+5,mod=1e9+7;

int n;
vector<int>g[maxn];
ll dp[maxn][2],vis[maxn];

void dfs(int x,int fat=0){
	vis[x]=1;
	dp[x][0]=dp[x][1]=1;
	
	for(auto u : g[x])if(u!=fat){
		dfs(u, x);
		(dp[x][0] *= dp[u][1]) %= mod;
		(dp[x][1] *= (dp[u][0] + dp[u][1])) %= mod;
	}
}

signed main(){
	scanf("%d",&n);
	for(int i=1;i<=n-1;i++){
		int x,y;scanf("%d%d",&x,&y);
		g[x].pb(y), g[y].pb(x);
	}
	ll ans=0;
	for(int i=1;i<=n;i++)if(!vis[i]){
		dfs(i);
		(ans+=dp[i][0]+dp[i][1])%=mod;
	}
	cout<<ans;

	return 0;
}

Q

从数据范围很难想到是 dp 啊。。。
dp[i][j] 表示考虑到前 i 个花,选最后一个花的高度是 j 的最大价值
dp[i][j]=mindp[p][k]+h[i],p<ik<j
发现其实第一维可以省略,因为转移到 i 的时候肯定 dp 数组存的是前面的 j

再设 dp[i] 表示考虑到前 i 个花,且最后一个花的位置是 i 的最大价值
dp[i]=mindp[j]+h[i],h[j]<h[i]
这样转移是 O(n2) 的,怎么优化?
如果我们把 dp 看成 dp[h[i]],那么能转移到 ij 其实是 dp[..<h[i]],考虑使用线段树优化
线段树每个点 i 存的是 dp[i] 但是这个点代表的是 h 值,dp[i] 就是当前点高度为 i 且选了的时候的最大价值

转移就用线段树求一下前缀 max 即可,因为 'j' 其实就是 h[j]<h[i] 的点,转移到线段树上就是 j<i 的点

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 2e5+5;

int n,h[maxn],a[maxn];
struct segm{
	ll mxf;
	segm(){mxf=0;}
}se[maxn << 2];
ll dp[maxn];

void update(int x,ll to,int l,int r,int num){
	if(l == r){
		se[num].mxf = to;
		return ;
	}
	int mid=l+r>>1;
	if(x<=mid)update(x,to,l,mid,num<<1);
	else update(x,to,mid+1,r,num<<1|1);
	se[num].mxf = max(se[num << 1].mxf, se[num << 1 | 1].mxf);
}

ll query(int x,int y,int l,int r,int num){
	if(x <= l && r <= y)return se[num].mxf;
	int mid=l+r>>1;
	if(y<=mid)return query(x,y,l,mid,num<<1);
	else if(x>mid)return query(x,y,mid+1,r,num<<1|1);
	else return max(query(x,y,l,mid,num<<1), query(x,y,mid+1,r,num<<1|1));
}

signed main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)scanf("%d",&h[i]);
	for(int i=1;i<=n;i++)scanf("%d",&a[i]);
	
	ll r=0;
	for(int i=1;i<=n;i++){
		ll qu = query(1, h[i], 1, n, 1);
		dp[i] = qu + a[i];
		r = max(r, dp[i]);
		update(h[i], dp[i], 1, n, 1);
	}
	cout << r;
	
	return 0;
}

R

dp[i][j] 表示 ij 的长度为 k 的方案数
kk+1ndp[i][j]=kdp[i][k]×a[k][j],而 a 就是邻接矩阵,为0/1
发现这就是矩阵乘法的形式,因此对原邻接矩阵求 k 次方即可

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, mod = 1e9+7;

int n;ll k;
struct mat{
	ll a[52][52];
	mat(){memset(a,0,sizeof a);}
};
mat b;

mat operator * (mat a, mat b){
	mat c;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)
			for(int k=1;k<=n;k++)
				(c.a[i][j] += a.a[i][k] * b.a[k][j] % mod) %= mod;
	return c;
}

mat pw(mat b, ll k){
	mat bs = b, c = b;-- k;
	while(k){
		if(k&1)c = c * bs;
		bs = bs * bs;
		k >>= 1;
	}
	return c;
}

signed main(){
	cin >> n >> k;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)cin >> b.a[i][j];
	
	mat now = pw(b, k);
	
	ll ans=0;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)(ans += now.a[i][j]) %= mod;
	cout<<ans;

	return 0;
}

S

随便数位 dp 一下...

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, mod = 1e9+7;

char s[10005];
int num[10005], d, n;
ll dp[10005][2][2][105];

ll dfs(int x,int up,int is0,int rem){
	ll &dd = dp[x][up][is0][rem];
	if(x == n+1){
		if(is0 || rem)return dd = 0;
		else return dd = 1;
	}
	if(~dd)return dd;
	
	dd = 0;
	int lim = up ? num[x] : 9;
	for(int i=0;i<=lim;i++){
		(dd += dfs(x+1, up && i == lim, is0 && i == 0, (rem + i) % d)) %= mod;
	}
	return dd;
}

signed main(){
	memset(dp, -1, sizeof dp);
	scanf("%s",s + 1);
	scanf("%d",&d);
	n = strlen(s + 1);
	for(int i=1;i<=n;i++)num[i] = s[i] - '0';
	printf("%d\n",dfs(1,1,1,0));

	return 0;
}

T

很有意思的一道题
dp[i][j] 表示考虑到第 i 位,且最后一位在 1..i 中是第 j 位的方案数
注意这里,因为我们只关注排列的相对大小,因此可以用相对的排名来计算方案

dp[i][j]=k=1j1dp[i1][k],s[i]<

dp[i][j]=k=ji1dp[i1][k],s[i]>

因为每次转移都是合法的,因此每个状态对应的方案数都是合法的方案数
前缀和优化一下即可

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 3005, mod = 1e9+7;

ll dp[3005][3005];
int n;
char s[3005];
ll sum[3005];

signed main(){
	scanf("%d",&n);scanf("%s",s + 2);
	dp[1][1] = sum[1] = 1;
	for(int i=2;i<=n;i++){
		for(int j=1;j<=i;j++){
			if(s[i] == '<')dp[i][j] = sum[j-1];
			else dp[i][j] = sum[i-1] - sum[j-1] + mod, dp[i][j] %= mod;
		}
		for(int j=1;j<=i;j++)sum[j] = sum[j-1] + dp[i][j], sum[j] %= mod;
	}
	ll ans = 0;
	for(int i=1;i<=n;i++)(ans += dp[n][i]) %= mod;
	cout << ans;

	return 0;
}

U

枚举子集的 O(3n) trick

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 18, maxm = (1<<16) + 5;

int n;
int a[maxn][maxn];
ll cap[maxm], dp[maxm];
signed main(){
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n;j++)scanf("%d",&a[i][j]);
	for(int S=0;S<=(1<<n)-1;S++){
		for(int i=1;i<=n;i++)
			for(int j=1;j<i;j++)
				if((S & (1<<i-1)) && (S & (1<<j-1)))
					cap[S] += a[i][j];
	}
	dp[0] = 0;
	for(int S=1;S<=(1<<n)-1;S++){
		for(int T=S;T;T=(T-1)&S){
			dp[S] = max(dp[S], dp[T] + cap[S ^ T]); 
		}
		dp[S] = max(dp[S], cap[S]);
	}
	cout << dp[(1<<n)-1];

	return 0;
}

V

换根dp。设 f[i] 表示考虑到 i 的子树,且 i 必选的方案数
f[i]=(1+f[u]),usoni
这是求出来以 1 为根的答案,如何换根?
g[i] 代表 i (子树以外)的方案,并且 i 必选的方案数
g[i]=g[fa[i]]×(1+f[u]) 其中 ui 的兄弟结点
最后 i 的答案就是 f[i]×g[i]
一个细节,如何快速求 (1+f[u])?因为模数不一定为质数所以不能用逆元。
维护一个前缀积一个后缀积,就可以计算了

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>
#define pb push_back

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 2e5+5;

int n,mod;
vector<int>g[maxn];
ll dp[maxn],ans[maxn], dpp[maxn];
int id[maxn];
vector<int>prem[maxn], sufm[maxn];
int leaf[maxn];

void dfs(int x,int fat=0){
	dp[x] = 1;
	prem[x].resize(g[x].size()+2);
	sufm[x].resize(g[x].size()+2);
	prem[x][0] = 1;
	sufm[x][g[x].size()+1] = 1;
	
	if(fat && g[x].size() == 1){
		leaf[x] = 1;
		return ;
	}
	
	for(int i=0;i<g[x].size();i++){
		int u=g[x][i];
		if(u!=fat){
			id[u] = i+1;
			dfs(u, x);
			
			prem[x][i+1] = 1ll * prem[x][i] * (1+dp[u]) % mod;
		}else prem[x][i+1] = prem[x][i]; 
	}
	for(int i=g[x].size()-1;i>=0;i--){
		int u=g[x][i];
		if(u!=fat){
			sufm[x][i+1] = 1ll * sufm[x][i+2] * (1+dp[u]) % mod;
		}else sufm[x][i+1] = sufm[x][i+2];
	}
	dp[x] = prem[x][g[x].size()];
}

ll cut(int x,int ii){
	ll res = 1ll * prem[x][ii-1] * sufm[x][ii+1] % mod;
	return res;
}
void changeroot(int x,int fat=0){
	for(int i=0;i<g[x].size();i++){
		int u = g[x][i];
		if(u != fat){
			ll cc = cut(x, i+1);
			dpp[u] = dpp[x];
			dpp[u] = 1ll*dpp[u]*cc%mod+1;
			
			ans[u] = dp[u]*dpp[u]%mod;
			changeroot(u, x);
		}
	}
}

signed main(){
	scanf("%d%d",&n,&mod);
	for(int i=1;i<=n-1;i++){
		int x,y;scanf("%d%d",&x,&y);
		g[x].pb(y), g[y].pb(x);
	}
	
	dfs(1);
	dpp[1] = 1;
	ans[1] = dp[1]*dpp[1]%mod;
	changeroot(1);
	for(int i=1;i<=n;i++)cout<<ans[i]<<'\n';

	return 0;
}

W

好题。
dp的思路就是在每个要求的右端点统计贡献
dp[i][j] 表示考虑到前 i 个位子,上一次选的是 j 的贡献(这里贡献还没算右端点在 i 右边的位置)
image
例如这里 dp[i][j] 就没有统计 [l2,r2] 的贡献,因为这需要在 dp[r2][j] 处统计
有转移:
dp[i][j]=dp[i1][j]+C(i,j),j<i
dp[i][i]=max{dp[i1][k]}+C(i,i)
这里的 C(i,j) 就代表右端点为 i 且左端点 <=j 的区间产生的贡献
但是这样时空复杂度都会爆炸,怎么做?

首先可以优化掉第一维,因为每次其实只有第二维会影响答案
这样方程就变成了:dp[j]+=C(i,j);dp[i]+=max{dp[k]}+C(i,i)
考虑用线段树维护 dp 值,例如有个要求是 [l,r,v],那就先离线按右端点排序,然后对于 i 从小到大,更新 dp[j]+=v,j[l,r],这是第一个转移
第二个转移就是单点修改,加上一个区间 max

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>
#define pb push_back

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn=2e5+5;

int n,m;
struct node{int l,r,w;}a[maxn];
struct segm{
	ll mx,lazy;
}se[maxn << 2];
int cmp(node a,node b){return a.r<b.r;}

void build(int x,int y,int num){
	if(x==y){se[num].mx=se[num].lazy=0;return ;}
	int mid=x+y>>1;build(x,mid,num<<1);build(mid+1,y,num<<1|1);se[num].mx=se[num].lazy=0;
}

void pd(int num){
	if(!se[num].lazy)return ;

	se[num<<1].mx += se[num].lazy;
	se[num<<1].lazy += se[num].lazy;
	se[num<<1|1].mx += se[num].lazy;
	se[num<<1|1].lazy += se[num].lazy;

	se[num].lazy = 0;
}

void update(int x,int y,ll to,int l,int r,int num){
	if(x<=l&&r<=y){
		se[num].mx += to;
		se[num].lazy += to;
		return ;
	}
	pd(num);
	int mid=l+r>>1;
	if(y<=mid)update(x,y,to,l,mid,num<<1);
	else if(x>mid)update(x,y,to,mid+1,r,num<<1|1);
	else update(x,y,to,l,mid,num<<1), update(x,y,to,mid+1,r,num<<1|1);
	se[num].mx = max(se[num<<1].mx, se[num<<1|1].mx);
}

signed main(){
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)scanf("%d%d%d",&a[i].l,&a[i].r,&a[i].w);
	sort(a+1,a+m+1,cmp);
	build(1,n,1);
	
	int curs = 1;
	for(int i=1;i<=n;i++){
		update(i,i,max(0ll, se[1].mx), 1, n, 1);
		while(curs <= m && a[curs].r == i){
			update(a[curs].l, a[curs].r, a[curs].w, 1, n, 1);
			++ curs;
		}
	}
	printf("%lld\n",max(0ll, se[1].mx));

	return 0;
}

X

传送门

Y

容斥一下就只需要求一下不合法的方案,如何统计能不重不漏?考虑每次在经过的第一个障碍点统计答案
dp[i] 表示必须经过第 i 个障碍点,且前面的障碍点都没有经过的方案数
dp[i]=(xi+yi2xi1)k=1i1dp[k]×(xixk+yiykxixk)
统计答案可以把 (H,W) 设置成第 n+1 个障碍点,然后dp[n+1] 即为所求

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>
#define pb push_back

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 2e5+5, mod=1e9+7;

int fac[maxn], inv[maxn];
int h,w,n;
ll dp[maxn];

int pw(int x,int y){
	if(!y)return 1;
	if(y == 1)return x;
	int mid=pw(x,y>>1);
	if(y&1)return 1ll*mid*mid%mod*x%mod;
	return 1ll*mid*mid%mod;
}

int C(int x,int y){return 1ll*fac[x]*inv[y]%mod*inv[x-y]%mod;}
struct node{int x,y;}a[maxn];

int cmp(node a,node b){return a.x!=b.x ? a.x<b.x : a.y<b.y;}
int ck(int i,int j){
	return a[j].x <= a[i].x && a[j].y <= a[i].y;
}
ll to(int x1,int y1,int x2,int y2){
	int dx = x2-x1, dy = y2-y1;
	return C(dx+dy, dx);
}

signed main(){
	fac[0] = inv[0] = 1;
	for(int i=1;i<=maxn-5;i++)fac[i] = 1ll*fac[i-1]*i%mod;
	inv[maxn-5] = pw(fac[maxn-5], mod-2);
	for(int i=maxn-6;i>=0;i--)inv[i] = 1ll*inv[i+1]*(i+1)%mod;
	
	scanf("%d%d%d",&h,&w,&n);
	for(int i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y);
	sort(a+1,a+n+1,cmp);
	a[n+1] = node{h,w};
	
	for(int i=1;i<=n+1;i++){
		dp[i] = C(a[i].x+a[i].y-2, a[i].x-1);
//		printf("! %d\n",dp[i]);
		for(int j=1;j<=n;j++)if(j != i && ck(i,j)){
			(dp[i] += mod - 1ll*dp[j]*to(a[j].x,a[j].y,a[i].x,a[i].y)%mod) %= mod;
		}
	}
	printf("%lld\n",dp[n+1]);

	return 0;
}

Z

斜率优化dp。
状态很显然:设 dp[i] 为跳到第 i 个位置的最小代价
k 为最优转移点,把方程拆成分别只与 i,k 相关的变量
image
可以看成是 yk=ai×xk+bi
要想使 dpi 最小,只需要使直线的截距最小,又因为斜率已经确定(在 i 位置时),因此可以考虑将这条直线从下向上移,碰到的第一个点就是 k 对应的点(因为此时截距最小)
发现答案一定取在下凸壳上,用单调队列维护一下(因为题目保证 h[i] 严格递增,因此可以单调队列)
每次进来一个新直线的时候,首先从队列头pop出来斜率小于当前直线的,然后这是队列头的点就是刚刚 >斜率的直线
image
(这里就是倒数第二个点和最后的点的斜率变成了队列头,因此这条直线和倒数第二个点相交)
相交的点就是最佳转移点,转移即可
最后别忘了把当前的点和(和队尾的点形成的直线的)斜率push进队列

// by SkyRainWind
#include <bits/stdc++.h>
#define mpr make_pair
#define debug() cerr<<"Yoshino\n"
#define pii pair<int,int>
#define pb push_back

using namespace std;

typedef long long ll;
typedef long long LL;

const int inf = 1e9, INF = 0x3f3f3f3f, maxn = 2e5+5;

int n;
ll C;
int h[maxn];
ll dp[maxn];
ll qu[maxn], hd, tl;

double gy(int x){return 1.0*dp[x]+1.0*h[x]*h[x];}
double gx(int x){return 1.0*h[x];}
double slope(int x,int y){
	return (1.0 * (gy(y) - gy(x)) / (gx(y) - gx(x)));
}

signed main(){
	cin.tie(0);
	cin>>n>>C;
	for(int i=1;i<=n;i++)cin>>h[i];
	
	qu[1] = 1;hd = tl = 1;
	dp[1] = 0;
	for(int i=2;i<=n;i++){
		while(hd < tl && slope(qu[hd], qu[hd+1]) <= 2*h[i])++ hd;
		int j = qu[hd];
		dp[i] = dp[j] + 1ll * (h[i]-h[j]) * (h[i]-h[j]) + C; 
		while(hd < tl && slope(qu[tl-1], qu[tl]) >= slope(qu[tl], i))-- tl;
		qu[++ tl] = i;
	}
	cout<<dp[n];

	return 0;
}
posted @   SkyRainWind  阅读(38)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示
点击右上角即可分享
微信分享提示