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\)的博客吧
4.状压
.......我实在没时间写了,待更新吧