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,d\)\(1,2,3\) 个寿司时,全拿走的期望步数
期望题一般都是逆推,\(dp[0][0][0]=0\)
考虑一般情况:$$dp[b][c][d]=1+\frac{a}{n}dp[b][c][d]+\frac{b}{n}dp[b-1][c][d]+\frac{c}{n}dp[b+1][c-1][d]+\frac{d}{n}dp[b][c+1][d-1]$$,消元可得 $$dp[b][c][d]=\frac{b}{b+c+d}dp[b-1][c][d]+\frac{c}{b+c+d}dp[b+1][c-1][d]+\frac{d}{b+c+d}dp[b][c+1][d-1]+\frac{n}{b+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(n^2)\) 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] = \min{dp[p][k]} + h[i], p<i \and k<j\)
发现其实第一维可以省略,因为转移到 \(i\) 的时候肯定 \(dp\) 数组存的是前面的 \(j\)

再设 \(dp[i]\) 表示考虑到前 \(i\) 个花,且最后一个花的位置是 \(i\) 的最大价值
\(dp[i] = \min{dp[j]} + h[i], h[j]<h[i]\)
这样转移是 \(O(n^2)\) 的,怎么优化?
如果我们把 \(dp\) 看成 \(dp[h[i]]\),那么能转移到 \(i\)\(j\) 其实是 \(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]\) 表示 \(i\rightarrow j\) 的长度为 \(k\) 的方案数
\(k\rightarrow k+1\)\(ndp[i][j]=\sum_k{dp[i][k]\times 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] = \sum_{k=1}^{j-1}dp[i-1][k], s[i]为< \]

\[dp[i][j] = \sum_{k=j}^{i-1}dp[i-1][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(3^n)\) 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] = \prod{(1+f[u])}, u \in son_i\)
这是求出来以 \(1\) 为根的答案,如何换根?
\(g[i]\) 代表 \(i\) (子树以外)的方案,并且 \(i\) 必选的方案数
\(g[i] = g[fa[i]] \times \prod{(1+f[u])}\) 其中 \(u\)\(i\) 的兄弟结点
最后 \(i\) 的答案就是 \(f[i] \times g[i]\)
一个细节,如何快速求 \(\prod{(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]\) 就没有统计 \([l_2,r_2]\) 的贡献,因为这需要在 \(dp[r_2][j]\) 处统计
有转移:
\(dp[i][j]=dp[i-1][j]+C(i,j), j<i\)
\(dp[i][i]=max\{dp[i-1][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\in [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] = \binom{x_i+y_i-2}{x_i-1}-\sum_{k=1}^{i-1}dp[k]\times \binom{x_i-x_k+y_i-y_k}{x_i-x_k}\)
统计答案可以把 \((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
可以看成是 \(y_k=a_i\times x_k+b_i\)
要想使 \(dp_i\) 最小,只需要使直线的截距最小,又因为斜率已经确定(在 \(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 @ 2023-01-31 12:26  SkyRainWind  阅读(32)  评论(0编辑  收藏  举报