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\) 位的方案数
注意这里,因为我们只关注排列的相对大小,因此可以用相对的排名来计算方案
因为每次转移都是合法的,因此每个状态对应的方案数都是合法的方案数
前缀和优化一下即可
// 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\) 右边的位置)
例如这里 \(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\) 相关的变量
可以看成是 \(y_k=a_i\times x_k+b_i\)
要想使 \(dp_i\) 最小,只需要使直线的截距最小,又因为斜率已经确定(在 \(i\) 位置时),因此可以考虑将这条直线从下向上移,碰到的第一个点就是 \(k\) 对应的点(因为此时截距最小)
发现答案一定取在下凸壳上,用单调队列维护一下(因为题目保证 \(h[i]\) 严格递增,因此可以单调队列)
每次进来一个新直线的时候,首先从队列头pop出来斜率小于当前直线的,然后这是队列头的点就是刚刚 >斜率的直线
(这里就是倒数第二个点和最后的点的斜率变成了队列头,因此这条直线和倒数第二个点相交)
相交的点就是最佳转移点,转移即可
最后别忘了把当前的点和(和队尾的点形成的直线的)斜率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;
}