最新要闻
- 砍单三大产品线 苹果市值跌破2万亿美元 富士康表态
- 世界热点评!抄底吗?知名投资人段永平:已将手上腾讯美股换成苹果
- 天天观速讯丨二次元外观下带来澎湃性能!铭瑄MS RTX 4070 Ti OC12G璦珈评测:最强192bit显卡非它莫属
- 10升好身材!ROG发布冰刃X迷你机:居然有没发布的RTX 4070
- 金庸最成功的作品 来源自历史上让人耻辱的失败
- 焦点要闻:500吨级!中国最强液体火箭发动机的“家”快建好了
- 胡伟武:Linux桌面软件生态是x86和ARM“软肋” 龙芯希望一两年后全面超过
- 丰田总裁再度质疑电动汽车:这会让车企降低价值!
- 每日资讯:RTX 4090加持:ROG发布新款XG Mobile显卡坞
- 即时:三星发布全球首款8K 150寸投影仪:离墙几毫米即可完成投射
- 焦点热文:画面太美!男子意外发现交通卡余额有172万 官方回应真相无奈
- 环球即时看!宏碁发布eKinekt BD 3酷骑桌:工作之余还能骑动感单车
- 世界热讯:1.6亿美元是罚定了!印度法庭驳回谷歌推翻垄断案的请求
- 当前热点-17万元起真香!奇瑞最高颜值SUV星途瑶光盲订量已达6012台
- 环球热点评!2022广州车展压轴登场 这八款新能源新车千万不要错过
- 今头条!全新NT架构加持!腾讯QQ原生上架麒麟应用商店
手机
iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- iphone11大小尺寸是多少?苹果iPhone11和iPhone13的区别是什么?
- 警方通报辅警执法直播中被撞飞:犯罪嫌疑人已投案
- 男子被关545天申国赔:获赔18万多 驳回精神抚慰金
- 3天内26名本土感染者,辽宁确诊人数已超安徽
- 广西柳州一男子因纠纷杀害三人后自首
- 洱海坠机4名机组人员被批准为烈士 数千干部群众悼念
家电
一本通动态规划篇解题报告
一本通动态规划篇解题报告
\(\text{By DaiRuiChen007}\)
【资料图】
题目来源:《信息学奥赛一本通》提高版 - LibreOJ
数位 dp
0. 过程模板
求解数位 dp 时,用 \(dp_{i,j}\) 表示长度为 \(i\),最高位为 \(j\) 时满足数字的个数,则统计答案的流程如下:
- 分解原数位 \(a_{len}\sim a_1\)(\(a_{len}\) 是最高位)
- 加上所有长度长度小于 \(len\) 的,\(\text{Answer}\gets\sum\limits_{i=1}^{len}\sum\limits_{j=1}^9 dp_{i,j}\)
- 加上所有长度等于 \(len\),且最高位小于 \(a_{len}\) 的,\(\text{Answer}\gets \sum\limits_{i=1}^{a_{len}-1}dp_{len,i}\)
- 加上所有前 \(i-1\) 位与原数相等,第 \(i\) 位小于原数的,\(\text{Answer}\gets\sum\limits_{i=len-1}^{1} \sum\limits_{j=0}^{a_i-1} dp_{i,j}\),如果到某一位的时候前面若干位组成的数已经不满足题目要求,则直接退出
这样做可以统计出区间 \([1,x)\) 之间满足条件的数的个数
I. Amount of Degrees
\(\text{Link}\)
思路分析
考虑拆成两个前缀和,然后答案转化为统计 \([1,x]\) 之间满足条件的个数,将 \(x\) 变成 \(b\) 进制,其中恰好有 \(k\) 个数码 \(1\),所以答案与 \(x\) 中 \(\ge 2\) 的数码无关,如果遇到这样的数码,就将这一位及其后面的所有数码都设为 \(1\),不影响答案,然后二进制数位 dp 即可,只需要考虑第四种情况即可
代码呈现
#include#define int long longusing namespace std;int k,b;inline int C(int n,int m) {int res=1;for(int x=n;x>n-m;--x) res*=x;for(int x=1;x<=m;++x) res/=x;return res;}inline int calc(int x) {int len=0,a[35],res=0;while(x) {a[++len]=x%b;x/=b;}for(int i=len;i>=1;--i) {if(a[i]>1) {for(int j=i;j>=1;--j) {a[j]=1;}break;}}int t=k;for(int i=len;i>=1;--i) {if(!a[i]) continue;res+=C(i-1,t);--t; if(t<0) break;}return res;}signed main() {int x,y;scanf("%lld%lld%lld%lld",&x,&y,&k,&b);printf("%lld\n",calc(y+1)-calc(x));return 0;}
II. 数字游戏
\(\text{Link}\)
思路分析
拆前缀和然后套模板,预处理 dp 的状态转移方程如下:
\[dp_{i,j}=\begin{cases}1 &i=1\\\sum\limits_{k=j}^{9} dp_{i-1,k}&\text{otherwise}\end{cases}\]代码呈现
#include#define int long long#define f puts("comehere");using namespace std;int dp[11][10];inline int calc(int x) {int len=0,a[11],res=0;while(x) {a[++len]=x%10;x/=10;}for(int i=1;i=1;--i) {for(int j=a[i+1];j
III.Windy 数
\(\text{Link}\)
思路分析
同样模板,状态转移方程如下:
\[dp_{i,j}=\begin{cases}1 &i=1\\\sum\limits_{k=0}^{9} [|j-k|\ge2]\times dp_{i-1,k} &\text{otherwise}\end{cases}\]代码呈现
#include#define int long longusing namespace std;int dp[20][20],a[20];inline int calc(int x) {int len=0,res=0;while(x) a[++len]=x%10,x/=10;for(int i=1;i0;--l) {for(int i=0;i=2) res+=dp[l][i];}if(abs(a[l+1]-a[l])<2) break;}return res;}signed main() {for(int i=0;i<=9;++i) dp[1][i]=1;for(int i=2;i<=10;++i) {for(int j=0;j<=9;++j) {for(int k=0;k<=9;++k) {if(abs(j-k)>=2) dp[i][j]+=dp[i-1][k];}}}int start,end;scanf("%lld%lld",&start,&end);printf("%lld\n",calc(end+1)-calc(start));return 0;}
IV. 数字游戏
\(\text{Link}\)
思路分析
略有不同,设 \(dp_{i,j,r}\) 表示长度为 \(i\),最高位为 \(j\),\(\bmod N\) 的余数为 \(r\) 的数的个数,套模板的时候统计后 \(i\) 位的余数不是 \(0\),而是与前 \(i-1\) 位的和是 \(N\) 的倍数的数,状态转移方程如下:
\[dp_{i,j,r}=\begin{cases}0 &i=1,j\not\equiv r\pmod N\\1 &i=1,j\equiv r\pmod N\\\sum\limits_{k=0}^9 dp_{i-1,k,r-k} &\text{otherwise}\end{cases}\]转移时用刷表法更为方便,注意对于多组数据要分别处理 \(dp\) 数组
代码呈现
#include#define int long longusing namespace std;int dp[11][10][100],mod;inline int calc(int x) {int len=0,a[11],res=0;while(x) {a[++len]=x%10;x/=10;}for(int i=1;i=1;--i) {for(int j=0;j
V. 不要 62
\(\text{Link}\)
思路分析
模板 dp,考虑前后两位之间的关系就可以去除含 \(62\) 的情况,注意在统计第三种情况的时候不要忘记排除第 \(i-1\) 为是 \(6\),这一位又恰好考虑到 \(2\) 的情况,状态转移方程如下:
\[dp_{i,j}=\begin{cases}0&j=4\\1&i=1,j\neq 4\\\sum\limits_{k=0}^9 [k\neq 4\land(k\neq 2\lor j\neq 6)]\times dp_{i-1,k} &\text{otherwise}\end{cases}\]代码呈现
#include#define int long longusing namespace std;int dp[11][10];inline int calc(int x) {int len=0,a[11],res=0;while(x) {a[++len]=x%10;x/=10;}for(int i=1;i=1;--i) {for(int j=0;j
VI. 恨 7 不成妻
\(\text{Link}\)
思路分析
数位 dp?大模拟!(误)
考虑 \(dp_{i,j,d,s,0/1/2}\) 分别表示前 \(i\) 位,开头为 \(j\),数字和模 \(7\) 余 \(d\),原数字模 \(7\) 余 \(s\),的数的个数/数字和/平方和
转移平方和的时候记得用完全平方和展开,这里直接贴转移方程的 C++ 源码了(实在是太长了)
for(int i=0;i<=9;++i) {if(i==7) continue;dp[1][i][i%7][i%7][0]=1;dp[1][i][i%7][i%7][1]=i;dp[1][i][i%7][i%7][2]=i*i;}for(int i=2;i<=19;++i) {for(int j=0;j<=9;++j) {if(j==7) continue;for(int k=0;k<=9;++k) {for(int dig=0;dig<7;++dig) {for(int sum=0;sum<7;++sum) {int con=j*pow10(i-1),t=con%MOD;dp[i][j][(dig+j)%7][(sum+con%7)%7][0]+=dp[i-1][k][dig][sum][0];dp[i][j][(dig+j)%7][(sum+con%7)%7][0]%=MOD;dp[i][j][(dig+j)%7][(sum+con%7)%7][1]+=dp[i-1][k][dig][sum][0]*t%MOD;dp[i][j][(dig+j)%7][(sum+con%7)%7][1]%=MOD;dp[i][j][(dig+j)%7][(sum+con%7)%7][1]+=dp[i-1][k][dig][sum][1];dp[i][j][(dig+j)%7][(sum+con%7)%7][1]%=MOD;dp[i][j][(dig+j)%7][(sum+con%7)%7][2]+=t*t%MOD*dp[i-1][k][dig][sum][0]%MOD;dp[i][j][(dig+j)%7][(sum+con%7)%7][2]%=MOD;dp[i][j][(dig+j)%7][(sum+con%7)%7][2]+=t*dp[i-1][k][dig][sum][1]%MOD*2%MOD;dp[i][j][(dig+j)%7][(sum+con%7)%7][2]%=MOD;dp[i][j][(dig+j)%7][(sum+con%7)%7][2]+=dp[i-1][k][dig][sum][2];dp[i][j][(dig+j)%7][(sum+con%7)%7][2]%=MOD;}}}}}
注意:一定一定一定要注意是否会溢出
代码呈现
#include#define int long longusing namespace std;const int MOD=1e9+7;int dp[21][10][7][7][3];//length start_digit digit_sum%7 number%7 answer_powerinline int pow10(int x) {int res=1;for(int i=1;i<=x;++i) res=res*10;return res;}inline int calc(int x) {int len=0,a[21],res=0;while(x) {a[++len]=x%10;x/=10;}for(int i=1;i=1;--i) {for(int j=0;j
VII. 数字计数
\(\text{Link}\)
思路分析
经典数位 dp,设 \(dp_{i,j,d}\) 表示长度为 \(i\),最高位为 \(j\) 的数字中 \(d\) 出现的次数,状态转移方程如下:
\[dp_{i,j,d}=\begin{cases}0 &i=1,j\neq d\\1 &i=1,j=d\\10^{i-1}+\sum\limits_{k=0}^9 dp_{i-1,k,d} &i\neq 1,j=d\\\sum\limits_{k=0}^9dp_{i-1,k,d} &\text{otherwise}\end{cases}\]注意计数的时候要考虑数字第 \(i\) 位对答案是否有贡献
代码呈现
#include#define int __int128using namespace std;inline int read() {int x=0;char c=getchar();while(!isdigit(c)) c=getchar();while(isdigit(c)) x=(x<<3)+(x<<1)+c-"0",c=getchar();return x;}inline void write(int x) {if(x>=10) write(x/10);putchar(x%10+"0");return ;}int dp[15][10][10];/*dp[i][j][k]:i-digit j-end k-count which digit*/int a[15],pw[15];inline int calc(int x,int digit) {int res=0,len=0;while(x) {a[++len]=x%10,x/=10;}for(int i=1;i0;--l) {for(int i=0;il;--i) if(a[i]==digit) res+=pw[l-1]*a[l];}return res;}signed main() {for(int i=0,r=1;i<=12;++i,r*=10) pw[i]=r;for(int i=0;i<=9;++i) dp[1][i][i]=1;for(int l=2;l<=12;++l) {for(int i=0;i<=9;++i) {dp[l][i][i]+=pw[l-1];for(int d=0;d<=9;++d) {for(int j=0;j<=9;++j) {//[ij...]dp[l][i][d]+=dp[l-1][j][d];}}}}int a=read(),b=read();for(int i=0;i<=9;++i) {write(calc(b+1,i)-calc(a,i));putchar(" ");}puts("");return 0;}
状压 dp
I. 国王
\(\text{Link}\)
思路分析
如果某行的某个位置有国王则其状态的二进制下对应位为 \(1\),否则为 \(0\)
设 \(dp_{i,j,s}\) 表示前 \(i\) 行共放了 \(j\) 个国王,第 \(i\) 行状态为 \(s\) 的方案数,预处理出可行单行方案和其对应的放置国王数,然后枚举上一行的放置情况,如果满足则转移
设单行的可能放置方案的集合为 \(\mathbf S\),时间复杂度为 \(\Theta(nk|\mathbf S|^2)\)
代码呈现
#include#define int long longusing namespace std;const int MAXN=11;int king[1< choice;signed main() {scanf("%lld%lld",&n,&l);for(int i=0;i<(1<>1)&i) continue; choice.push_back(i);int j=i;king[i]=__builtin_popcount(i);}for(int i:choice) if(king[i]<=l) ++dp[1][i][king[i]];for(int i=2;i<=n;++i) {for(int j:choice) {for(int k:choice) {if((k&j)||((k<<1)&j)||((k>>1)&j)) continue;for(int h=1;h<=l;++h) {if(h+king[j]>l) continue;dp[i][j][h+king[j]]+=dp[i-1][k][h];}}}}for(int i=1;i<=n;++i) {for(int j:choice) {ans+=dp[i][j][l];}}printf("%lld",ans);return 0;}
II. 牧场的安排
\(\text{Link}\)
思路分析
如果某行的某个位置被选取了则其状态的二进制下对应位为 \(1\),否则为 \(0\),预处理出每行可能的状态集 \(\mathbf S\)
设 \(dp_{i,s}\) 表示第 \(i\) 行的状态为 \(s\) 时前 \(i\) 行的方案总数,如果 \(s\) 中选取的位置都不是贫瘠的,那么就枚举上一行的情况并转移,时间复杂度 \(\Theta(n|\mathbf S|^2)\)
代码呈现
#includeusing namespace std;const int MOD=1e8;int a[15],state[151],dp[151][15];signed main() {int n,m,tot=0;scanf("%d%d",&n,&m);for(int s=0;s<(1<>1)&s) continue;state[++tot]=s;} for(int i=1;i<=n;++i) {for(int j=0;j
III. 涂抹果酱
\(\text{Link}\)
思路分析
三进制状态压缩,某行的某个位置的颜色(\(1\sim 3\))对应其状态中对应位的数码(\(0\sim 2\)),预处理出每行的合法状态集 \(\mathbf S\)
设 \(dp_{i,s}\) 表示第 \(i\) 行状态为 \(s\) 时,前 \(i\) 行的涂色方案数,每次枚举往前一行状态,合法则转移
计算答案的时候,将第 \(k\) 行的状态作为初始第 \(1\) 行的状态,因为上下两行独立,所以
\[\text{Answer}=\sum_{s\in\mathbf S}(dp_{n-k+1,s})\times \sum_{s\in\mathbf S}(dp_{k,s})\]时间复杂度 \(\Theta(n|\mathbf S|^2)\)
代码呈现
#include#define int long longusing namespace std;const int MAXN=1e5+1,MAXS=243,MOD=1e6;int dp[MAXN][243],pw[6]={1,3,9,27,81,243};inline int calc(int s,int pos) {return (s/pw[pos])%3;}signed main() {int n,m,k,st=0;scanf("%lld%lld%lld",&n,&m,&k);vector state;for(int i=0;i
IV. 炮兵阵地
\(\text{Link}\)
思路分析
某行的某个位置如果有炮兵阵地,则该行状态的对应位置为 \(1\) 否则为 \(0\),预处理出每行符合要求的状态集 \(\mathbf S\),同时计算出 \(\mathbf S\) 中的每个状态的价值
设 \(dp_{i,s,l}\) 表示第 \(i\) 行状态为 \(s\),第 \(i-1\) 行状态为 \(s-1\) 时,前 \(i\) 行最多摆放的阵地个数,转移时分别枚举第 \(i,i-1,i-2\) 三行的状态,满足则转移,时间复杂度 \(\Theta(n|\mathbf S|^3)\)
代码呈现
#include#define int long longusing namespace std;int a[105],state[63],cost[63],dp[63][63][105];char str[105];inline int calc(int x) {return __builtin_popcount(x);}signed main() {int n,m,tot=0;scanf("%lld%lld",&n,&m);for(int s=0;s<(1<>1)&s||(s>>2)&s) continue;state[++tot]=s;cost[tot]=calc(s);} for(int i=1;i<=n;++i) {scanf("%s",str);for(register int j=0;j
V. 动物园
\(\text{Link}\)
题目大意
令 \(dp_{i,s}\) 表示区间 \([i,i+4]\) 内的动物状态为 \(s\)(存在为 \(1\),否则为 \(0\)),预处理出状态 \(s\) 可以使多少个 \(i\) 上的小朋友满意
转移时保证前四位不变的前提下枚举第 \(i-1\) 位的值,边界条件由枚举起点状态得来,为保证原图是环,统计答案的状态必须与初始状态一致
思路分析
#includeusing namespace std;const int MAXN=1e4+1,INF=1e9;int dp[MAXN][32],val[MAXN][32];signed main() {int n,m,res=0;scanf("%d%d",&n,&m);for(int t=1;t<=m;++t) {int a,b,c,x=0,y=0;scanf("%d%d%d",&a,&b,&c);for(int i=1;i<=b;++i) {int u;scanf("%d",&u);x+=1<<((u-a+n)%n);}for(int i=1;i<=c;++i) {int u;scanf("%d",&u);y+=1<<((u-a+n)%n);}for(int s=0;s<32;++s) if(((~s)&x)||(s&y)) ++val[a][s];}for(int start=0;start<32;++start) {for(int i=0;i<32;++i) dp[0][i]=i==start?0:-INF;for(int i=1;i<=n;++i) {for(int s=0;s<32;++s) {dp[i][s]=max(dp[i-1][(s&15)<<1],dp[i-1][(s&15)<<1|1])+val[i][s];}}res=max(res,dp[n][start]);}printf("%d\n",res);return 0;}
树形 dp
0. 约定
\(\mathbf C_{p}\) 表示 \(p\) 的子节点构成的集合
\(w_{u,v}\) 表示 \(u,v\) 之间边的边权
\(\mathbf{L}\) 表示树的叶节点构成的集合
\(\mathbf T\) 为整棵树所有节点构成的集合
\(r\) 为树的根节点
I. 二叉苹果树
\(\text{Link}\)
思路分析
经典树上背包,枚举每个儿子保留多少条边,父亲保留多少条边
结合 01 背包的思想,设 \(dp_{p,w}\) 表示当前以 \(p\) 为根的子树保留最多 \(w\) 条边的答案,得到如下状态转移方程:
\[dp_{p,w}=\begin{cases}0&p\in\mathbf L\\\max\limits_{v\in\mathbf C_p}\left\{\max\limits_{i=1}^w \{dp_{v,i}+dp_{p,w-i-1}+w_{p,v}\}\right\} &\text{otherwise}\end{cases}\]时间复杂度 \(\Theta(nq^2)\)
代码呈现
#include#define int long longusing namespace std;int dp[101][101],val[101],n,q;struct node {int des,val;};vector edge[101];inline void dfs(int p,int f) {if(edge[p].size()==1) return ;for(node t:edge[p]) {int v=t.des;if(v==f) continue;dfs(v,p);for(int i=q;i>0;--i) {for(int j=0;j
II. 选课
\(\text{Link}\)
思路分析
树形 dp,以 \(0\) 作为根节点,最终形态会变成一棵树
同样 01 背包,设 \(dp_{p,w}\) 表示以 \(p\) 为根的子树中至多选择 \(w\) 节课的答案,有如下状态转移方程:
\[dp_{p,w}=\begin{cases}0 &w=0\\a_p &w>0\\\max\limits_{v\in\mathbf C_p}\left\{\max\limits_{i=1}^w \{dp_{v,i}+dp_{p,w-i}\}\right\} &p\not\in\mathbf L\end{cases}\]注意初始值所有节点的 \(dp\) 值都是 \(a_p\),边界条件中的 \(p\) 可以不是叶节点
时间复杂度 \(\Theta(nm^2)\)
代码呈现
#include using namespace std;const int MAXN=105;int w[MAXN],dp[MAXN][MAXN],m,n;vector edge[MAXN];inline void dfs(int x) {for(int i=1;i<=m;++i) dp[x][i]=w[x];for(int i=0;i0;--j) {for(int k=1;k
III. 数字转换
\(\text{Link}\)
思路分析
按题目要求将节点 \(1\sim n\) 全部连接,发现此时这些节点构成的图是一个森林,所以原题目转化为求森林最长的直径,直接 dp
设 \(dp_{p}\) 表示以 \(p\) 为根的最长链,不难得到:
\[dp_p=\begin{cases}0 &p\in\mathbf L\\\max\limits_{v\in\mathbf C_p}\{ dp_v\}+1&\text{otherwise}\end{cases}\]求答案时枚举每一个节点为根时的最长链长度与次长链长度相加即可
\[\text{Answer}=\max_{p\in\mathbf T}\left\{\max_{u,v\in\mathbf C_p}\{dp_u+dp_v\}\right\}\]时间复杂度 \(\Theta(n)\)
代码呈现
#includeusing namespace std;const int MAXN=5e4+1;vector edge[MAXN];int dp[MAXN][2],ans=0;bool vis[MAXN];inline int calc(int x) {int res=0;for(int i=1;i*i<=x;++i) {if(x%i) continue;res+=i+(x/i);if(i*i==x) res-=i;}return res-x;}inline void dfs(int p,int f) {vis[p]=true;vector res;for(int v:edge[p]) {if(v==f) continue;dfs(v,p);res.push_back(dp[v][0]+1);}sort(res.begin(),res.end(),greater());if(res.size()>0) dp[p][0]=res[0];if(res.size()>1) dp[p][1]=res[1];ans=max(ans,dp[p][0]+dp[p][1]);return ;}signed main() {int n;scanf("%d",&n);for(int i=1;i<=n;++i) {int t=calc(i);if(t>=i||t<=0) continue;edge[t].push_back(i);edge[i].push_back(t);}for(int i=1;i<=n;++i) if(!vis[i]) dfs(i,0);printf("%d\n",ans);return 0;}
IV. 战略游戏
\(\text{Link}\)
思路分析
经典的树上覆盖问题,考虑 dp,设 \(dp_{p,0/1}\) 表示不选或者选 \(p\) 节点后覆盖 \(p\) 的子树的最小代价
如果节点 \(p\) 不覆盖,那么 \(p\) 的每个儿子都必须覆盖,如果节点 \(p\) 被覆盖,那么 \(p\) 的每个儿子可以覆盖或不覆盖,得到如下状态转移方程:
\[\begin{aligned}dp_{p,0}&=\begin{cases}0 &p\in\mathbf L\\\sum\limits_{v\in\mathbf C_p} dp_{v,1} &\text{otherwise}\end{cases}\\dp_{p,1}&=\begin{cases}1&p\in\mathbf L\\\sum\limits_{v\in\mathbf C_p}\min(dp_{v,0},dp_{v,1})+1&\text{otherwise}\end{cases}\end{aligned}\]最终答案为 \(\min(dp_{r,0},dp_{r,1})\),时间复杂度 \(\Theta(n)\)
代码呈现
#includeusing namespace std;int dp[1505][2];vector edge[1505];void dfs(int x) {dp[x][1]=1;dp[x][0]=0;for(register int i=0;i
V. 皇宫看守
\(\text{Link}\)
思路分析
遇上一题类似,不过这一题是覆盖相邻点,所以每个点 \(p\) 被覆盖的来源有 \(3\) 种情况:
- \(p\) 的父亲覆盖了 \(p\),\(p\) 的儿子可能是自身覆盖或者被儿子覆盖
- \(p\) 的儿子覆盖了 \(p\),\(p\) 的儿子可能是被自身覆盖或者被儿子覆盖,注意转移时必须保证至少覆盖了一个儿子
- \(p\) 本身被覆盖了,\(p\) 的儿子三种覆盖情况都有可能
设 \(dp_{p,0/1/2}\) 分别表示节点 \(p\) 从以上第 \(1/2/3\) 中情况被覆盖的子树最小花费,得到如下状态转移方程:
\[\begin{aligned}dp_{p,0}&=\begin{cases}0&p\in\mathbf L\\\sum\limits_{v\in\mathbf C_p} \min(dp_{v,1},dp_{v,2}) &\text{otherwise}\end{cases}\\dp_{p,1}&=\begin{cases}0&p\in\mathbf L\\\min\limits_{v\in\mathbf C_p}\{dp_{v,2}-\min(dp_{v,1},dp_{v,2})\}+\sum\limits_{v\in\mathbf C_p} \min(dp_{v,1},dp_{v,2})&\text{otherwise}\end{cases}\\dp_{p,2}&=\begin{cases}w_p&p\in \mathbf L\\w_p+\sum\limits_{v\in\mathbf C_p}\min(dp_{v,0},dp_{v,1},dp_{v,2}) &\text{otherwise}\end{cases}\end{aligned}\]注:转移式 \(dp_{p,1}\) 前面的那一堆复杂的东西是选择至少一个节点 \(v\) 是由本身覆盖的最少新增价值,是为了保证 \(p\) 一定可以被儿子覆盖
最终的答案是 \(\min(dp_{r,1},dp_{r,2})\),因为根节点不可能再有父亲了
时间复杂度 \(\Theta(n)\)
代码呈现
#includeusing namespace std;const int MAXN=1501,INF=INT_MAX;int dp[MAXN][3],w[MAXN];vector edge[MAXN];void dfs(int p,int f) {int val=INF;for(int v:edge[p]) {if(v==f) continue;dfs(v,p);dp[p][0]+=min(dp[v][1],dp[v][2]);dp[p][1]+=min(dp[v][1],dp[v][2]);val=min(val,dp[v][2]-min(dp[v][1],dp[v][2]));dp[p][2]+=min(dp[v][0],min(dp[v][1],dp[v][2]));}dp[p][1]+=val,dp[p][2]+=w[p];return ;}int main() {int n;scanf("%d",&n);for(int i=1;i<=n;++i) {int u,tot;scanf("%d",&u);scanf("%d%d",&w[u],&tot);for(int j=1;j<=tot;++j) {int v;scanf("%d",&v);edge[u].push_back(v);edge[v].push_back(u);}}dfs(1,0);printf("%d",min(dp[1][1],dp[1][2]));}
VI. 加分二叉树
\(\text{Link}\)
思路分析
伪装成树形 dp 的区间 dp(误)
不考虑树形 dp,考虑区间 dp 设 \(dp_{l,r}\) 表示将区间 \([l,r]\) 整合成一棵树时的最大价值,状态转移方程如下:
\[dp_{l,r}=\begin{cases}1&l>r\\a_l&l=r\\\max\limits_{k=l}^{r} \{dp_{l,k-1}\times dp_{k+1,r}+a_k\}&\text{otherwise}\end{cases}\]记得记录转移方式然后 dfs 输出先序遍历,时间复杂度 \(\Theta(n^3)\)
代码呈现
#includeusing namespace std;long long f[50][50],root[50][50],val[50];void print(int start,int end) {if(start<=end) {printf("%d ",root[start][end]);if(start
VII. 旅游规划
\(\text{Link}\)
思路分析
求树的直径覆盖的点,首先 dp 求出每个点的最长链和树的直径,然后 dfs 输出所有可能的答案即可,时间复杂度 \(\Theta(n)\)
代码呈现
#include#define pii pairusing namespace std;const int MAXN=2e5+1;int dp[MAXN][2],tr[MAXN][2],ans;vector edge[MAXN],res;vector tmp[MAXN];inline void dfs(int p,int f) {for(int v:edge[p]) {if(v==f) continue;dfs(v,p);tmp[p].push_back(make_pair(dp[v][0]+1,v));}sort(tmp[p].begin(),tmp[p].end(),greater());if(tmp[p].size()>0) dp[p][0]=tmp[p][0].first;if(tmp[p].size()>1) dp[p][1]=tmp[p][1].first;ans=max(ans,dp[p][0]+dp[p][1]);return ;}inline void go(int p) {res.push_back(p);if(tmp[p].empty()) return ;go(tmp[p][0].second);for(int i=1;i0) {go(tmp[p][0].second);for(int i=1;i1) {go(tmp[p][1].second);for(int i=2;i
VIII. 周年纪念晚会
\(\text{Link}\)
思路分析
没有上司的舞会,经典树形 dp 题,设 \(dp_{p,0/1}\) 表示第 \(p\) 个人不来或来其子树可以获得的最大价值,得到如下状态转移方程:
\[\begin{aligned}dp_{p,0}&=\begin{cases}0&p\in\mathbf L\\\sum\limits_{v\in\mathbf C_p} \max(dp_{v,0},dp_{v,1})&\text{otherwise}\end{cases}\\dp_{p,1}&=\begin{cases}w_i&p\in\mathbf L\\w_i+\sum\limits_{v\in\mathbf L} dp_{v,0} &\text{otherwise}\end{cases}\end{aligned}\]时间复杂度 \(\Theta(n)\)
代码呈现
#includeusing namespace std;vector v[6005];int f[6005][2];bool vis[6005],isRoot[6005];void dp(int root) {vis[root]=true;for(int i=0;i
XI. 叶子的染色
\(\text{Link}\)
思路分析
根节点的位置并不影响答案,所以任选一个非叶节点进行 dp,设 \(dp_{p,0/1/2}\) 分别表示 \(p\) 被染成白色/染成黑色/未被染色时以 \(p\) 为根的子树都被覆盖的最小代价,可以得到如下状态转移方程:
\[\begin{aligned}dp_{p,0}&=\begin{cases}1 &p\in\mathbf L,c_p=0\\\infty &p\in\mathbf L,c_p=1\\1+\sum\limits_{v\in\mathbf C_p} \min(dp_{v,0}-1,dp_{v,1},dp_{v,2}) &\text{otherwise}\end{cases}\\dp_{p,1}&=\begin{cases}1 &p\in\mathbf L,c_p=1\\\infty &p\in\mathbf L,c_p=0\\1+\sum\limits_{v\in\mathbf C_p}\min(dp_{v,0},dp_{v,1}-1,dp_{v,2})&\text{otherwise}\end{cases}\\dp_{p,2}&=\begin{cases}1 &p\in\mathbf L\\\sum\limits_{v\in\mathbf C_p}\min(dp_{v,0},dp_{v,1},dp_{v,2})&\text{otherwise}\end{cases}\end{aligned}\]注:
- \(dp_{p,0}\) 从 \(dp_{v,0}\) 转移而来时,可以不用在 \(v\) 染色,因为 \(p\) 同样染色了,可以直接贡献 \(v\) 的子树,所以转移时 \(-1\),\(dp_{p,1}\) 的转移同理
- \(dp_{p,2}\) 的边界条件是 \(1\),因为 \(p\) 如果不染色,其他节点至少要有一个染色
最终答案是 \(\min(dp_{r,0},dp_{r,1},dp_{r,2})\)
时间复杂度 \(\Theta(m)\)
代码呈现
#includeusing namespace std;const int MAXN=1e4+1,INF=1e9;int c[MAXN],dp[MAXN][3],n,m;vector edge[MAXN];inline void dfs(int p,int f) {if(p<=n) {dp[p][c[p]]=dp[p][2]=1;dp[p][c[p]^1]=INF;} else {dp[p][0]=dp[p][1]=1;}for(int v:edge[p]) {if(v==f) continue;dfs(v,p);dp[p][0]+=min(dp[v][0]-1,min(dp[v][1],dp[v][2]));dp[p][1]+=min(dp[v][1]-1,min(dp[v][0],dp[v][2]));dp[p][2]+=min(dp[v][2],min(dp[v][0],dp[v][1]));}return ;}signed main() {scanf("%d%d",&m,&n);for(int i=1;i<=n;++i) scanf("%d",&c[i]);for(int i=1;i
X. 骑士
\(\text{Link}\)
思路分析
基环树上 dp,如果将每个骑士与仇恨的人连一条边,则每条边上至多选一个人,转化成普通 dp 即可,套用周年纪念晚会的模板即可
注意:
- 本题的图是基环树森林
- 基环树上 dp 需要先找到环,然后将环上任意两个节点断开进行 dp
- 如果将每个骑士单向连接至他的仇人,则基环树是一颗内向基环树,在内向基环树上找环更方便,不用构造无向边
- 如果将每个骑士单向连接至仇恨他的人,则基环树是一颗外向基环树,在外向基环树上 dp 更加方便
- dp 的时候如果通过环上的边连回树根时,不能从树根状态转移
- 为了方便起见,我们在 dp 的时候不选树根,防止环上的其他节点与树根互相仇恨
时间复杂度 \(\Theta(n)\)
代码呈现
#includeusing namespace std;#define int long longconst int MAXN=1e6+1;vector edge[MAXN];int f[MAXN],w[MAXN],dp[MAXN][2],root;bool vis[MAXN];inline void dfs(int p) {vis[p]=true;dp[p][0]=0,dp[p][1]=w[p];for(int v:edge[p]) {if(v==root) continue;dfs(v);dp[p][0]+=max(dp[v][0],dp[v][1]);dp[p][1]+=dp[v][0];}return ;}signed main() {int n,res=0; scanf("%lld",&n);for(int i=1;i<=n;++i) {int v;scanf("%lld%lld",&w[i],&v);edge[v].push_back(i);f[i]=v;}for(int i=1;i<=n;++i) {if(vis[i]) continue;int x=i;while(!vis[f[x]]) vis[x]=true,x=f[x];int lst=0;dfs(root=x); lst=dp[x][0];dfs(root=f[x]);res+=max(lst,dp[f[x]][0]);}printf("%lld\n",res);return 0;}
区间 dp
I. 石子合并
\(\text{Link}\)
思路分析
首先拆环为链,然后再在链上考虑区间 dp,令 \(dp_{l,r,0/1}\) 分别表示将区间 \([l,r]\) 合并为一个元素后的最大/最小权值
不难的出如下状态转移方程:
\[dp_{l,r,0}=\begin{cases}a_l&l=r\\\max\limits_{k=l}^{r-1}\{dp_{l,k,0}+dp_{k+1,r,0}\}&\text{otherwise}\end{cases}\]\[dp_{l,r,1}=\begin{cases}a_l&l=r\\\min\limits_{k=l}^{r-1}\{dp_{l,k,1}+dp_{k+1,r,1}\}&\text{otherwise}\end{cases}\]按区间长度从小到大枚举,对于每个区间枚举中间断点 \(k\),时间复杂度 \(\Theta(n^3)\)
注意:拆环为链之后,每个长度为 \(n\) 的区间的 dp 值未必相等,而且可能同时作为答案,需要依次统计
代码呈现
#includeusing namespace std;int fMin[210][210],fMax[210][210],sum[210],a[210];int main() {int n;cin>>n;for(int i=1;i<=n;i++) {cin>>a[i];a[i+n]=a[i];}for(int i=1;i<=n*2;i++) {sum[i]=sum[i-1]+a[i];}for(int l=1;l
II. 能量项链
\(\text{Link}\)
思路分析
和上一题一样的区间 dp 模板题,直接拆环为链,令 \(dp_{l,r}\) 为合并区间 \([l,r]\) 获得的最大价值,状态转移方程式如下:
\[dp_{l,r}=\begin{cases}a_l&l=r\\\max\limits_{k=l}^{r-1} \{dp_{l,k}\times a_{k+1}\times dp_{k+1,r}\} &\text{otherwise}\end{cases}\]时间复杂度 \(\Theta(n^3)\)
代码呈现
#include using namespace std;const int MAXN=3e2+1;int dp[MAXN][MAXN],s[MAXN],ans;int main() {int n;scanf("%d",&n);for(int i=1;i<=n;++i) {scanf("%d",&s[i]);s[i+n]=s[i];}int m=n<<1;for(int l=1;l
III. 凸多边形的划分
\(\text{Link}\)
思路分析
同样环上区间 dp,但是不用拆环为链
注意到如果将凸多边形的第 \(l\) 个顶点到第 \(r\) 个顶点分割成若干个小三角形,则必然连接顶点 \(l,r\),等价于将顶点区间 \([l,r]\) 看成一个新的凸多边形
所以可以令 \(dp_{l,r}\) 为将第 \(l\) 个顶点到第 \(r\) 个顶点分割成若干个小三角形的最小代价,状态转移方程如下:
\[dp_{l,r}=\begin{cases}0 &r-l+1\le 2\\\min\limits_{k=l}^r \{dp_{l,k}+dp_{k,r}+a_l\times a_k\times a_r\} &\text{otherwise}\end{cases}\]记得开 __int128
或者高精,时间复杂度 \(\Theta(n^3)\)
代码呈现
#include#define ll __int128const ll INF=1e30;inline ll read() {ll x=0;char ch=getchar();while(!isdigit(ch)) ch=getchar();while(isdigit(ch)) {x=x*10+(ch-"0");ch=getchar();}return x;}inline void write(ll x) {if(x>=10) write(x/10);putchar((char)(x%10+"0"));return ;}ll a[55],dp[55][55];signed main() {int n;scanf("%d",&n);for(int i=1;i<=n;++i) a[i]=read();for(int len=2;len
IV. 括号配对
\(\text{Link}\)
思路分析
很像 CSP-S2021 T2 的题,同样考虑区间 dp,设 \(dp_{l,r}\) 表示使得 \([l,r]\) 变成 GBE 的最小操作次数,直接枚举断点得到如下状态转移:
\[dp_{l,r}=\begin{cases}0 &l>r\\1 &l=r\\\max\limits_{k=l}^{r-1} \{dp_{l,k}+dp_{k+1,r}\}&\text{otherwise}\end{cases}\]类似 CSP-S2021 T2,每次转移的时候同样考虑需不需要将两边的括号通过添加来配对,得到如下方案:
\[dp_{l,r}=\begin{cases}dp_{l+1,r-1}&s_l=\texttt{[},s_r=\texttt] \lor s_l=\texttt(,s_r=\texttt)\\dp_{l+1,r}+1&s_l=\texttt{[},s_r\neq\texttt] \lor s_l=\texttt(,s_r\neq\texttt)\\dp_{l,r-1}+1&s_l\neq\texttt{[},s_r=\texttt] \lor s_l\neq\texttt(,s_r=\texttt)\\\end{cases}\]综合两种转移,时间复杂度 \(\Theta(n^3)\)
代码呈现
#includeusing namespace std;char s[105];int dp[105][105];signed main() {int n;scanf("%s",s+1);n=strlen(s+1);memset(dp,0x3f,sizeof(dp));for(int i=1;i<=n;++i) dp[i][i]=1;for(int i=1;i
V. 分离与合体
\(\text{Link}\)
思路分析
考验阅读理解的区间 dp 题(误)
同样设 \(dp_{l,r}\) 表示取得 \([l,r]\) 之间每个钥匙的最大价值,状态转移方程如下:
\[dp_{l,r}=\begin{cases}0&l=r\\\max\limits_{k=l}^{r-1} \{dp_{l,k}+dp_{k+1,r}+(a_l+a_r)\times a_k\} &\text{otherwise}\end{cases}\]记得纪录转移方案,输出方案的时候请使用 BFS,时间复杂度 \(\Theta(n^3)\)
代码呈现
#include#define int long long#define pii pairusing namespace std;int a[301],dp[301][301],f[301][301];signed main() {int n;scanf("%lld",&n);for(int i=1;i<=n;++i) scanf("%lld",&a[i]);memset(dp,-0x3f,sizeof(dp));for(int i=1;i<=n;++i) dp[i][i]=0;for(int len=1;lendp[l][r]) dp[l][r]=w,f[l][r]=k;}}}printf("%lld\n",dp[1][n]);queue q;q.push(make_pair(1,n));while(!q.empty()) {pii p=q.front();q.pop();if(p.first==p.second) continue;int tr=f[p.first][p.second];printf("%lld ",tr);q.push(make_pair(p.first,tr));q.push(make_pair(tr+1,p.second));}return 0&putchar("\n");}
VI. 矩阵取数游戏
\(\text{Link}\)
思路分析
经典区间 dp,对于每行分开考虑,设 \(dp_{i,l,r}\) 将 \([l,r]\) 这个区间看做一整行的答案,每次转移的时候,之前获得的答案要记得 \(\times2\)
\[dp_{l,r}=\begin{cases}2a_l &l=r\\2\max(dp_{l+1,r}+a_l,dp_{l,r-1}+a_r) &\text{otherwise}\end{cases}\]本题对 \(2^i\) 的处理并没有直接体现,而是体现在从短区间转移到长区间时的 \(\times2\),时间复杂度 \(\Theta(n^2)\)
另注:本题需要 __int128
或高精
代码呈现
#includeusing namespace std;__int128 ans=0,f[100][100],mp[100][100];__int128 read() {__int128 x=0;int f=1;char input=getchar();while(!isdigit(input)) {if(input=="-") {f=-1;}input=getchar();}while(isdigit(input)) {x=x*10+input-"0";input=getchar();}return x*f;}void write(__int128 x) {if(x>10) {write(x/10);}putchar(x%10+"0");return ;}int main() {int n,m;scanf("%d%d",&n,&m);for(int i=1;i<=n;i++) {for(int j=1;j<=m;j++) {mp[i][j]=read();}}for(int row=1;row<=n;row++) {for(int i=1;i<=m;++i) f[i][i]=2*mp[row][i];for(int l=1;l<=m;l++) {for(int i=1;i+l<=m;i++) {int j=i+l; f[i][j]=max(2*f[i+1][j]+2*mp[row][i],2*f[i][j-1]+2*mp[row][j]);}}ans+=f[1][m];}write(ans);return 0;}
单调队列优化 dp
I. 滑动窗口
\(\text{Link}\)
思路分析
单调队列模板题,在维护的队列中元素满足单调性的前提下,记得从队首按时序要求删除不符合要求的答案
时间复杂度 \(\Theta(n)\)
代码呈现
#includeusing namespace std;const int MAXN=1e6+1; struct node {int data,rank;};deque q[2];int ans[MAXN][2];int main(){int n,m,input,turn=0;scanf("%d%d",&n,&m);for(int i=1;i<=n;i++) {scanf("%d",&input);while(!q[0].empty()&&q[0].back().data<=input) q[0].pop_back();q[0].push_back((node){input,i});while(!q[1].empty()&&q[1].back().data>=input) q[1].pop_back();q[1].push_back((node){input,i});if(i>m){++turn;while(q[0].front().rank<=turn) q[0].pop_front();ans[turn][0]=q[0].front().data;while(q[1].front().rank<=turn) q[1].pop_front();ans[turn][1]=q[1].front().data;}else if(i==m) {ans[0][0]=q[0].front().data;ans[0][1]=q[1].front().data;}}for(int i=0;i<=turn;i++) printf("%d ",ans[i][1]);puts("");for(int i=0;i<=turn;i++) printf("%d ",ans[i][0]);puts("");return 0;}
II. 最大连续和
\(\text{Link}\)
思路分析
设 \(dp_i\) 表示以第 \(i\) 个元素为结尾的序列中,长度不超过 \(m\) 的序列中的最大和, \(sum_i\) 表示数组 \(a_i\) 的前缀和,不难得到如下转移方程:
\[\begin{aligned}dp_i&=\max_{j=i-m}^{i-1} \{sum_i-sum_j\}\\&=sum_i-\min_{j=i-m}^{i-1}\{sum_j\}\end{aligned}\]不难看出状态转移时只需要对于权值 \(sum_i\) 维护一个长度为 \(m\) 的递增单调队列即可,答案为 \(\max\limits_{i=1}^n\{dp_i\}\)
时间复杂度 \(\Theta(n)\)
代码呈现
#include#define int long longusing namespace std;const int MAXN=2e5+1,INF=1e18;int s[MAXN],q[MAXN];signed main() {int n,m;scanf("%lld%lld",&n,&m);for(int i=1;i<=n;++i) scanf("%lld",&s[i]),s[i]+=s[i-1];int head=1,tail=1,res=-INF;for(int i=1;i<=n;++i) {while(head<=tail&&i-q[head]>m) ++head;if(head<=tail) res=max(res,s[i]-s[q[head]]);while(head<=tail&&s[q[tail]]>s[i]) --tail;q[++tail]=i;}printf("%lld\n",res);return 0;}
III. 修剪草坪
\(\text{Link}\)
思路分析
设 \(dp_{i,0/1}\) 表示考虑到第 \(i\) 个位置不选/选时前 \(i\) 位的答案,\(sum_i\) 为 \(E_i\) 的前缀和,状态转移 \(dp_{i,1}\) 时,枚举上一个没有选择的位置即可,状态转移方程如下:
\[\begin{aligned}dp_{i,0}&=\begin{cases}0 &i=0\\\max(dp_{i-1,0},dp_{i-1,1}) &\text{otherwise}\end{cases}\\dp_{i,1}&=\begin{cases}0&i=0\\\max\limits_{j=i-k}^{i-1} \{dp_{j,0}+sum_i-sum_j\}&\text{otherwise}\end{cases}\end{aligned}\]对于 \(dp_{i,1}\) 的转移,可以通过对于权值 \(dp_{i,0}-sum_i\) 维护一个长度为 \(k\) 的递减单调队列即可
时间复杂度 \(\Theta(n)\)
代码呈现
#include#define int long longusing namespace std;const int MAXN=1e5+1;int s[MAXN],q[MAXN],dp[MAXN][2];signed main() {int n,k;scanf("%lld%lld",&n,&k);for(int i=1;i<=n;++i) scanf("%lld",&s[i]),s[i]+=s[i-1];int head=0,tail=0;for(int i=1;i<=n;++i) {dp[i][0]=max(dp[i-1][1],dp[i-1][0]);while(head<=tail&&q[head]
IV. 旅行问题
\(\text{Link}\)
思路分析
顺时针逆时针相似,这里以顺时针为例,环上问题,首先考虑拆环为链,然后预处理出从 \(1\) 出发到达第 \(i+1\) 个点时耗费的的油量,此时如果判断从点 \(k\) 出发是否可行,则需满足 \(\forall i\in[k,k+n],s_i\ge s_{k-1}\),不难发现只需要对于权值 \(s_i\) 维护一个长度为 \(n\) 的递增单调队列即可
注意解决逆时针时点 \(i\) 到下一个点的距离是 \(d_{i-1}\)
时间复杂度 \(\Theta(n)\)
代码呈现
#include#define int long longusing namespace std;const int MAXN=2e6+1;int s[MAXN],q[MAXN],p[MAXN],d[MAXN],n;bool ok[MAXN];signed main() {scanf("%lld",&n);for(int i=1;i<=n;++i) scanf("%lld%lld",&p[i],&d[i]);for(int i=1;i<=n;++i) s[i]=s[i-1]+p[i]-d[i];for(int i=1;i<=n;++i) s[i+n]=s[i+n-1]+p[i]-d[i];int head=1,tail=0;for(int i=1;i<=n;++i) {while(head<=tail&&s[q[tail]]>s[i]) --tail;q[++tail]=i;}for(int i=1;i<=n;++i) {while(head<=tail&&q[head]=s[i-1]) ok[i]=true;while(head<=tail&&s[q[tail]]>s[i+n]) --tail;q[++tail]=i+n;}int tmp=d[n];for(int i=n;i>1;--i) d[i]=d[i-1];d[1]=tmp;reverse(p+1,p+n+1);reverse(d+1,d+n+1);for(int i=1;i<=n;++i) s[i]=s[i-1]+p[i]-d[i];for(int i=1;i<=n;++i) s[i+n]=s[i+n-1]+p[i]-d[i];head=1,tail=0;for(int i=1;i<=n;++i) {while(head<=tail&&s[q[tail]]>s[i]) --tail;q[++tail]=i;}for(int i=1;i<=n;++i) {while(head<=tail&&q[head]=s[i-1]) ok[n-i+1]=true;while(head<=tail&&s[q[tail]]>s[i+n]) --tail;q[++tail]=i+n;}for(int i=1;i<=n;++i) {if(ok[i]) puts("TAK");else puts("NIE");}return 0;}
V. Banknotes
\(\text{Link}\)
思路分析
简单的多重背包模板,单调队列或二进制优化维护都可以,这里选择的是二进制优化
将一件价值为 \(b_i\),有 \(c_i\) 个的物品分别拆成价值 \(2^0\times b_i,2^1\times b_i,2^2\times b_i\cdots\) 的物品,得到新的若干件货币的价值 \(v_i\) 与其权重 \(w_i\)(即其个数),然后采用 01 背包的策略进行优化
设 \(dp_{i,j}\) 表示前 \(i\) 件物品凑出 \(j\) 的面值的最小花费,得到如下状态转移方程:
\[dp_{i,j}=\begin{cases}0 &i=0,j=0\\\infty &i=0,j>0\\\min(dp_{i-1,j},dp_{i-1,j-v_i}+w_i)&\text{otherwise}\end{cases}\]通过滚动数组优化掉第一维即可
时间复杂度 \(\Theta(kn\log n)\)
代码呈现
#include#define int long longusing namespace std;const int MAXN=5e3+1,MAXK=2e4+1;int w[MAXN],v[MAXN],dp[MAXK];int b[MAXN],c[MAXN];signed main() {memset(dp,0x3f,sizeof(dp));int n,cnt=0,k;scanf("%lld",&n);for(int i=1;i<=n;++i) scanf("%lld",&b[i]);for(int i=1;i<=n;++i) scanf("%lld",&c[i]);scanf("%lld",&k);for(int i=1;i<=n;++i) {for(int t=1;t<=c[i];t=(t<<1)) {w[++cnt]=b[i]*t;v[cnt]=t;c[i]-=t;} if(c[i]) {w[++cnt]=b[i]*c[i];v[cnt]=c[i];}}dp[0]=0;for(int i=1;i<=cnt;++i) {for(int j=k;j>=w[i];--j) {dp[j]=min(dp[j],dp[j-w[i]]+v[i]);}}printf("%lld\n",dp[k]);return 0;}
VI. 烽火传递
\(\text{Link}\)
思路分析
类似第三题,设 \(dp_{i,0/1}\) 表示第 \(i\) 个位置不设置或设置时,前 \(i\) 个烽火台的最小花费,得到如下状态转移方程
\[\begin{aligned}dp_{i,0}&=\begin{cases}0&i=0\\\max\limits_{j=i-m}^{i-1} \{dp_{j,1}\}&\text{otherwise}\end{cases}\\dp_{i,1}&=\begin{cases}0 &i=0\\\max(dp_{i-1,0},dp_{i-1,1})+a_i &\text{otherwise}\end{cases}\end{aligned}\]状态转移时以 \(dp_{i,1}\) 作为权值维护一个长度为 \(k\) 的递减单调队列即可
时间复杂度 \(\Theta(n)\)
代码呈现
#include#define int long longusing namespace std;const int MAXN=2e5+1;int a[MAXN],dp[MAXN][2],q[MAXN];signed main() {memset(dp,0x3f,sizeof(dp));int n,m;scanf("%lld%lld",&n,&m);for(int i=1;i<=n;++i) scanf("%lld",&a[i]);dp[0][0]=dp[0][1]=0;int head=1,tail=1;for(int i=1;i<=n;++i) {dp[i][1]=min(dp[i-1][0],dp[i-1][1])+a[i];while(head<=tail&&q[head]<=i-m) ++head;dp[i][0]=dp[q[head]][1];while(head<=tail&&dp[q[tail]][1]>dp[i][1]) --tail;q[++tail]=i;}printf("%lld\n",min(dp[n][0],dp[n][1]));return 0;}
VII. 绿色通道
\(\text{Link}\)
思路分析
简单题,再上一题的基础 dp 上用二分枚举最长空串长度即可,用耗时检验是否可行
时间复杂度 \(\Theta(n\log n)\)
代码呈现
#include#define int long longusing namespace std;const int MAXN=5e4+1;int a[MAXN],dp[MAXN][2],q[MAXN],n,t;inline bool check(int v) {memset(dp,0x3f,sizeof(dp));dp[0][0]=dp[0][1]=0;int head=1,tail=1;for(int i=1;i<=n;++i) {dp[i][1]=min(dp[i-1][0],dp[i-1][1])+a[i];while(head<=tail&&q[head]dp[i][1]) --tail;q[++tail]=i;}return min(dp[n][0],dp[n][1])<=t;}signed main() { scanf("%lld%lld",&n,&t);for(int i=1;i<=n;++i) scanf("%lld",&a[i]);int l=0,r=n,res=-1;while(l<=r) {int mid=(l+r)>>1;if(check(mid)) res=mid,r=mid-1;else l=mid+1;}printf("%lld\n",res);return 0;}
VII. 理想的正方形
\(\text{Link}\)
思路分析
对于每一行维护一个长度为 \(n\) 的单调队列,求出每个数所在行往前 \(n\) 位的区间中的最大最小值
然后枚举正方形单位右下角点,查询向上 \(k\) 行的最大最小值暴力即可
时间复杂度 \(\Theta(a\times b\times n)\)
代码呈现
#includeconst int MAXN=1001,INF=1e9;int min[MAXN][MAXN],max[MAXN][MAXN];int a[MAXN][MAXN],q[MAXN];signed main() {int n,m,k,head,tail;scanf("%d%d%d",&n,&m,&k);for(int i=1;i<=n;++i) {for(int j=1;j<=m;++j) {scanf("%d",&a[i][j]);}}for(int i=1;i<=n;++i) {head=1,tail=0;for(int j=1;j<=m;++j) {while(head<=tail&&q[head]<=j-k) ++head;min[i][j]=a[i][j];if(head<=tail) min[i][j]=std::min(min[i][j],a[i][q[head]]);while(head<=tail&&a[i][q[tail]]>a[i][j]) --tail;q[++tail]=j;}}for(int i=1;i<=n;++i) {head=1,tail=0;for(int j=1;j<=m;++j) {while(head<=tail&&q[head]<=j-k) ++head;max[i][j]=a[i][j];if(head<=tail) max[i][j]=std::max(max[i][j],a[i][q[head]]);while(head<=tail&&a[i][q[tail]]
IX. 股票交易
\(\text{Link}\)
思路分析
类似背包(大概?)的一道 dp 题,思路较简单,细节处理和代码实现有一定难度
设 \(dp_{i,j}\) 表示在第 \(i\) 天持有 \(j\) 支股票所获得的最大利润
边界条件:
\[dp_{0,j}=\begin{cases}0&j=0\\-\infty&\text{otherwise}\end{cases}\]考虑按不同操作进行分类讨论状态转移
- 第 \(i\) 天什么都不做
此时的状态转移方程最简单,直接从前一天转移过来即可:
\[dp_{i,j}\gets dp_{i-1,j}\]- 第 \(i\) 天首次买入股票
此时状态转移直接从 \(0\) 只股票买到 \(j\) 只股票,注意购买股票数量的限制
\[\forall j\in[0,AS_i],dp_{i,j}\gets -AP_i\times j\]- 第 \(i\) 天买股票
由于两次买股票有时间限制,所以从第 \(i-w-1\) 天的状态转移,枚举之前的股票数
\[\begin{aligned}dp_{i,j} &\gets \max_{k=j-AS_i}^{j}\{dp_{i-w-1,k}-(j-k)\times AP_i\}\\&=\max_{k=j-AS_i}^{j}\{dp_{i-w-1,k}+k\times AP_i\}-j\times AP_i\end{aligned}\]不难发现状态转移时只需要对于权值 \(dp_{i-w-1,k}+k\times AP_i\) 维护一个长度为 \(AS_i\) 的单调队列即可
- 第 \(i\) 天卖股票
同上,可得状态转移方程:
\[\begin{aligned}dp_{i,j}&\gets\max_{k=i}^{i+BS_i}\{dp_{i-w-1,k}+(k-j)\times BP_i\}\\&=\max_{w=i}^{i+BS_i} \{dp_{i-w-1,k}+k\times BP_i\}-j\times BP_i\end{aligned}\]同样维护一个长度为 \(BS_i\) 的单调队列即可,注意枚举状态 \(j\) v的时候要倒序
代码呈现
#includeusing namespace std;const int MAXN=2001;int dp[MAXN][MAXN],q[MAXN];signed main() {memset(dp,-0x3f,sizeof(dp));int n,m,k,head,tail;scanf("%d%d%d",&n,&m,&k);for(int i=1;i<=n;++i) {int ap,bp,as,bs;scanf("%d%d%d%d",&ap,&bp,&as,&bs);for(int j=0;j<=as;++j) dp[i][j]=max(dp[i][j],-j*ap);for(int j=0;j<=m;++j) dp[i][j]=max(dp[i][j],dp[i-1][j]);if(i<=k) continue;head=1,tail=0;for(int j=0;j<=m;++j) {while(head<=tail&&q[head]=0;--j) {while(head<=tail&&q[head]>j+bs) ++head;while(head<=tail&&dp[i-k-1][q[tail]]+q[tail]*bp
-
当前视讯!Python的保留字、标识符、变量的定义、常用数据类型、数据类型转换
Python包含的保留字可以执行如下命令进行查看:importkeyword keyword关键词print(keyword kwlist) ...
来源: 一本通动态规划篇解题报告
当前视讯!Python的保留字、标识符、变量的定义、常用数据类型、数据类型转换
砍单三大产品线 苹果市值跌破2万亿美元 富士康表态
世界热点评!抄底吗?知名投资人段永平:已将手上腾讯美股换成苹果
天天观速讯丨二次元外观下带来澎湃性能!铭瑄MS RTX 4070 Ti OC12G璦珈评测:最强192bit显卡非它莫属
10升好身材!ROG发布冰刃X迷你机:居然有没发布的RTX 4070
金庸最成功的作品 来源自历史上让人耻辱的失败
焦点要闻:500吨级!中国最强液体火箭发动机的“家”快建好了
世界信息:CF13C. Sequence
看点:设计模式简单介绍
胡伟武:Linux桌面软件生态是x86和ARM“软肋” 龙芯希望一两年后全面超过
丰田总裁再度质疑电动汽车:这会让车企降低价值!
每日资讯:RTX 4090加持:ROG发布新款XG Mobile显卡坞
即时:三星发布全球首款8K 150寸投影仪:离墙几毫米即可完成投射
焦点热文:画面太美!男子意外发现交通卡余额有172万 官方回应真相无奈
天天速看:写给大忙人看的Go语言快速指南(中文翻译)
天天实时:[概率论与数理统计]笔记:2.2 随机变量的数字特征
Python:numpy模块最详细的教程
python3实现字符串的全排列的方法(无重复字符)两种解决方法
全球快播:python中可以处理word文档的模块:docx模块
环球即时看!宏碁发布eKinekt BD 3酷骑桌:工作之余还能骑动感单车
世界热讯:1.6亿美元是罚定了!印度法庭驳回谷歌推翻垄断案的请求
当前热点-17万元起真香!奇瑞最高颜值SUV星途瑶光盲订量已达6012台
环球热点评!2022广州车展压轴登场 这八款新能源新车千万不要错过
今头条!全新NT架构加持!腾讯QQ原生上架麒麟应用商店
天天微资讯!国产可乐为什么都在陆续消失?专家道出原因
哄娃神器 一公司推出可自动驾驶婴儿车:售价约2.2万人民币
画风有《魔兽》那味了:国产多端MMO《塔瑞斯世界》PV首曝
【环球热闻】男子一口气网购20个26元扫地机器人:场面壮观实测失望
男子为研究显卡到网吧一口气拿7块:网友调侃要都是4090赚大
环球微资讯!Float value issue in GLSL
【环球热闻】事件总线 + 函数计算构建云上最佳事件驱动架构应用
实时:C罗说世界杯唯一赢阿根廷的是沙特 这里是新挑战 不是养老
配大哥H9同款2.0T 国产豪华轿跑红旗H6官图发布:罕见中置双排气
全球热头条丨微软再次挑战谷歌:Bing或将推出ChatGPT AI版本
【全球时快讯】一加首次推出100W双口充电器:支持65W PD快充 首发229元
世界观点:华系车崛起!欧洲每10台新能源汽车 就有1台来自中国
MySQL——索引
全球关注:易基因|METTL3 通过调节m6A 修饰抑制口腔鳞状细胞癌安罗替尼敏感性 | 肿瘤研究
环球今头条!苹果app怎么上架
天天看热讯:有史以来第一个6GHz CPU i9-13900KS现身中国!要卖6500元?
【当前热闻】3999元用4年依然流畅!一加11开启预售
热头条丨奥迪A6L提车三天出问题 4、5挡异响!4S店同意退车
热文:河南一女生118元买网红甜品巴斯克蛋糕 收到后吐槽像烧饼:生日毁了
环球动态:60多岁男子高速逆行撞废比亚迪新车 刚提一个月心疼不已
JS逆向实战10——某集团RSA长加密
WINDOWS文本编辑器丨EmEditor功能简介
天天最资讯丨中国首批电子后视镜来了 吉利旗下路特斯首搭
微头条丨1000元大额券:骆驼户外徒步鞋179元起、夹棉冲锋衣199元起
长安马自达旗舰SUV大促:优惠7万多、还送5万公里延保
杨迪在淘宝真的火 阳敌手机壳搜索量暴涨2300倍
一加11搭载三星2K柔性微曲屏:安卓首发随帧变动技术
【K哥爬虫普法】大数据风控第一案:从魔蝎科技案件判决,看爬虫技术刑事边界
简讯:通过持续交付提升发布效率
三款免费强大的SSH工具食用指南
焦点速递!Rpmbuild原码打包成rpm包
微速讯:App在苹果上架难吗
快讯:00后员工吐槽老板创意土被约谈 网友:老一辈审美观需升级
苹果妥协了!iOS第三方商店有望上线:欧洲iPhone专享
全球新动态:女子关窗围炉煮茶3小时致一氧化碳中毒:医生科普紧急救助办法
天天即时看!北京2人坠冰身亡 专家科普发现冰面破裂该如何自救
今日快讯:IO、NIO、BIO傻傻分不清吗,让我对象告诉你~~
游客误把燃香扔进400年文物里:网友心痛!科普雍和宫须弥山
专家称《原神》唯美:展现出我国的高现代化水准
今头条!尚硅谷Vue2.0+3.0的笔记资料(cli开始)
天天消息!docx替换word属性打勾
【天天播资讯】贾跃亭的法拉第未来重返2023年CES FF 91完成一系列升级
【速看料】二手车商亏哭!日系车保值“神话”破灭 国人无视省油、小毛病少爱上新能源
全球今热点:曾称污染比燃油车更大!丰田章男再度质疑电动车
郑州大气!明日10点起发放年货消费券:全国用户都能用
每日速读!全球首款!外星人推出24.5寸500Hz IPS电竞显示器:0.5ms急速响应
浅析 Dubbo 3.0 中接口级地址推送性能的优化
环球快报:行走的救护员!凯迪拉克车主把AED放车上:还允许破窗使用
焦点!特斯拉自动辅助驾驶追尾事故车辆 前车车主当场被吓懵
Win11 22H2又出新Bug:文件管理器随机“突然出现”
【独家焦点】动物也懂协同合作 两只雪豹一起偷鸡:一只踩点、一只放哨
天天播报:雷军:相信旗舰机会全部标配无线充电
环球快资讯:实战Flink sql语法改造
快讯:上映仅半个月 《阿凡达2》拿下2022全球票房年度冠军:超越《壮志凌云2》
当前通讯!现货:抗原检测试剂盒3.9元/份(顺丰包邮)
NVIDIA发布RTX视频超分辨率技术:看视频也有“DLSS”了
世界新资讯:时隔15年 Qi2无线充电标准官宣:基于苹果MagSafe打造 磁吸将降临安卓
面试官:Docker 有几种网络模式?5 年工作经验都表示答不上来。。
梦想云图Node.JS服务 (网页CAD,在线CAD )
每日看点!upload-lab靶场
世界讯息:电动车充电自燃一家4口不幸遇难 现场惨烈:网友感慨为何在家充电?
百事通!新势力年交付突破百万背后 谁得意 谁失意?
环球新消息丨安卓阵营绝无仅有!一加11屏幕体验最接近苹果iPhone ProMotion
全球焦点!售价或低于20万 特斯全新入门新车效果图曝光:颜值挺高
焦点要闻:祖传1200万像素要终结!曝苹果iPhone 15将配备4800万像素
滑雪不会刹车女生一路靠吼下坡 网友调侃练河东狮吼:医生科普受伤有多严重
环球新动态:一加11今天发!起步就是12GB+256GB 拒绝凑数卡价位
PC主机消失不可避免:备胎随时上位
快看点丨华硕ROG发布首款四频Wi-Fi 7八爪鱼游戏路由:25Gbps、三万兆网口
天天实时:太突然!国产饮料巨头宣布破产:一代名饮国产可乐退场网友唏嘘
焦点播报:增程车是必然被淘汰的技术 谁买坑谁?理想、华为反驳
世界通讯!Spring IOC官方文档学习笔记(六)之自定义bean的特性
数据结构作业(三):直接插入排序 和 归并排序
当前信息:就因为一张朋友圈截图 全国的蒙脱石散都卖光了
只需钻入地下几千米 就有无穷能源!为啥没人干呢?