集训之各种dp
1.线性
「BZOJ1609」麻烦的聚餐
分别求一遍连续非下降/上升子序列长度,用总长减去,取最小值即可,主要\(O(n^2)\)优化
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn=3e5+5;
typedef long long ll;
int n,f[maxn],a[maxn],top,ans;
int main(){
// freopen("1.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
f[++top]=a[1];
for(int i=2;i<=n;++i){
if(a[i]>=f[top]){
f[++top]=a[i];
continue;
}
int x=upper_bound(f+1,f+1+top,a[i])-f;
f[x]=a[i];
}
ans=n-top;
f[top=1]=a[n];
for(int i=n-1;i>=1;--i){
if(a[i]>=f[top]){
f[++top]=a[i];
continue;
}
int x=upper_bound(f+1,f+1+top,a[i])-f;
f[x]=a[i];
}
printf("%d\n",min(ans,n-top));
return 0;
}
「P2066」机器分配
\(f[i][j]\)表示i个公司分j台机器所得的最大利润
转移方程:\(f[i][j]=max(f[i][j],f[i-1][k]+a[i][j-k]),1<=i<=n,1<=j<=m,0<=k<=j\)
Code
#include <cstdio>
#include <cmath>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=20;
int f[maxn][maxn],a[maxn][maxn],n,m,ans,xx,shu[maxn];
void B(int x,int y){
if (x==0) return;
for (int k=0;k<=y;k++){
if (ans==f[x-1][k]+a[x][y-k]){
ans=f[x-1][k];
B(x-1,k);
printf("%d %d\n",x,y-k);
break;
}
}
}
int main(){
// freopen("1.in","r",stdin);
scanf("%d%d",&n,&m);
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)
for(int j=1;j<=m;++j){
for(int k=0;k<=j;++k){
f[i][j]=max(f[i][j],f[i-1][k]+a[i][j-k]);
}
}
printf("%d\n",f[n][m]);
ans=f[n][m];
B(n,m);
return 0;
}
2.树形
没有上司的舞会
相邻节点不能在一起,0表示不参加,1表示参加。
主要要搞清0/1分别由谁转移,详见代码,不赘述。
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn=6000+5;
typedef long long ll;
struct Edge{int next,to;}e[maxn*2];
int n,a[maxn],f[maxn][2],cnt,x,y,head[maxn];
void Add(int x,int y){
e[++cnt].to=y;
e[cnt].next=head[x];
head[x]=cnt;
}
void dfs(int u,int fa){
f[u][1]=a[u];
int xx=0;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==fa)continue;
dfs(v,u);
f[u][1]=max(f[u][1],f[v][0]+a[u]);
xx+=max(f[v][1],f[v][0]);
}
f[u][0]=xx;
}
int main(){
// freopen("1.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%d",&a[i]);
for(int i=1;i<n;++i)scanf("%d%d",&x,&y),Add(x,y),Add(y,x);
scanf("%d%d",&x,&y);
dfs(1,0);
printf("%d\n",max(f[1][1],f[1][0]));
return 0;
}
小胖守皇宫
题目就是说,每相邻的两个节点必须有一个人守着,并有一定的花费,给你个节点花费,求花费最小。
0表示儿子守着,1是自己守,2是父亲守。
那个tot变量有些迷,待更新吧,树形的和后面的状压还得持续更新
Code
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
using namespace std;
const int maxn=1500+5,Inf=0x3f3f3f3f;
typedef long long ll;
struct Edge{int next,to;}e[maxn*2];
int n,a[maxn],f[maxn][3],cnt,x,m,k,head[maxn];
void Add(int x,int y){
e[++cnt].to=y;
e[cnt].next=head[x];
head[x]=cnt;
}
void dfs(int u,int fa){
f[u][1]=a[u];
int tot=Inf;
for(int i=head[u];i;i=e[i].next){
int v=e[i].to;
if(v==fa)continue;
dfs(v,u);
f[u][1]+=min(f[v][0],min(f[v][1],f[v][2]));
f[u][0]+=min(f[v][1],f[v][0]);
f[u][2]+=min(f[v][1],f[v][0]);
tot=min(tot,f[v][1]-f[v][0]);
}
if(tot>0)f[u][0]+=tot;
}
int main(){
//freopen("1.in","r",stdin);
scanf("%d",&n);
int aa;
for(int i=1;i<=n;++i){
scanf("%d%d%d",&k,&aa,&m);
a[k]=aa;
while(m--){
scanf("%d",&x);
Add(k,x);Add(x,k);
}
}
if(n==1)printf("%d\n",a[1]);
else{
dfs(1,0);
printf("%d\n",min(f[1][0],f[1][1]));
}
return 0;
}
3.区间
整数划分
和乘积最大那道题很像,不过更复杂一点,要输出。
f[i][j]表示前i位划分为m部分的最大乘积。(就这个部分关键要想到)
预处理出任意i~j位的数a[i][j]
转移:\(f[i][j]=max(f[i][j],f[k][j-1]*a[k+1][i]),j-1<=k<=i-1\)
边界:f[0][0]=1(因为是相乘,所以为1不能为0)
转移时记录前i位划分为j段的最后一个“*”前面那位数,递归输出。
Code
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=23;
ll a[maxn][maxn],f[maxn][maxn];
int t,hua[maxn][maxn],m,n,len;
char s[maxn];
void P(int x,int y){
if(y==0)return;
P(hua[x][y],y-1);
for(int i=hua[x][y]+1;i<=x;++i)printf("%c",s[i]);
printf(" ");
}
void C(){
memset(f,0,sizeof f);
memset(a,0,sizeof a);
memset(hua,0,sizeof hua);
memset(s,0,sizeof s);
}
void R(){
scanf(" %s %d",s+1,&m);
n=strlen(s+1),m=min(m,n);
for(int i=1;i<=n;++i)
for(int j=i;j<=n;++j) a[i][j]=a[i][j-1]*10LL+s[j]-'0';
memset(f,-1,sizeof f);
f[0][0]=1;
}
void Solve(){
for(int i=1;i<=n;++i)
for(int j=1;j<=min(i,m);++j)
for(int k=j-1;k<=i-1;++k)
if(f[i][j]<f[k][j-1]*a[k+1][i]) f[i][j]=f[k][j-1]*a[k+1][i],hua[i][j]=k;
}
void Pr(){
printf("%lld\n",f[n][m]);
P(n,m);
printf("\n");
}
int main(){//可怜的主函数
//freopen("1.in","r",stdin);
scanf("%d",&t);
while(t--){
C();R();
Solve();
Pr();
}
return 0;
}
矩阵连乘
题意是什么鬼我看不懂......
解释一下:
可以这么理解:对于三个连续的矩阵,分两步求,再将这两步结果相加。
A:2 * 3 B:3 * 4 C:4 * 5
(A * B) * C=(2 * 3 * 4)+(2 * 4 * 5)
A * ( B * C)=(3 * 4 * 5)+(2 * 3 * 5)
f[i][j]表示第i个到第j个的矩阵乘积最小运算量
转移:\(f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[i-1]*a[k]*a[j]),i<=k<j\)
边界:\(f[i][j]=Inf,f[i][i]=0,1<=i<=n\)
Code
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn=105,Inf=0x7f7f7f7f;
int n,f[maxn][maxn],a[maxn];
int main(){
// freopen("1.in","r",stdin);
memset(f,Inf,sizeof f);
scanf("%d",&n);
for(int i=0;i<=n;++i)scanf("%d\n",&a[i]);
for(int i=1;i<=n;++i)f[i][i]=0;
for(int l=2;l<=n;++l)
for(int i=1,j;i+l-1<=n;++i){
j=i+l-1;
for(int k=i;k<j;++k) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+a[i-1]*a[k]*a[j]);
}
printf("%d\n",f[1][n]);
return 0;
}
凸多边形的三角剖分
也是区间的套路,由简单衍生出来
先顺时针给各个顶点编上号,\(f[i][j]\)表示连接i号顶点和j号顶点,所形成的下面的一个多边形的最小剖分值。
转移:\(f[i][j]=min(f[i][k]+f[k][j]+a[i] * a[k] * a[j]),i+1<=k<=j-1\)
边界:初始化\(f[i][i+2]=a[i] * a[i+1] * a[i+2],(1<=i<=n-2)\)
Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=50+5;
int n;
ll a[maxn],f[maxn][maxn];
int main(){
//freopen("1.in","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;++i)scanf("%lld\n",&a[i]);
for(int i=1;i+2<=n;++i)f[i][i+2]=a[i]*a[i+1]*a[i+2];
for(int d=3;d<=n;++d){
for(int i=1;i+d-1<=n;++i){
int j=i+d-1;
for(int k=i+1;k<j;++k){
if(f[i][j])f[i][j]=min(f[i][j],f[i][k]+f[k][j]+a[k]*a[i]*a[j]);
else f[i][j]=f[i][k]+f[k][j]+a[k]*a[i]*a[j];
}
}
}
printf("%lld\n",f[1][n]);
return 0;
}
多边形
李煜东的《算法进阶指南》P284~286
我就不再解释啦
Code
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <cstring>
using namespace std;
const int maxn=50+5,Inf=1e9+10;
int n,data[maxn<<1];
int f[maxn<<1][maxn<<1][3];
int ff[maxn<<1];
char c[maxn<<1];
int q,w,e,r;
int ans_point=(-1)*Inf;
void Read(){
cin>>n;
for(int i=1;i<=n;++i){
cin>>c[i]>>data[i];
c[i+n]=c[i];
data[i+n]=data[i];
}
}
void Solve(){
for(int i=1;i<=n;++i){
for(int j=1;j<=(n<<1);++j){
for(int k=1;k<=(n<<1);++k){
f[j][k][1]=(-1)*Inf;
f[j][k][2]=Inf;
}
}
for(int j=1;j<=(n<<1);++j){
f[j][j][1]=f[j][j][2]=data[j];
if(c[j+1]=='t') f[j][j+1][1]=f[j][j+1][2]=data[j]+data[j+1];
else f[j][j+1][1]=f[j][j+1][2]=data[j]*data[j+1];
}
for(int l=2;l<=n;++l){
for(int j=i;j+l<=i+n;++j){
int k=j+l-1;
for(int x=j;x<k;++x){
if(c[x+1]=='t'){
f[j][k][1]=max(f[j][k][1],f[j][x][1]+f[x+1][k][1]);
f[j][k][2]=min(f[j][k][2],f[j][x][2]+f[x+1][k][2]);
}else{
q=f[j][x][1]*f[x+1][k][1];
w=f[j][x][2]*f[x+1][k][2];
e=f[j][x][1]*f[x+1][k][2];
r=f[j][x][2]*f[x+1][k][1];
f[j][k][1]=max(f[j][k][1],max(max(q,w),max(e,r)));
f[j][k][2]=min(f[j][k][2],min(min(q,w),min(e,r)));
}
}
}
}
ff[i]=f[i][i+n-1][1];
}
for(int i=1;i<=n;++i){
ans_point=max(ans_point,ff[i]);
}
printf("%d\n",ans_point);
for(int i=1;i<=n;++i){
if(ans_point==ff[i]) printf("%d ",i);
}
}
int main(){
// freopen("1.in","r",stdin);
Read();
Solve();
return 0;
}
低价回文
见\(soda\)的博客吧
我的Code
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
const int maxn=2e3+5;
int f[maxn][maxn],n,m,v[30];
char s[maxn],ss;
int main(){
// freopen("1.in","r",stdin);
scanf("%d%d %s",&n,&m,s+1);
for(int i=1,x,y;i<=m;++i)scanf(" %c%d%d",&ss,&x,&y),v[ss-'a']=min(x,y);
for(int i=m;i>=1;--i)
for(int j=i+1;j<=m;++j){
if(s[i]==s[j])f[i][j]=f[i+1][j-1];
else f[i][j]=min(f[i+1][j]+v[s[i]-'a'],f[i][j-1]+v[s[j]-'a']);
}
printf("%d\n",f[1][m]);
return 0;
}
4.状压
终于到了状压dp了,说实话我的状压dp真的不好,前几天集训时遇到状压的题我都不会,前两天没有时间,今天不能再拖了,总结一下。
互不侵犯
在\(N * N\)的棋盘里面放\(K\)个国王,使他们互不攻击,共有多少种摆放方案。国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共\(8\)个格子。
给你N和K的值,求方案数
\(1<=N<=9,0<=K<=N * N\).
我们用\(f[i][S][k]\)表示前i行放k个国王,且第i行状态为S时的方案数。状态0是不放国王,1是放国王。
边界是\(f[0][0][0]=1\),这样才能第一层转移到底层时为1(也可以直接将第一整行全预处理出来,方案数都为1)
转移是比较简单的,不需要常数了:\(f[i][S][k]+=f[i-1][s][k-cnt]\),(本行由上一行转移过来)这个k是S中1的个数,不一定是总数K,s是上一行的状态,与S不能冲突,(不能互相攻击)。
#include <cstdio>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn=1<<9;
int n,m;
ll f[10][maxn][82];
int lowbit(int x){return x&-x;}
int Q(int x){//求x的二进制中1的个数
int cnt=0;
for(int i=x;i;i-=lowbit(i))cnt++;
return cnt;
}
int main(){
scanf("%d%d",&n,&m);
f[0][0][0]=1;
int maxs=1<<n;
for(int i=1;i<=n;++i){
for(int S=0;S<maxs;++S){
int cnt=Q(S);
if(S&(S<<1))continue;
for(int s=0;s<maxs;++s){//s是上一行的状态枚举,与内层的k的枚举可以互换,但这样更省时间,因为特判会省去一些冗余循环
if(S&s||(S<<1)&s||(S>>1)&s)continue;
for(int k=cnt;k<=m;++k) f[i][S][k]+=f[i-1][s][k-cnt];
}
}
}
ll ans=0;
for(int S=0;S<maxs;++S)ans+=f[n][S][m];//最后结果是最后一行放k个国王的各种状态所对应的方案数之和
printf("%lld\n",ans);
return 0;
}
炮兵阵地
这个题目和上个差不多,除了不能相互攻击外,还有些地方不能放,最后问你的是最多的个数而不是方案数了。
\(f[i][S][s]\)是前\(i\)行中,第\(i\)行状态是\(S\),\(i-1\)行状态是\(s\)时的个数。
转移是枚举本行状态\(S\),算出该状态下本行的个数\(cnt\),用上一行的\(f[i-1][s][ss]\)再加上\(cnt\),得到\(i\)行状态为\(S\),上一行状态为\(s\)时的最大个数。
即:\(f[[i][S][s]=max(f[i][S][s],f[i-1][s][ss]+cnt)\).
其中\(ss\)是&i-2&行的状态,也需要枚举。另外本题有个优化,因为炮兵左右攻击范围挺大(2格),所以,尽管一行有M<=10个格子,但真正合法的状态数不超过60个,我们可以提前将1行的合法的状态编上号预处理出来,时间/空间复杂度都可减少,空间可减少大概八九十倍的样子。
#include <cstdio>
#include <algorithm>
using namespace std;
int n,m,tot;
const int maxn=1<<9;
int a[101],st[70],f[101][70][70];
char s[15];
bool J(int x){
if(x&(x<<1)||x&(x<<2))return 0;
return 1;
}
int Q(int x){
int cnt=0;
for(int i=st[x];i;i-=(i&-i))cnt++;
return cnt;
}
int main(){
scanf("%d%d", &n, &m);
for(int i=1; i<=n; i++){
scanf(" %s",s);
for(int j=0; j<m; j++){
if(s[j] == 'H') a[i]|=(1<<j);
}
}
int maxs=1<<m;
for(int i=0;i<maxs;++i)if(J(i))st[++tot]=i;
for(int i=1;i<=tot;++i)if(!(st[i]&a[1]))f[1][i][1]=Q(i);
for(int i=2;i<=n;++i){
for(int S=1;S<=tot;++S){
if(st[S]&a[i])continue;
int cnt=Q(S);
for(int s=1;s<=tot;++s){
if((st[S]&st[s])||(st[s]&a[i-1]))continue;
for(int ss=1;ss<=tot;++ss){
if((st[ss]&a[i-2])||(st[ss]&st[s])||(st[S]&st[ss]))continue;
f[i][S][s]=max(f[i][S][s],f[i-1][s][ss]+cnt);
}
}
}
}
int Max=0;
for(int i=1;i<=tot;++i)
for(int j=1;j<=tot;++j){
if((st[i]&a[n])||(st[j]&a[n-1])||(st[i]&st[j]))continue;
Max=max(Max,f[n][i][j]);
}
printf("%d\n",Max);
return 0;
}
旅游景点 Tourist Attractions
愤怒的小鸟
动物园
待更新吧......
集训之各种dp的更多相关文章
- 2016HUAS_ACM暑假集训4M - 基础DP
简单的0-1背包问题,大家都会做的.题意不想解释太多. 简述题目的案例及以几个关键 Sample Input 1 //测试组数T 5 10 ...
- 2016HUAS_ACM暑假集训4K - 基础DP
我不知道怎么用DP,不过DFS挺好用.DFS思路很明显,搜索.记录,如果刚好找到总价值的一半就说明搜索成功. 题目大意:每组6个数,分别表示价值1到6的物品个数.现在问你能不能根据价值均分. Samp ...
- LOJ.6074.[2017山东一轮集训Day6]子序列(DP 矩阵乘法)
题目链接 参考yww的题解.本来不想写来但是他有一些笔误...而且有些地方不太一样就写篇好了. 不知不觉怎么写了这么多... 另外还是有莫队做法的...(虽然可能卡不过) \(60\)分的\(O(n^ ...
- 暑假集训 || 状压DP
emm 位操作实现技巧: 获得第i位的数据: if(!(data & (1<< i))) 则data的第 i 位为0,else 为 1 设置第i位为1,data=(data | ...
- UOJ275 [清华集训2016] 组合数问题 【Lucas定理】【数位DP】
题目分析: 我记得很久以前有人跟我说NOIP2016的题目出了加强版在清华集训中,但这似乎是一道无关的题目? 由于$k$为素数,那么$lucas$定理就可以搬上台面了. 注意到$\binom{i}{j ...
- 【LOJ6077】「2017 山东一轮集训 Day7」逆序对 生成函数+组合数+DP
[LOJ6077]「2017 山东一轮集训 Day7」逆序对 题目描述 给定 n,k ,请求出长度为 n的逆序对数恰好为 k 的排列的个数.答案对 109+7 取模. 对于一个长度为 n 的排列 p ...
- 「长乐集训 2017 Day10」划分序列 (二分 dp)
「长乐集训 2017 Day10」划分序列 题目描述 给定一个长度为 n nn 的序列 Ai A_iAi,现在要求把这个序列分成恰好 K KK 段,(每一段是一个连续子序列,且每个元素恰好属于一 ...
- 【题解】P4247 [清华集训]序列操作(线段树修改DP)
[题解]P4247 [清华集训]序列操作(线段树修改DP) 一道神仙数据结构(DP)题. 题目大意 给定你一个序列,会区间加和区间变相反数,要你支持查询一段区间内任意选择\(c\)个数乘起来的和.对1 ...
- (2016北京集训十)【xsy1528】azelso - 概率期望dp
北京集训的题都是好题啊~~(于是我爆0了) 注意到一个重要的性质就是期望是线性的,也就是说每一段的期望步数可以直接加起来,那么dp求出每一段的期望就行了... 设$f_i$表示从$i$出发不回到$i$ ...
随机推荐
- 什么?你还不会获取地址栏(url)的值
function getUrlParam(name) {//封装方法 var reg = new RegExp("(^|&)" + name + "=([^&am ...
- BigDecimal的setScale常用方法(ROUND_UP、ROUND_DOWN、ROUND_HALF_UP、ROUND_HALF_DOWN)
BigDecimal的setScale四大常用方法总结 // 设置小数点后第三位数字一大一小观察效果BigDecimal num = new BigDecimal("3.3235667&qu ...
- 第12章 Java内存模型与线程
参考<深入理解Java虚拟机> 一.Java内存模型 1.Java内存模型 2.内存间交互操作 流程图: 3.volatile关键字 两个特性: 3.1.保证变脸对所有线程的可见性: 由 ...
- Python3 源码阅读-深入了解Python GIL
今日得到: 三人行,必有我师焉,择其善者而从之,其不善者而改之. 今日看源码才理解到现在已经是2020年了,而在2010年的时候,大佬David Beazley就做了讲座讲解Python GIL的设计 ...
- c#撸的控制台版2048小游戏
1.分析 最近心血来潮,突然想写一个2048小游戏.于是搜索了一个在线2048玩玩,熟悉熟悉规则. 只谈核心规则:(以左移为例) 1.1合并 以行为单位,忽略0位,每列依次向左进行合并,且每列只能合并 ...
- Node.js 学习笔记(一)
node.js说白了就是JavaScript. node.js的性能是php的86倍(大概). 在下载完后可以用命令行打开及运行. 什么是 Web 服务器? Web服务器一般指网站服务器,是指驻留 ...
- Selenium和ChromeDriver的安装与配置
安装安装selenium: win: pip install seleniumliunx: pip3 install selenium12安装ChromeDriver, 该工具供selenium使用C ...
- eval5: TypeScript编写的JavaScript解释器
eval5是基于TypeScript编写的JavaScript解释器,100%支持ES5语法. 项目地址:https://github.com/bplok20010/eval5 使用场景 浏览器环境中 ...
- mysql小数类型
原文链接:https://blog.csdn.net/weixin_42047611/article/details/81449663 MySQL 中使用浮点数和定点数来表示小数. 浮点类型有两种,分 ...
- css在 IE8下的兼容性
常用伪类选择器 IE7 IE8 IE9 :hover √ √ √ :focus × √ √ :first-child √ √ √ :last-child × × √ :first-of-type ...