题单。
A
从 \(i-1\) 与 \(i-2\) 转移即可。
#include<bits/stdc++.h>
using namespace std;
int n;
int h[100031];
int dp[100031];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>h[i];
memset(dp,0x3f,sizeof(dp));
dp[1]=0;
for(int i=1;i<=n;i++){
if(i-1>=1) dp[i]=min(dp[i],dp[i-1]+abs(h[i]-h[i-1]));
if(i-2>=1) dp[i]=min(dp[i],dp[i-2]+abs(h[i]-h[i-2]));
}
cout<<dp[n];
return 0;
}
B
枚举转移点 \(i-k \sim i-1\) 转移即可。
#include<bits/stdc++.h>
using namespace std;
int n,k;
int h[100031];
int dp[100031];
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>h[i];
memset(dp,0x3f,sizeof(dp));
dp[1]=0;
for(int i=1;i<=n;i++)
for(int j=1;j<=k;j++)
if(i-j>=1) dp[i]=min(dp[i],dp[i-j]+abs(h[i]-h[i-j]));
cout<<dp[n];
return 0;
}
C
用 \(dp_{1 \sim n,0 \sim 3}\) 记录每天三种活动的幸福值即可。
转移:
\[\begin{cases} dp_{i,0}=\max(dp_{i,1},dp_{i,2})+a_{i,0} \\ dp_{i,1}=\max(dp_{i,0},dp_{i,2})+a_{i,1} \\ dp_{i,2}=\max(dp_{i,0},dp_{i,1})+a_{i,2} \\ \end{cases} \]#include<bits/stdc++.h>
using namespace std;
int n,k;
int a[100031][3];
int dp[100031][3];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i][0]>>a[i][1]>>a[i][2];
memset(dp,-0x3f,sizeof(dp)),dp[0][0]=dp[0][1]=dp[0][2]=0;
for(int i=1;i<=n;i++){
dp[i][0]=max(dp[i-1][1],dp[i-1][2])+a[i][0];
dp[i][1]=max(dp[i-1][0],dp[i-1][2])+a[i][1];
dp[i][2]=max(dp[i-1][0],dp[i-1][1])+a[i][2];
}
cout<<max(dp[n][0],max(dp[n][1],dp[n][2]));
return 0;
}
D
01 背包板子。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int N,W;
int w[131],v[131];
int dp[100031];
signed main(){
cin>>N>>W;
for(int i=1;i<=N;i++) cin>>w[i]>>v[i];
for(int i=1;i<=N;i++)
for(int j=W;j>=w[i];j--)
dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
cout<<dp[W];
return 0;
}
E
01 背包变种。
考虑通过 01 背包求出对于所有 \(1 \le i \le \text{MAX}\) 的 \(i\),
在满足 \(\sum v_i \le \text{MAX}\) 的条件下最大化的 \(\sum w_i\),
其中 \(\text{MAX}=n \times 10^3\)(因为 \(1 \le v_i \le 10^3\)),记为 \(dp_i\)。
然后倒序枚举所有的 \(dp_i\)(为了保证找到的第一个就是最大的),
若当前的 \(dp_i \le W\),输出即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int N,W,MAX;
int w[131],v[131];
int dp[100031];
signed main(){
cin>>N>>W;
MAX=N*1000;
for(int i=1;i<=N;i++) cin>>w[i]>>v[i];
memset(dp,0x3f,sizeof(dp));
dp[0]=0;
for(int i=1;i<=N;i++)
for(int j=MAX;j>=v[i];j--)
dp[j]=min(dp[j],dp[j-v[i]]+w[i]);
for(int i=MAX;i;i--){
if(dp[i]<=W){
cout<<i; break;
}
}
return 0;
}
F
LCS 板子。
dp 时可以记录转移节点,然后输出时按转移节点递归输出即可。
#include<bits/stdc++.h>
using namespace std;
string a,b;
int n,m;
int dp[3031][3031];
int x[3031][3031],y[3031][3031];
void print(int nn,int mm){
if(nn==0||mm==0) return;
print(nn-x[nn][mm],mm-y[nn][mm]);
if(a[nn]==b[mm]) cout<<a[nn];
}
int main(){
cin>>a>>b;
a="#"+a,b="#"+b;
n=a.size()-1,m=b.size()-1;
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(a[i]==b[j]) dp[i][j]=dp[i-1][j-1]+1,x[i][j]=y[i][j]=1;
else if(dp[i-1][j]>dp[i][j-1]) dp[i][j]=dp[i-1][j],x[i][j]=1;
else dp[i][j]=dp[i][j-1],y[i][j]=1;
}
}
print(n,m);
return 0;
}
G
令 \(dp_x\) 表示从节点 \(x\) 出发的最长路径长度。
转移时,递归地向儿子寻找路径,进行收集型 dp 即可,别忘加上与儿子的连边。顺便对答案取 \(\max\)。
#include<bits/stdc++.h>
using namespace std;
int n,m,ans;
int dp[100031];
vector<int> G[100031];
void dfs(int x,int fa){
if(dp[x]) return;
dp[x]=-1;
for(auto i:G[x]){
if(i==fa) continue;
dfs(i,x);
dp[x]=max(dp[x],dp[i]);
}
dp[x]++;
ans=max(ans,dp[x]);
}
int main(){
cin>>n>>m;
for(int i=1,u,v;i<=m;i++)
cin>>u>>v,G[u].push_back(v);
for(int i=1;i<=n;i++) dfs(i,0);
cout<<ans;
return 0;
}
H
从左、上的 .
转移即可。
#include<bits/stdc++.h>
#define int unsigned long long
#define MOD ((int)(1e9+7))
using namespace std;
int n,m,ans;
char a[1031][1031];
int dp[1031][1031];
signed main(){
cin>>n>>m;
dp[1][1]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
cin>>a[i][j];
for(int i=1;i<=n;i++){
for(int j=1;j<=m;j++){
if(i==1&&j==1) continue;
if(a[i-1][j]!='#') dp[i][j]=(dp[i][j]+dp[i-1][j])%MOD;
if(a[i][j-1]!='#') dp[i][j]=(dp[i][j]+dp[i][j-1])%MOD;
}
}
cout<<dp[n][m]%MOD;
return 0;
}
I
期望 dp 板子。
令 \(dp_{i,j}\) 表示前 \(i\) 个银币有 \(j\) 个朝上的概率。
初始:
\[\begin{cases} dp_{0,0}=1\\ dp_{i,0}=dp_{i-1,0} \times (1-p_i) \end{cases} \]转移:
\[dp_{i,j}=dp_{i-1,j-1} \times p_i + dp_{i-1,j} \times (1-p_i) \]注意至少要保留 \(10\) 位小数。
#include<bits/stdc++.h>
using namespace std;
int n;
long double ans;
long double p[3031];
long double dp[3031][3031];
int main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>p[i];
dp[0][0]=1;
for(int i=1;i<=n;i++){
dp[i][0]=dp[i-1][0]*(1-p[i]);
for(int j=1;j<=i;j++)
dp[i][j]=dp[i-1][j-1]*p[i]+dp[i-1][j]*(1-p[i]);
}
for(int i=0;i<=n;i++)
if(i>n-i)
ans+=dp[n][i];
cout<<setprecision(10)<<fixed<<ans;
return 0;
}
J
不好想的期望 dp。
令 \(dp_{i,j,k}\) 表示有 \(i\) 个 \(1\) 个寿司的盘子、\(j\) 个 \(2\) 个寿司的盘子、\(k\) 个 \(3\) 个寿司的盘子的操作次数的数学期望值(概率与其答案之积的总和)。
转移:
-
对于空盘子,直接加上每次操作的平均概率 \(\dfrac{n}{i+j+k}\) 即可。
-
对于 \(1\) 个寿司的盘子,直接加上概率 \(\dfrac{i}{i+j+k} \ \times\) 答案 \(dp_{i-1,j,k}\) 即可。
-
对于 \(2\) 个寿司的盘子,直接加上概率 \(\dfrac{j}{i+j+k} \ \times\) 答案 \(dp_{i+1,j-1,k}\)(因为 \(2\) 个寿司吃一个就变成了 \(1\) 个) 即可。
-
对于 \(3\) 个寿司的盘子,直接加上概率 \(\dfrac{k}{i+j+k} \ \times\) 答案 \(dp_{i,j+1,k-1}\) 即可。
答案为 \(dp_{cnt_1,cnt_2,cnt_3}\)(\(cnt_i\) 为 \(i\) 个寿司的盘子的初始数量)。
仍然需要至少保留十位小数。
并且为保证无后效性,需要按照 \(k,j,i\) 的顺序枚举状态。
#include<bits/stdc++.h>
using namespace std;
int n;
int cnt[31];
double dp[331][331][331];
int main(){
cin>>n;
for(int i=1,a;i<=n;i++) cin>>a,cnt[a]++;
for(int k=0;k<=n;k++){
for(int j=0;j<=n;j++){
for(int i=0;i<=n;i++){
if(!i&&!j&&!k) continue;
dp[i][j][k]=(double)n;
if(i) dp[i][j][k]+=dp[i-1][j][k]*(double)i;
if(j) dp[i][j][k]+=dp[i+1][j-1][k]*(double)j;
if(k) dp[i][j][k]+=dp[i][j+1][k-1]*(double)k;
dp[i][j][k]/=(double)(i+j+k);
}
}
}
cout<<setprecision(10)<<fixed<<dp[cnt[1]][cnt[2]][cnt[3]];
return 0;
}
K
博弈论 dp 板子。
令 \(dp_i\) 表示还剩下 \(i\) 个石子时是先手胜(\(1\))还是后手胜(\(0\))。
显然 \(dp_i\) 可以转移到 \(dp_{i,a_j}\)。
很容易想到当一个先手必胜状态可以转移到另一个必败状态时,
当前状态是必败的。
于是我们枚举 \(i,j\) 表示所有状态
并检查其是否会转移到先手必败状态去即可。
#include<bits/stdc++.h>
using namespace std;
int k,n;
int a[131],dp[100031];
int main(){
cin>>n>>k;
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1;i<=k;i++)
for(int j=1;j<=n;j++)
dp[i]+=(i-a[j]>=0&&!dp[i-a[j]]);
cout<<(dp[k]?"First":"Second");
return 0;
}
L
有意思的题。似乎既可贪心又可 dp。
很容易想到令 \(dp_{i,j}\) 表示两端数下标为 \(i,j\) 时的 \(X-Y\) 值。
分情况转移:
-
若已经被取走的数为偶数个,则此时是先手操作,令 \(dp_{i,j}=\max(dp_{i+1,j}+a_i,dp_{i,j-1}+a_j)\)。
-
若已经被取走的数为奇数个,则此时是后手操作,令 \(dp_{i,j}=\min(dp_{i+1,j}-a_i,dp_{i,j-1}-a_j)\)。
答案为 \(dp_{1,n}\)。
贪心思路有心情再更。
//dp code
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,a[3031];
int dp[3031][3031];
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
for(int l=1;l<=n;l++){
for(int i=1;i+l-1<=n;i++){
int j=i+l-1;
if((n-l)%2) dp[i][j]=min(dp[i+1][j]-a[i],dp[i][j-1]-a[j]);
else dp[i][j]=max(dp[i+1][j]+a[i],dp[i][j-1]+a[j]);
}
}
cout<<dp[1][n];
return 0;
}
//greedy code
//......
M
前缀和优化板子。
首先令 \(dp_{i,j}\) 表示前 \(i\) 个人分到 \(j\) 块糖果的方案数。
易得朴素 dp 方程:
\[dp_{i,j}=\sum^{j}_{k=\max(0,j-a_{i-1})} dp_{i-1,k} \]直接转移时间复杂度 \(O(nk^2)\),无法通过。
考虑到每个状态都是由其上面同列所有行的状态转移而来,
于是令 \(sum_i\) 表示 \(\sum^{i}_{j=0} dp_{i,j}\),转移:
\[sum_i=sum_{i-1}+dp_{i-1,j} \]然后 dp 转移方程改为:
\[dp_{i,j}=sum_j-sum_{\max(0,j-a_{i-1})-1} \]即可直接转移。时间复杂度 \(O(nk)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MOD=1e9+7;
int n,k,a[131];
int sum[100031],dp[131][100031];
int get_sum(int l,int r){
if(!l) return sum[r]%MOD;
return (sum[r]+MOD-sum[l-1])%MOD;
}
signed main(){
cin>>n>>k;
for(int i=0;i<n;i++) cin>>a[i];
dp[0][0]=1ll;
for(int i=1;i<=n;i++){
sum[0]=dp[i-1][0];
for(int j=1;j<=k;j++)
sum[j]=(sum[j-1]+dp[i-1][j])%MOD;
for(int j=0;j<=k;j++)
dp[i][j]=get_sum(max(0ll,j-a[i-1]),j);
}
cout<<dp[n][k]%MOD;
return 0;
}
N
原:P1775。
区间 dp 板子。
令 \(dp_{i,j}\) 表示区间 \([i,j]\) 合并成一个数的最小代价。
考虑枚举中转点 \(k\),显然有转移:
\[dp_{i,j}=dp_{i,k}+dp_{k+1,j}+\sum^{j}_{p=i} a_p \]其中 \(\sum^{j}_{p=i} a_p\) 用前缀和维护即可。
时间复杂度 \(O(n^3)\)。
注意枚举 \(i\) 时为确保无后效性需要倒序枚举,枚举 \(k\) 时不能到 \(j\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
int a[431],sum[431];
int dp[431][431];
signed main(){
cin>>n;
for(int i=1;i<=n;i++)
cin>>a[i],sum[i]=sum[i-1]+a[i];
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++) dp[i][i]=0;
for(int i=n;i>=1;i--)
for(int j=i+1;j<=n;j++)
for(int k=i;k<j;k++)
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+(sum[j]-sum[i-1]));
cout<<dp[1][n];
return 0;
}
O
状压 dp 板子。
考虑对于枚举集合 \(S\),然后枚举 \(i\),让第 \(cur\) 个男人匹配第 \(i\) 个女人。
易得转移方程:
\[dp_S=dp_S+dp_{S-2^{i-1}}(i \in S \ \text{and} \ a_{cur,i}=1) \]考虑用 \(1 \sim 2^n-1\) 的二进制数表示 \(S\),则转移方程改为:
\[dp_S=dp_S+dp_{S-2^{i-1}}(\lfloor \dfrac{S}{2^{i-1}} \rfloor \bmod 2 =0 \ \text{and} \ a_{cur,i}=1) \]此时 \(cur\) 即为 \(S\) 二进制下有多少个 \(1\)(因为男女人数相等)。
直接转移即可,答案为 \(dp_{1,2^n-1}\),时间复杂度 \(O(n2^n)\)。
#include<bits/stdc++.h>
using namespace std;
const int MOD=1e9+7;
int n;
int a[31][31];
int dp[(1<<21)];
int main(){
cin>>n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a[i][j];
dp[0]=1;
for(int S=1;S<(1<<n);S++){
int cur=0,S2=S;
while(S2>0) cur+=(S2&1),S2>>=1;
for(int i=1;i<=n;i++)
if(a[cur][i]&&((S>>(i-1))&1))
dp[S]=(dp[S]+dp[S-(1<<(i-1))])%MOD;
}
cout<<dp[(1<<n)-1]%MOD;
return 0;
}
P
树形 dp 板子。
令 \(dp_{i,0/1}\) 表示将节点 \(i\) 及其子树染成黑(\(1\))或白(\(0\))的方案数。
显然有转移:
\[dp_{i,0}=dp_{i,0} \times (dp_{j,0}+dp_{j,1}) \]\[dp_{i,1}=dp_{i,1} \times dp_{j,0} \](其中 \(j \in i\) 的子树)
直接 dfs 进行转移即可。时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
vector<int> G[200031];
int dp[100031][2];
const int MOD=1e9+7;
void dfs(int x,int f){
dp[x][0]=dp[x][1]=1;
for(auto i:G[x]){
if(i==f) continue;
dfs(i,x);
dp[x][0]=(dp[x][0]*(dp[i][0]+dp[i][1]))%MOD;
dp[x][1]=(dp[x][1]*dp[i][0])%MOD;
}
}
signed main(){
cin>>n;
for(int i=1,u,v;i<n;i++)
cin>>u>>v,
G[u].push_back(v),
G[v].push_back(u);
dfs(1,0);
cout<<(dp[1][0]+dp[1][1])%MOD;
return 0;
}
Q
题目要求出最大带权上升子序列(Maximum Weighted Increasing Subsequence,我们姑且称其为 MWIS)。
令 \(dp_i\) 表示以 \(i\) 结尾的 MWIS 的长度。显然有转移:
\[dp_i=\max^{i-1}_{j=1}\{dp_j+a_i \mid h_j<h_i\} \]套个树状数组优化取 \(\max\) 再进行转移即可,时间复杂度 \(O(n \log n)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,ans,a[200031],h[200031],t[200031];
void add(int x,int y){ for(int i=x;i<=n;i+=i&(-i)) t[i]=max(t[i],y); }
int qry(int x){ int q=0; for(int i=x;i;i-=i&(-i)) q=max(q,t[i]); return q; }
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>h[i];
for(int i=1;i<=n;i++) cin>>a[i];
for(int i=1,dp;i<=n;i++) dp=qry(h[i]-1)+a[i],add(h[i],dp),ans=max(ans,dp);
cout<<ans;
return 0;
}
R
矩阵加速 dp 板子。
首先令 \(dp_{i,j,k}\) 表示 \(i \to j\) 路径中长度为 \(k\) 的路径条数。
考虑像 floyd 那样枚举中转点 \(t\) 进行转移。
于是易得转移方程:
\[dp_{i,j,k}=\sum^{n}_{t=1} dp_{i,t,k-1}+dp_{t,j,1} \]直接转移时间复杂度 \(O(n^3k)\),T 飞了。
然后我们发现这个转移方程就是矩阵快速幂。
于是套个矩阵快速幂即可。时间复杂度 \(O(n^3 \log k)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k,ans;
const int MOD=1e9+7;
struct matrix{
int siz,v[53][53];
matrix(){ memset(v,0,sizeof(v)); }
}a;
matrix operator* (const matrix &a,const matrix &b){
matrix ret; ret.siz=a.siz;
for(int i=1;i<=ret.siz;i++)
for(int j=1;j<=ret.siz;j++)
for(int t=1;t<=ret.siz;t++)
ret.v[i][j]+=a.v[i][t]*b.v[t][j]%MOD,ret.v[i][j]%=MOD;
return ret;
}
matrix qpow(matrix a,int b){
matrix ret; ret.siz=a.siz;
for(int i=1;i<=ret.siz;i++) ret.v[i][i]=1;
while(b){
if(b&1) ret=ret*a;
a=a*a,b>>=1;
}
return ret;
}
signed main(){
cin>>n>>k,a.siz=n;
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
cin>>a.v[i][j];
matrix res=qpow(a,k);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
ans=(ans+res.v[i][j])%MOD;
cout<<ans;
return 0;
}
S
数位 dp 板子。
令 \(dp_{i,j,k}\) 表示 \(1 \sim K\) 中,目前处于前缀 \(i\) 位,模 \(d\) 余数为 \(j\),且与 \(K\) 相同(\(k=1\)) / 不同(\(k=0\))的数的个数。
显然有转移:
\[dp_{i,j,k}=\sum^{m}_{t=0} dp_{i-1,(j+t) \bmod d,k \&(t=m)} \](其中 \(m\) 为当前这一位能填的最大数值,\(t\) 枚举的是所有能填的数,枚举顺序是从高位到低位)
直接转移即可。需要注意一些细节(在代码中)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
string k;
int d,num[10031];
int dp[10031][131][2];
const int MOD=1e9+7;
int dfs(int pos,int res,int sta){
if(!pos) return !res;
if(dp[pos][res][sta]!=(int)-1) return dp[pos][res][sta]%MOD;
int ret=0,maxx=9;
if(sta) maxx=num[pos]; //保证构造出的数 <=k
for(int i=0;i<=maxx;i++)
ret=(ret+dfs(pos-1,(res+i)%d,sta&&(i==maxx)))%MOD;
return dp[pos][res][sta]=ret;
}
signed main(){
cin>>k>>d;
memset(dp,-1,sizeof(dp));
for(int i=0;i<k.size();i++) num[i+1]=k[k.size()-i-1]-'0';
cout<<((dfs(k.size(),0,1)-1)%MOD+MOD)%MOD; //要 -1,因为 0 也被算入了
return 0;
}
T
简单前缀和优化。
令 \(dp_{i,j}\) 表示第 \(i\) 个数填 \(j\) 时的方案数。
答案即为 \(\sum_{k=1}^{n} dp_{i,k}\)。
易得朴素转移方程:
\[\begin{cases} dp_{i,j}=\sum_{k=1}^{j-1} dp_{i,k} \ \ \ s_i=\texttt{<}\\ dp_{i,j}=\sum_{k=j}^{i-1} dp_{i,k} \ \ \ s_i=\texttt{>}\\ \end{cases} \]套个前缀和优化掉 \(\sum\) 即可。时间复杂度 \(O(n^2)\)。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
string s;
int dp[3031][3031];
int sum[3031][3031];
const int MOD=1e9+7;
signed main(){
cin>>n>>s,s="#"+s;
dp[1][1]=1;
for(int i=1;i<=n;i++) sum[1][i]=1;
for(int i=2;i<=n;i++){
for(int j=1;j<=i;j++){
if(s[i-1]=='<') dp[i][j]=sum[i-1][j-1]%MOD;
else dp[i][j]=(sum[i-1][i-1]-sum[i-1][j-1]+MOD)%MOD;
sum[i][j]=(sum[i][j-1]+dp[i][j])%MOD;
}
}
cout<<sum[n][n]%MOD;
return 0;
}
U
看到 \(n \le 16\) 直接状压 dp。
令 \(dp_i\) 表示集合 \(i\) 能产生的最大得分。
易得转移方程:
\[dp_i=\max_{j \in i} \{ dp_j+dp_k \} \](其中 \(k\) 为 \(j\) 的补集)
初始状态预处理出每个集合 \(i\) 分成一组的最大得分即可。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n;
int a[31][31];
int val[(1<<17)],dp[(1<<17)];
signed main(){
cin>>n;
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
cin>>a[i][j];
for(int S=1;S<(1<<n);S++)
for(int i=0;i<n;i++)
for(int j=0;j<i;j++)
if(((1<<i)&S)&&((1<<j)&S))
val[S]+=a[i][j];
for(int S=1;S<(1<<n);S++){
dp[S]=val[S];
for(int i=S&(S-1);i;i=S&(i-1))
dp[S]=max(dp[S],dp[i]+dp[S^i]);
}
cout<<dp[(1<<n)-1];
return 0;
}
V
换根 dp 板子。
令 \(dp1_u\) 表示在 \(u\) 的子树内,黑节点与 \(u\) 组成连通块的方案数;\(dp2_u\) 则是在子树外。
对于 \(dp1_u\),显然有转移方程:
\[dp1_u=\prod dp1_v+1(v \in \text{son}\ u) \]对于 \(dp2_u\),我们考虑将问题转化为求父节点 \(f\) 子树内的方案数,易得转移方程:
\[dp2_u=(dp_{f} \times \prod dp_v(v \in \text{brother}\ u)+1)+1 \]对于 \(\prod dp_v(v \in \text{brother}\ u)+1\) 预处理前 / 后缀积即可直接转移。
时间复杂度 \(O(n)\)。
#include<bits/stdc++.h>
using namespace std;
int n,MOD;
vector<int> G[100031];
int dp1[100031],dp2[100031];
int pre[100031],lst[100031];
void dfs1(int x,int fa){
vector<int> son; dp1[x]=1;
for(auto i:G[x]){
if(i==fa) continue;
dfs1(i,x);
dp1[x]=1ll*dp1[x]*(dp1[i]+1)%MOD;
son.push_back(i);
}
int npre=1,nlst=1;
for(int i=0;i<son.size();i++)
pre[son[i]]=npre,npre=1ll*npre*(dp1[son[i]]+1)%MOD;
for(int i=son.size()-1;i>=0;i--)
lst[son[i]]=nlst,nlst=1ll*nlst*(dp1[son[i]]+1)%MOD;
}
void dfs2(int x,int fa){
if(fa==-1) dp2[x]=1;
else dp2[x]=(1ll*dp2[fa]*(1ll*pre[x]*lst[x]%MOD)%MOD+1)%MOD;
for(auto i:G[x])
if(i!=fa) dfs2(i,x);
}
signed main(){
cin>>n>>MOD;
for(int i=1,u,v;i<n;i++)
cin>>u>>v,G[u].push_back(v),G[v].push_back(u);
dfs1(1,-1),dfs2(1,-1);
for(int i=1;i<=n;i++) cout<<1ll*dp1[i]*dp2[i]%MOD<<'\n';
return 0;
}
W
令 \(dp_{i,j}\) 表示前 \(i\) 个数中最后一个 \(1\) 在位置 \(j\) 时能获得的最大分数。
易得转移:
\[dp_{i,j}=dp_{i-1,j}+\sum_{l_k \le j,r_k=i} v_k \]可以发现在转移过程中可能有同一个规则被重复使用,
于是我们考虑转移时对于每条规则将其相关联的转移一并操作。
因为规则影响的是连续区间,所以可以使用线段树维护,
具体地,我们每次令 \(dp_i\) 初始化为前 \(i-1\) 的最大 \(dp\) 值,
然后运用双指针思想枚举每个满足 \(r_j=i\) 的规则,转移即可。
每次转移均摊 \(O(1)\),总时间复杂度 \(O(n \log n)\)。
#include<bits/stdc++.h>
#define int long long
#define ll(p) (p<<1)
#define rr(p) (p<<1|1)
using namespace std;
int n,m;
struct interval{
int l,r,v;
}a[200031];
struct Segment_tree{
int tag,val;
}t[800031];
bool cmp(interval x,interval y){ return x.r<y.r; }
void spd(int rt){
t[ll(rt)].val+=t[rt].tag,t[rr(rt)].val+=t[rt].tag;
t[ll(rt)].tag+=t[rt].tag,t[rr(rt)].tag+=t[rt].tag,t[rt].tag=0;
}
void upd(int rt,int l,int r,int s,int e,int v){
if(l>=s&&r<=e){
t[rt].val+=v,t[rt].tag+=v; return;
}
spd(rt); int mid=(l+r)>>1;
if(s<=mid) upd(ll(rt),l,mid,s,e,v);
if(e>mid) upd(rr(rt),mid+1,r,s,e,v);
t[rt].val=max(t[ll(rt)].val,t[rr(rt)].val);
}
signed main(){
cin>>n>>m;
for(int i=1;i<=m;i++) cin>>a[i].l>>a[i].r>>a[i].v;
sort(a+1,a+m+1,cmp);
for(int i=1,j=1;i<=n;i++){
upd(1,1,n,i,i,max(0ll,t[1].val));
while(a[j].r==i&&j<=m) upd(1,1,n,a[j].l,a[j].r,a[j].v),j++;
}
cout<<max(0ll,t[1].val);
return 0;
}
X
一眼 01 背包。但是直接做会 T。
于是根据国王游戏那题想到对于每个箱子按照 \(s_i+w_i\) 从小到大排序,
然后就过了。
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,ans=-1e9;
int dp[20031];
struct node{
int w,s,v;
}a[1031];
bool cmp(node x,node y){
return x.s+x.w<y.s+y.w;
}
signed main(){
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i].w>>a[i].s>>a[i].v;
sort(a+1,a+n+1,cmp);
memset(dp,-1,sizeof(dp)),dp[0]=0;
for(int i=1;i<=n;i++)
for(int j=a[i].s;j>=0;j--)
dp[j+a[i].w]=max(dp[j+a[i].w],dp[j]+a[i].v);
for(int i=0;i<=20000;i++) ans=max(ans,dp[i]);
cout<<ans;
return 0;
}
Y
Z
耗时 2 个月,终于更完了 qwq。
标签:AtCoder,int,sum,long,vp,DP,include,转移,dp From: https://www.cnblogs.com/XOF-0-0/p/18048888