动态规划杂题
概率期望dp转这里 http://www.cnblogs.com/L-Memory/p/7352637.html
树形dp转这里 http://www.cnblogs.com/L-Memory/p/7470228.html
bzoj1705 (poj3602)
题意:有一列树,总共有n(n<=100000)棵,给定树的高度,要在相邻树和树之间架设电线,代价为c*(两树的高度差的绝对值),
但可以增高树的高度,需要代价为<增高高度的平方>
求最小代价
思路:f[i][j] = min(f[i-1][k]+|j-k|*c+(a[i]-j)*(a[i]-j))
摘出与k无关项得
f[i][j] = min(f[i-1][k]+|j-k|*c) + (a[i]-j)*(a[i]-j)
记P = min(f[i-1][k]+|j-k|*c) , Q = (a[i]-j)*(a[i]-j)
则f[i][j] = P + Q
P = min(A,B),其中
A = min(f[i-1][k]+(j-k)*c) (k<=j)
B = min(f[i-1][k]+(k-j)*c) (k>j)
A = min(f[i-1][k]-k*c) + j*c
B = min(f[i-1][k]+k*c) - j*c
记C[X][i] = min(f[X][k] - k*c) k∈[1,i]
记D[X][i] = min(f[X][k] + k*c) k∈[i,n]
则A = C[i-1][j] + j*c
则B = D[i-1][j+1] - j*c
显然C、D在任何时刻只需保存X=i-1一行的值
注意高度只能增高,所以h[i]∈[a[i],100]
利用辅助数组优化后,时间复杂度降为O(N*100)
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 100010
#define H 110
#define inf 0x3f3f3f3f
using namespace std;
int n,c,h,P,Q,A,B;
int a[N],C[H],D[H],f[N][H];
int main()
{
scanf("%d%d",&n,&c);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
h=a[1];
for(int i=1;i<=n;i++) h=max(h,a[i]);
h=min(h,100);
memset(f,0x3f,sizeof f);
for(int i=1;i<=n;i++)
{
if(i==1)
for(int j=a[i];j<=h;j++)
f[1][j]=(j-a[1])*(j-a[1]);
else
{
for(int j=a[i];j<=h;j++)
{
Q=(j-a[i])*(j-a[i]);
A=C[j]+j*c;
B=D[j+1]-j*c;
P=min(A,B);
f[i][j]=P+Q;
}
}
C[0]=D[h+1]=inf;
for(int j=1;j<=h;j++) C[j]=min(C[j-1],f[i][j]-j*c);
for(int j=h;j>=1;j--) D[j]=min(D[j+1],f[i][j]+j*c);
}
int ans=inf;
for(int i=1;i<=h;i++) ans=min(ans,f[n][i]);
printf("%d\n",ans);
return 0;
return 0;
return 0;
}
poj3666
题意:
给定一个序列,以最小代价将其变成单调增或单调减序列,代价看题目公式。
思路:dp[i][j]表示第i个数高度为第j个时的代价
假设前几个数组成的最大值为β,我们要考虑从0-β的改变值,然后不断推到第n个序列。
显然,这样的复杂度为0(Nβ^2),当然这样的复杂度显然是出事的。
像之前那样扫描的话,那么其实忽略了一个很重要的事实,就是在变到α(α<β),其实对于α+1~β之内不会对α造成影响,于是可以用一个最小值的临时变量储存在α之前的最小值,用这个更新dp即可,那样就少了一次扫β的复杂度复杂度变为O(Nβ);但β实在是太大了。
所以要离散化
#include<iostream>
#include<algorithm>
#include<cstdio>
#define Abs(a) ((a)>0?(a):-(a))
#define Mod(a,b) (((a)-1+(b))%(b)+1)
using namespace std;
const int N=2005;
const long long inf=0x7f7f7f7f;
int n;
int a[N],b[N];
long long int dp[N][N];
void solve()
{
for(int i=1;i<=n;i++)
{
long long mn=dp[i-1][1];
for(int j=1;j<=n;j++)
{
mn=min(mn,dp[i-1][j]);
dp[i][j]=Abs(a[i]-b[j])+mn;
}
}
long long ans=dp[n][1];
for(int i=1;i<=n;i++)
ans=min(ans,dp[n][i]);
printf("%lld\n",ans);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",a+i);
b[i]=a[i];
}
sort(b+1,b+n+1);
solve();
return 0;
}
codevs2205
题意:小C的老师给了他一个长度为N的数字序列,每个位置有一个整数,他需要小C帮他找到这个数字序列里面有多少个等差数列。
思路:f[i][j]表示以i为终点(且i不为起点),差为j的个数
穷举起点和第二项,然后更新第二项的值。
最后就需要加上n,以为一个元素也是等差数列。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1007
#define mod 9901
using namespace std;
int n,m,d,a[N];
long long ans,f[N][N<<2];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
d=a[j]-a[i]+1000;
f[j][d]=(f[i][d]+f[j][d]+1)%mod;
}
ans=n;
for(int i=1;i<=n;i++)
for(int j=0;j<=2000;j++)
ans=(ans+f[i][j])%mod;
printf("%lld\n",ans);
return 0;
return 0;
return 0;
}
codevs1253
某人喜欢按照自己的规则去市场买菜,他每天都列一个买菜的清单,自由市场的菜码放也有一个顺序,该人有一个特点,就是按顺序买菜,从不走回头路,当然,她希望能花最好的钱买到所有的菜,你能帮帮他吗?
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int pos[100001],pos1[101];
double f[100001][101],val[1000001];
int n,m,ans;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d",&pos1[i]);
for(int i=1;i<=m;i++)
scanf("%d%lf",&pos[i],&val[i]);
memset(f,127,sizeof f);
for(int i=1;i<=m;i++) f[i][0]=0;
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
{
f[i][j]=f[i-1][j];
if(pos[i]==pos1[j])
f[i][j]=min(f[i][j],f[i-1][j-1]+val[i]);
}
if(f[m][n]>=0x7fffffff) printf("Impossible\n");
else
printf("%.2f\n",f[m][n]);
return 0;
return 0;
return 0;
}
codevs1959
一个学校举行拔河比赛,所有的人被分成了两组,每个人必须(且只能够)在其中的一组,要求两个组的人数相差不能超过1,且两个组内的所有人体重加起来尽可能地接近。
思路:二维费用背包模型
设dp[i][j][k]表示前i个人选j个能否得到体重为k。
这样dp的转移有2种,不选(dp[i-1][j][k]),选(dp[i-1][j-1][k-w[i]])(w表示体重),
得到dp数组以后,再枚举可能的答案,然后求的差值最小的,得到答案
/*
二维费用背包模型
设dp[i][j][k]表示前i个人选j个能否得到体重为k。
这样dp的转移有2种,不选(dp[i-1][j][k]),选(dp[i-1][j-1][k-w[i]])(w表示体重),
得到dp数组以后,再枚举可能的答案,然后求的差值最小的,得到答案
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
bool dp[51][45000];
int n,w[111],s;
int main()
{
scanf("%d",&n);
for(int i = 1;i <= n;i ++) scanf("%d",&w[i]),s += w[i];
dp[0][0] = 1;
int k = (n+1)/2;
for(int i = 1;i <= n;i ++)
for(int j = k-1;j >= 0;j --)
for(int v = 450*i;v >= 0;v --)
if(dp[j][v])
dp[j+1][v+w[i]] = 1;
int minn=1<<30;
n = 0;
for(int i = 0;i <= 450*k;i ++)
{
if(dp[k][i] && abs(s - i*2) < minn) //看成s-i和i的差值
{
minn = abs(s-i*2);
if(i <= s/2) n = i;
else n = s-i;
}
}
printf("%d %d",n,s-n);
return 0;
}
codevs1043
设有N*N的方格图(N<=10,我们将其中的某些方格中填入正整数,而其他的方格中则放入数字0。如下图所示(见样例):
某人从图的左上角的A 点出发,可以向下行走,也可以向右走,直到到达右下角的B点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字0)。
此人从A点到B 点共走两次,试找出2条这样的路径,使得取得的数之和为最大。
思路:
设f[x1][y1][x2][y2]表示第一个人走到x1,y1点,第二个人走到x2,y2点的最优解。
方程为 f[x1][y1][x2][y2] =max(f[x1][y1][x2][y2],f[x1-1][y1][x2-1][y2],f[x1-1][y1][x2][y2-1],f[x1][y1-1][x2-1][y2],f[x1][y1-1][x2]
[y2-1]) + G[x1][y1] 若 x1 != x2 || y1 != y2,还应加上G[x2][y2]。即必须累加当前走过的路径上的数字,为防止两个人同时走到一个点而累加两次产生错误答案,判定一下。
#include <cstdio>
#include <iostream>
using namespace std;
int map[99][99];
int dp[55][55][55][55];
int main()
{
int n;
scanf("%d",&n);
while(1)
{
int x,y,c;
scanf("%d%d%d",&x,&y,&c);
if(!x&&!y&&!c) break;
map[x][y]=c;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=n;k++)
for(int l=1;l<=n;l++)
{
int s1=max(dp[i-1][j][k-1][l],dp[i-1][j][k][l-1]);
int s2=max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1]);
int pls=map[i][j]+map[k][l];
if(i==k&&j==l)
pls-=map[i][j];
dp[i][j][k][l]=max(s1,s2)+pls;
}
printf("%d",dp[n][n][n][n]);
return 0;
}
codevs1196 几乎同上
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 51
using namespace std;
int dp[N][N][N][N],a[N][N];
int n,m,ans;
void DP ()
{
int ans1,ans2;
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
for(int k=1;k<=m;k++)
for(int l=1;l<=n;l++)
{
ans1=max(dp[i-1][j][k-1][l],dp[i][j-1][k][l-1]);
ans2=max(dp[i-1][j][k][l-1],dp[i][j-1][k-1][l]);
dp[i][j][k][l]=max(ans1,ans2)+a[i][j]+a[k][l];
if(i==k && j==l) dp[i][j][k][l]-=a[i][j];
}
ans=dp[m][n][m][n];
}
int main()
{
scanf("%d%d",&m,&n);
for(int i=1;i<=m;i++)
for(int j=1;j<=n;j++)
scanf("%d",&a[i][j]);
DP();
printf("%d\n",ans);
return 0;
}
codevs3369
神牛有很多…当然…每个同学都有自己衷心膜拜的神牛.
某学校有两位神牛,神牛甲和神牛乙。新入学的N位同学们早已耳闻他们的神话。所以,已经衷心地膜拜其中一位了。
现在,老师要给他们分机房。
但是,要么保证整个机房都是同一位神牛的膜拜者,或者两个神牛的膜拜者人数差不超过M。
另外,现在N位同学排成一排,老师只会把连续一段的同学分进一个机房。老师想知道,至少需要多少个机房。
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#define N 2501
using namespace std;
int dp[N],tot1[N],tot2[N],a[N];
int n,m,cnt1,cnt2,ans;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]==1) tot1[i]=1;
else tot2[i]=1;
}
for(int i=1;i<=n;i++)tot1[i]+=tot1[i-1];
for(int i=1;i<=n;i++)tot2[i]+=tot2[i-1];
memset(dp,0x3f,sizeof dp);dp[0]=0;
for(int i=1;i<=n;i++)
for(int j=0;j<i;j++)
{
if(abs((tot1[i]-tot1[j])-(tot2[i]-tot2[j]))<=m ||
tot1[i]-tot1[j]==0 ||tot2[i]-tot2[j]==0)
dp[i]=min(dp[i],dp[j]+1);
}
printf("%d\n",dp[n]);
return 0;
}
codevs1090
设一个n个节点的二叉树tree的中序遍历为(l,2,3,…,n),其中数字1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第j个节点的分数为di,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含tree本身)的加分计算方法如下:
subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数
若某个子树为主,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空
子树。试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出;
(1)tree的最高加分
(2)tree的前序遍历
姿势比较奇特
二叉树的中序遍历是把根节点放在中间
换而言之就是把根节点左右两边的树形序列(子树)合并起来
那么很明显这道题就是一个合并类的区间DP了
和石子合并思路相同,需要注意的是初始状态必须为1(因为是相乘),不然结果会出错
dp[i][j]表示中序遍历i到j最大值
方程:dp[i,j]:=max(dp[i][k-1]*dp[k+1][j]+dp[k][k]
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 101
using namespace std;
int n,num[N][N];
long long f[N][N];
void find(int x,int y)
{
if(x<=y)
{
printf("%d ",num[x][y]);
find(x,num[x][y]-1);
find(num[x][y]+1,y);
}
}
int main()
{
scanf("%d",&n);
for(int i=0;i<=n;i++) for(int j=0;j<=n;j++)
{
f[i][j]=1;num[i][i]=i;
}
for(int i=1;i<=n;i++) scanf("%d",&f[i][i]);
for(int i=n;i>=1;i--)
for(int j=i+1;j<=n;j++)
for(int k=i;k<=j;k++)
{
if(f[i][j]<(f[i][k-1]*f[k+1][j]+f[k][k]))
f[i][j]=f[i][k-1]*f[k+1][j]+f[k][k],
num[i][j]=k;
}
printf("%lld\n",f[1][n]);find(1,n);
return 0;
}
bzoj 1084 最大子矩阵
题意:这里有一个n*m的矩阵,请你选出其中k个子矩阵,使得这个k个子矩阵分值之和最大。注意:选出的k个子矩阵
不能相互重叠。
思路:
这题因为m只有1、2两种情况,所以分开讨论。
m=1 这部分还是比较水的,f[i][j]表示前i行选j个矩形的最大分值。
方程f[i][j]=max(f[i-1][j],max{f[k][j-1]+sum[i]-sum[k]|0<=k<=i})(sum是前缀和)
* m=2
f[i][j][k]表示第一列前i行,第二列前j行选k个矩形的最大分值,方程和m=1是较相似,但多了i=j时的情况,
叙述比较麻烦,具体看代码。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 105
using namespace std;
int dp[N][N][N],f[N][N],s[N][2],sum[N],a[N],b[N];
int n,m,k,ans;
int main()
{
scanf("%d%d%d",&n,&m,&k);
if(m==1)
{
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
sum[i]=sum[i-1]+a[i];
}
for(int i=1;i<=n;i++)
{
f[i][0]=0;
for(int j=1;j<=k;j++)
{
f[i][j]=f[i-1][j];
for(int l=1;l<=i;l++)
f[i][j]=max(f[i][j],f[l][j-1]+sum[i]-sum[l]);
}
}
printf("%d\n",f[n][k]);
}
else
{
for(int i=1;i<=n;i++)
{
scanf("%d%d",&a[i],&b[i]);
s[i][0]=s[i-1][0]+a[i];
s[i][1]=s[i-1][1]+b[i];
}
for(int i1=1;i1<=n;i1++) for(int i2=1;i2<=n;i2++)
{
dp[i1][i2][0]=0;
for(int j=1;j<=k;j++)
{
dp[i1][i2][j]=max(dp[i1-1][i2][j],dp[i1][i2-1][j]);
for(int l=0;l<i1;l++)
dp[i1][i2][j]=max(dp[i1][i2][j],dp[l][i2][j-1]+s[i1][0]-s[l][0]);
for(int l=0;l<i2;l++)
dp[i1][i2][j]=max(dp[i1][i2][j],dp[i1][l][j-1]+s[i2][1]-s[l][1]);
if(i1==i2)
{
for(int l=0;l<i1;l++)
dp[i1][i2][j]=max(dp[i1][i2][j],dp[l][l][j-1]+s[i1][0]-s[l][0]+s[i2][1]-s[l][1]);
}
}
}
printf("%d\n",dp[n][n][k]);
}
return 0;
}
bzoj1079 着色方案
题意:有n个木块排成一行,从左到右依次编号为1~n。你有k种颜色的油漆,其中第i种颜色的油漆足够涂ci个木块。
所有油漆刚好足够涂满所有木块,即c1+c2+...+ck=n。相邻两个木块涂相同色显得很难看,所以你希望统计任意两
个相邻木块颜色不同的着色方案。
思路:
先确定这是一道dp
再看范围得结论这是一道多维dp,想起了王八棋。
然后定状态,很多维的话只能一种一维咯,因为要转移所以还要加一维
f[a][b][c][d][e][k] 铅丝维是种类最后是上一块放的是第几种
转移略麻烦,比如这次三块的减了一,两块的就加了一,乘的时候就要减回来。
乘法原理,自行体会,,,,,,
#include<iostream>
#include<cstdio>
#define ll long long
#define mod 1000000007
using namespace std;
ll f[16][16][16][16][16][6];
int x[6],n;
bool L[16][16][16][16][16][6];
ll dp(int a,int b,int c,int d,int e,int k)
{
ll t=0;
if(L[a][b][c][d][e][k])return f[a][b][c][d][e][k];
if(a+b+c+d+e==0)return 1;
if(a)
t+=(a-(k==2))*dp(a-1,b,c,d,e,1 );
if(b)
t+=(b-(k==3))*dp(a+1,b-1,c,d,e,2);
if(c)
t+=(c-(k==4))*dp(a,b+1,c-1,d,e,3);
if(d)
t+=(d-(k==5))*dp(a,b,c+1,d-1,e,4);
if(e)
t+=e*dp(a,b,c,d+1,e-1,5);
L[a][b][c][d][e][k]=1;
return f[a][b][c][d][e][k]=(t%mod);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
int t;
scanf("%d",&t);
x[t]++;
}
printf("%lld",dp(x[1],x[2],x[3],x[4],x[5],0));
return 0;
}
bzoj1046 上升子序列
题目:对于一个给定的S={a1,a2,a3,…,an},若有P={ax1,ax2,ax3,…,axm},满足(x1 < x2 < … < xm)且( ax1 < ax
2 < … < axm)。那么就称P为S的一个上升序列。如果有多个P满足条件,那么我们想求字典序最小的那个。任务给
出S序列,给出若干询问。对于第i个询问,求出长度为Li的上升序列,如有多个,求出字典序最小的那个(即首先
x1最小,如果不唯一,再看x2最小……),如果不存在长度为Li的上升序列,则打印Impossible.
思路:
我们知道nlogn的最长上升子序列中定义状态f[i]表示以i结尾的...
这个题要求以i开头的 所以倒着做最长下降子序列就好了,f只记录长度
所以需要有个best数组存序列
最后输出答案,若要求的序列长度为x,如果以第一个数(字典序最小的数)
开头的最长上升子序列大等于x,则将它放在答案第一个,
第二个数开头小于x,则舍弃,第三个大于x-1,放答案第二个,以此类推
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define inf 1000000000
#define N 100007
using namespace std;
int n,m,cnt;
int a[N],f[N],best[N];
inline int read()
{
int x=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
void solve(int x)
{
int last=0;
for(int i=1;i<=n;i++)
if(f[i]>=x&&a[i]>last)
{
printf("%d",a[i]);
if(x!=1)printf(" ");
last=a[i];x--;
if(!x)break;
}printf("\n");
}
int find(int x)
{
int l=1,r=cnt,ans=0;
while(l<=r)
{
int mid=(l+r)>>1;
if(best[mid]>x)ans=mid,l=mid+1;
else r=mid-1;
}return ans;
}
void dp()
{
for(int i=n;i;i--)
{
int t=find(a[i]);
f[i]=t+1;cnt=max(cnt,t+1);
if(best[t+1]<a[i]) best[t+1]=a[i];
}
}
int main()
{
n=read();
for(int i=1;i<=n;i++) a[i]=read();
dp(); m=read();
for(int i=1;i<=m;i++)
{
int x=read();
if(x<=cnt)solve(x);
else puts("Impossible");
}
return 0;
}
bzoj1019 汉诺塔
题意:普通汉诺塔加了优先级
思路:优先级可以预处理
如果是普通汉诺塔
操作就是将第一个柱子除底盘外的移到第二个柱子,然后把底盘移到第三个柱子,然后把第二个柱子的盘子移动到第三个
但基本的汉诺塔问题的操作是没有限制的,就是你想移哪儿移哪儿,但是这题不一样,这题强制了一个操作优先级,所以要用不同的方法去做。
f[x][i]: 第x号柱子移i个盘子到最优柱子的最优解
p[x][i]:第x号柱子移i个盘子到p[x][i]号柱子是最优解
那么就有两种情况,第一种就是普通的汉诺塔移动
f[a][i]=f[a][i-1]+1+f[b][i-1];
另外一种就是特殊的
a上i-1个盘子移至b上,将a上的第i个盘子,移至c。由于i个盘子还没叠到一起,所以接下来还要再次移动b上的i-1个
如果移到c的话就是经典算法
如果i-1个盘子移到a的话,那么最终就是移到b柱子上
f[a][i]=f[a][i-1]+1+f[b][i-1]+1+f[a][i-1];
#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define ll long long
using namespace std;
int p[4][31],x[10],y[10];
int n; char s[3];
ll f[4][31];
int main()
{
scanf("%d",&n);
for(int i=1;i<=6;i++)
{
scanf("%s",s);
x[i]=s[0]-'A'+1,y[i]=s[1]-'A'+1;
}
for(int i=6;i>=1;i--) p[x[i]][1]=y[i];//倒着来的原因是优先级高的会覆盖优先级低的,被覆盖的我们就不要了
for(int i=1;i<=3;i++) f[i][1]=1LL;
for(int i=2;i<=n;i++)
{
for(int a=1;a<=3;a++)
{
int b=p[a][i-1],c=6-a-b;//c是什么?一号二号三号加起来是6嘛,减去其他两个不就是剩下那个了?
if(p[b][i-1]==c)
{
f[a][i]=f[a][i-1]+1+f[b][i-1];//1是底盘移动的步数
p[a][i]=c;
}
else if(p[b][i-1]==a)
{
f[a][i]=f[a][i-1]+1+f[b][i-1]+1+f[a][i-1];
p[a][i]=b;
}
}
}
printf("%lld\n",f[1][n]);
return 0;
}
bzoj 1260 [CQOI2007]涂色paint
题意:求将一段序列染成指定颜色的最小步数
思路:
区间dp 类似石子归并有木有
f[i][j] 将1~n染色的最小步数
从小区间穷举区间后,分情况讨论
如果区间两端相等,就取[i+1,j],[i,j-1],[i+1,j-1]+1中的最小值;
如果不一样,就穷举中间断点k,取min([i,k][k+1,j])。
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 51
using namespace std;
int f[N][N],n,m,cnt;
char s[N];
int main()
{
memset(f,127/3,sizeof f);
scanf("%s",s);n=strlen(s);
for(int i=n;i>=1;i--) s[i]=s[i-1];
for(int i=1;i<=n;i++) f[i][i]=1;
for(int i=n-1;i>=1;i--)
{
for(int j=i+1;j<=n;j++)
{
if(s[i]==s[j])
{
f[i][j]=min(f[i+1][j],f[i][j-1]);
if(i<j-1)f[i][j]=min(f[i][j],f[i+1][j-1]+1);
}
else
for(int k=i;k<=j;k++) f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
}
}
printf("%d\n",f[1][n]);
return 0;
}
bzoj1089 [SCOI2003]严格n元树
题意:求深度为d的严格n元树
思路:
定义S[i]代表深度<=i的严格n元树的个数
那么最后S[d]-S[d-1]就是答案
那么对于S[i],我们由S[i-1]递推来,
我们考虑新加一个根节点,然后根节点有n个子节点,每个子节点都可以建一颗深度<=i-1的树,那么每个
子节点都有S[i-1]种选法,那么n个子节点就有S[i-1]^n选法,再加上都不选,就是深度为0的情况
那么S[i]:=(S[i-1]^n)+1;
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<iomanip>
using namespace std;
struct long_int{
int num[3000],cnt;
void operator = (int y)
{
num[1]=y;cnt=1;
}
int& operator [] (int x)
{
return num[x];
}
}S[200];
void operator *= (long_int &x,long_int &y)
{
long_int z=S[199];
int i,j;
for(i=1;i<=x.cnt;i++)
for(j=1;j<=y.cnt;j++)
{
z[i+j-1]+=x[i]*y[j];
z[i+j]+=z[i+j-1]/10000;
z[i+j-1]%=10000;
}
z.cnt=x.cnt+y.cnt;
if(!z[z.cnt])--z.cnt;
x=z;
}
void operator ++ (long_int &x)
{
int i=1;x[1]++;
while(x[i]==10000)x[i]=0,x[++i]++;
}
long_int operator - (long_int &x,long_int &y)
{
long_int z=S[199];
int i;
for(i=1;i<=x.cnt;i++)
{
z[i]+=x[i]-y[i];
if(z[i]<0) z[i]+=10000,z[i+1]--;
if(z[i]) z.cnt=i;
}
return z;
}
long_int operator ^ (long_int x,int y)
{
long_int z=S[199];z=1;
while(y)
{
if(y&1) z*=x;
x*=x;y>>=1;
}
return z;
}
ostream& operator << (ostream &os,long_int x)
{
int i;
os<<x[x.cnt];
for(i=x.cnt-1;i;i--)
os<<setfill('0')<<setw(4)<<x[i];
//os<<x[i];
return os;
}
int n,d;
int main()
{
int i;
cin>>n>>d;
if(!d)
{
puts("1");return 0;
}
S[0]=1;
for(i=1;i<=d;i++)
S[i]=S[i-1]^n,++S[i];
cout<<S[d]-S[d-1]<<endl;
}
bzoj1037[ZJOI2008]生日聚会Party
题意:求n个男生m个女生放一排任意一段差值不超过k个的方案数
思路:
开始想状态dp[i][j][k]:前i个人中j男k女方案数
但貌似不会转移 因为有任意一段差值不大于k的限制 考虑如何在状态中把限制表现出来
又前i个人不是男就是女(废话) 知道男就能推出女
所以令dp[i][j][x][y]:前i个人中有j个是男生,任意一段男生比女生最多多x人,女生比男生最多多y人的方案数
转移时显然四重循环 枚举多的人数
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cstdlib>
#define mod 12345678
#define N 157
using namespace std;
int f[N<<1][N][21][21],n,m,ans,cnt,k;
int main()
{
scanf("%d%d%d",&n,&m,&k);
f[0][0][0][0]=1;
for(int i=0;i<=n+m-1;i++)
for(int j=0;j<=n;j++)
for(int x=0;x<=k;x++)
for(int y=0;y<=k;y++)
if(f[i][j][x][y])
{
if(j+1<=n&&x+1<=k) (f[i+1][j+1][x+1][max(y-1,0)]+=f[i][j][x][y])%=mod;
if(i+1-j<=m&&y+1<=k) (f[i+1][j][max(x-1,0)][y+1]+=f[i][j][x][y])%=mod;
}
for(int i=0;i<=k;i++)
for(int j=0;j<=k;j++)
ans=(ans+f[n+m][n][i][j])%mod;
printf("%d\n",ans);
return 0;
}
bzoj2298[HAOI2011]problem a
题意:一次考试共有n个人参加,第i个人说:“有ai个人分数比我高,bi个人分数比我低。”问最少有几个人没有说真话(可能有相同的分数)
思路:
对于每一个描述,我们可以根据他所描述的比他高的和比他矮的人数来构造一条线段,左端点l即为y+1,右端点r为n-x。
当我们转化成线段以后,这一段线段就表示着分数相同的人数,那么显然,只有与这个线段完全重合的线段是符合要求的,
对于有交集的线段一定是有一个说谎的,但是对于完全重合的线段,还是有可能出现说谎的情况
所以我们可以写个前向星add(b[i].r,b[i].l-1,b[i].w)
l为该区间的左端点,r为该区间的右端点,w为权值
建这个边的原因是f[b[i].r]=max(f[b[i].r],f[b[j].l-1]+e[i].w)
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 100007
using namespace std;
int n,tot,num,cnt;
struct node{int l,r,w;}a[N],b[N];
struct node2{int u,v,net,w;}e[N];
int f[N],head[N];
int cmp(node a,node b)
{
if(a.r==b.r)return a.l<b.l;
return a.r<b.r;
}
int cmp2(node a,node b)
{
if(a.r==b.r)return a.w<b.w;
return a.r<b.r;
}
void add(int u,int v,int w)
{
e[++cnt].v=v;e[cnt].w=w;e[cnt].net=head[u];head[u]=cnt;
}
int main()
{
scanf("%d",&n);int x,y,ans=0;
memset(head,-1,sizeof(head)),cnt=0;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&x,&y);
if(x+y>=n){ans++;continue;}
a[++tot].l=x+1;
a[tot].r=n-y;
}
sort(a+1,a+tot+1,cmp);
for(int i=1;i<=tot;i++)
{
if(a[i].l==a[i-1].l&&a[i].r==a[i-1].r)
{
if(b[num].w<b[num].r-b[num].l+1)
b[num].w++;
}
else b[++num].l=a[i].l,b[num].r=a[i].r,b[num].w=1;
}
sort(b+1,b+num+1,cmp2);
for(int i=1;i<=num;i++) add(b[i].r,b[i].l-1,b[i].w);
int r=0;
int cnt=0;
for(int i=1;i<=n;i++)
{
f[i]=f[i-1];
for(int j=head[i];j!=-1;j=e[j].net)
{
int v=e[j].v;
f[i]=max(f[i],f[v]+e[j].w);
}
}
printf("%d\n",n-f[n]);
}
bzoj2431: [HAOI2009]逆序对数列
题意:若对于任意一个由1~n自然数组成的数列,可以很容易求出有多少个逆序对数。那么逆序对数为k的这样自然数数列到底有多少个?
思路:
dp[i][j]表示i的排列中逆序对数为j的方案数
考虑i的放置,i为最大值,所以放在i-1个位置都可以计算出对答案的贡献
dp[i][j]=Σdp[i-1][k] (j-i+1 <=k<= j)
特别的到i时最多可以贡献i-1对逆序对,所以从dp[0]~dp[j-i+1]这一段不能加
n^3超时,可用前缀和优化
貌似也可以滚动数组,但蒟蒻不会23333...
#include<iostream>
#include<cstdio>
#include<cstring>
#define N 1001
#define mod 10000
using namespace std;
int dp[N][N];
int n,k,ans,sum;
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++) dp[i][0]=1;
for(int i=2;i<=n;i++)
{
sum=0;
for(int j=0;j<=k;j++)
{
(sum+=dp[i-1][j])%mod;
dp[i][j]=sum%mod;
if(j-i+1>=0)((sum-=dp[i-1][j-i+1])+=mod)%mod;
}
}
printf("%d\n",dp[n][k]);
return 0;
}
bzoj4247 挂饰
题目大意:一开始你有一个挂钩,然后有n个挂饰,每个挂饰有一个价值和挂钩数量,求最大价值。
思路:
数据范围有误吧... n<=4000
dp[i][j] 用完第i个挂饰后还有j个空挂钩的max
背包问题 挂钩当体积
按挂钩数量排序 不排序的话这次挂上这个饰品即使j是负数也并不是不合法的,
因为挂饰间可以互换位置 只要后面挂饰的挂钩能够把j在最后补成自然数就可以了
dp[i][j]=max(dp[i-1][j],dp[i-1][max(j-a[i].v,0)+1]+a[i].w);
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 4001
#define inf 0x3f3f3f3f
using namespace std;
int dp[N<<1][N];
int n,ans;
struct node
{
int v,w;
bool operator < (const node &x) const{
return v>x.v;
}
}a[N];
int main()
{
scanf("%d",&n);
for(int i=0;i<=n;i++) dp[0][i]=dp[i][n+1]=-inf;
dp[0][1]=0;
for(int i=1;i<=n;i++)
scanf("%d%d",&a[i].v,&a[i].w);
sort(a+1,a+n+1);
ans=-inf;
for(int i=1;i<=n;i++)
for(int j=0;j<=n;j++)
dp[i][j]=max(dp[i-1][j],dp[i-1][max(j-a[i].v,0)+1]+a[i].w);
for(int i=0;i<=n;i++) ans=max(ans,dp[n][i]);
printf("%d\n",ans);
return 0;
}
NOI1999 棋盘分割
题目大意:将一个8*8的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,切n次球均方差最小值
思路:推一波式子发现答案转化为求平方和的最小值,就可以dp了。
设f(i,a,b,c,d)表示切第i刀,剩余的矩形左上角和右下角的坐标是(a,b)和(c,d),
除了剩余部分其它部分的xi平方和的最小值。
那么f(i)可以向f(i+1)转移,只需要暴力枚举第i+1刀从哪里切了一刀即可。
#include <iostream>
#include <cstdio>
#include <cmath>
using namespace std;
const int inf=1<<30;
int n, chess[9][9],sum[9][9],dp[9][9][9][9][15];
int getX(int y1, int x1, int y2, int x2)
{
int a=sum[y2][x2]-sum[y2][x1-1]-sum[y1-1][x2]+sum[y1-1][x1-1];
return a*a;
}
int main()
{
scanf("%d", &n);
for(int i=1; i<=8; i++)
for(int j=1; j<=8; j++)
scanf("%d", &chess[i][j]);
for(int i=1; i<=8; i++)
{
for(int j=1; j<=8; j++)
sum[i][j]=sum[i][j-1]+chess[i][j];
for(int j=1; j<=8; j++)
sum[i][j]+=sum[i-1][j];
}
for(int i1=1; i1<=8; i1++)
for(int j1=1; j1<=8; j1++)
for(int i2=i1; i2<=8; i2++)
for(int j2=j1; j2<=8; j2++)
dp[i1][j1][i2][j2][0]=getX(i1, j1, i2, j2);
for(int i=1; i<n; i++)
for(int i1=1; i1<=8; i1++)
for(int j1=1; j1<=8; j1++)
for(int i2=i1; i2<=8; i2++)
for(int j2=j1; j2<=8; j2++)
{
dp[i1][j1][i2][j2][i]=inf;
//左右切割
for(int k=j1; k<j2; k++)
dp[i1][j1][i2][j2][i]=min(dp[i1][j1][i2][j2][i], min(dp[i1][j1][i2][k][i-1]+dp[i1][k+1][i2][j2][0], dp[i1][j1][i2][k][0]+dp[i1][k+1][i2][j2][i-1]));
//上下切割
for(int k=i1; k<i2; k++)
dp[i1][j1][i2][j2][i]=min(dp[i1][j1][i2][j2][i], min(dp[i1][j1][k][j2][i-1]+dp[k+1][j1][i2][j2][0], dp[i1][j1][k][j2][0]+dp[k+1][j1][i2][j2][i-1]));
}
printf("%d\n",dp[1][1][8][8][n-1]);
return 0;
}
Luogu P2339 交作业
题目大意:要交C门作业,每个办公室有坐标和开放时间。每单位时间内可以向左或向右移动一个单位或者不动,求交完作业并走到某个点的最短时间。
思路:容易想到把教室排序,一段区间[l,r]先选外侧的教室交作业一定比先选里面的再出来再去另一边更优
f[l][r][0/1]表示决策到[l,r]这段区间,区间外的都已满足,选则l/r交作业的最短时间,转移看从那个教室移动过来即可。
#include<bits/stdc++.h>
#define N 1007
using namespace std;
int n,m,ans,cnt;
int f[N][N][2];
struct node{
int Time,pos;
bool operator < (const node &a) const{
return pos<a.pos;
}
}a[N];
inline int read()
{
int x=0,f=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
int main()
{
int C,H,B;
C=read();H=read();B=read();
for(int i=1;i<=C;i++)
a[i].pos=read(),a[i].Time=read();
sort(a+1,a+C+1);
memset(f,127/3,sizeof f);
f[1][C][0]=max(a[1].Time,a[1].pos);
f[1][C][1]=max(a[C].Time,a[C].pos);
for(int L=C-2;L>=0;L--) for(int i=1;i+L<=C;++i)
{
int j=i+L;
f[i][j][0]=min(max(f[i-1][j][0]+a[i].pos-a[i-1].pos,a[i].Time),
max(f[i][j+1][1]+ a[j+1].pos-a[i].pos,a[i].Time));
f[i][j][1]=min(max(f[i-1][j][0]+a[j].pos - a[i-1].pos,a[j].Time),
max(f[i][j+1][1]+ a[j+1].pos-a[j].pos,a[j].Time));
}
ans=0x3f3f3f3f;
for (int i=1;i<=C;i++)
ans=min(ans,f[i][i][0]+abs(a[i].pos-B));
printf("%d\n",ans);
return 0;
}
bzoj2101[Usaco2010 Dec]Treasure Chest 藏宝箱
题目大意:一排金币两个人轮流取,每个人取最左边或最右边,问先手获得的最大值。
思路:显然先手要让对手得到的最小,用区间的和减去对手的最小值就是先手的最大值。
f[i][j]=sum[j]-sum[i-1]-min(f[i+1][j],f[i][j+1])。空间不够,滚动数组优化。
#include<bits/stdc++.h>
#define N 5001
using namespace std;
int n,m,k,ans,cnt;
int f[2][N],sum[N];
inline int read()
{
int x=0,f=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
int main()
{
freopen("ly.in","r",stdin);
n=read();
for(int i=1;i<=n;i++)
{
sum[i]=read();
f[k][i]=sum[i];sum[i]+=sum[i-1];
}
for(int L=2;L<=n;L++)
{
for(int i=1;i+L-1<=n;i++)
{
int j=i+L-1;
f[k^1][i]=sum[j]-sum[i-1]-min(f[k][i],f[k][i+1]);
}k^=1;
}
printf("%d\n",f[k][1]);
return 0;
}
bzoj 1617: [Usaco2008 Mar]River Crossing渡河问题
思路:f[i]表示运i头牛的最短时间。转移枚举最后一次运几头。
f[i]=min(f[i],f[j]+sum[i-j]+m);
#include<bits/stdc++.h>
#define N 2511
#define ll long long
#define inf 0x3f3f3f3f;
using namespace std;
int n,m,cnt;
ll ans,f[N],T[N],sum[N];
inline int read()
{
int x=0,f=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
int main()
{
n=read();m=read();sum[0]=m;
for(int i=1;i<=n;i++) f[i]=inf;
for(int i=1;i<=n;i++)
{
T[i]=read();sum[i]=sum[i-1]+T[i];
f[i]=sum[i]+m;
}
for(int i=2;i<=n;i++)
{
for(int j=1;j<i;j++)
{
f[i]=min(f[i],f[j]+sum[i-j]+m);
}
}
printf("%lld\n",f[n]-m);
return 0;
}
思路:f[i][j]表示到第i个数,得到数值为j,向右合并的最右端点。 方程f[i][j]=f[f[i][j-1]][j-1];类似倍增。
#include<bits/stdc++.h>
#define N 270000
using namespace std;
int n,m,ans;
int f[N][66],a[N];
int main()
{
freopen("ly.in","r",stdin);
scanf("%d",&n);
for (int i=1; i<=n; i++)
{
scanf("%d",&a[i]);
f[i][a[i]]=i+1;
}
for (int j=2; j<=60; j++)
for (int i=1; i<=n; i++)
{
if (f[i][j]==0) f[i][j]=f[f[i][j-1]][j-1];
if (f[i][j]>0) ans=max(ans,j);
}
printf("%d\n",ans);
return 0;
}
bzoj4758: [Usaco2017 Jan]Subsequence Reversal(区间dp)
思路:想到可能是道区间dp,emm那就考虑一段区间[l,r]怎么维护里面交换那些数呢?
发现可以用值域这个东西把数给框住。又,反转区间肯定是越靠右的反转到越靠左位置。
那么由小区间推大区间时,只需要判断端点处包不包括在这一次的交换中即可。
所以可dp[i][j][L][R]为区间[i,j]里面min(ak) >= L, max(ak) <= R时,反转一次的最长不下降子序列。
转移见代码。
#include<bits/stdc++.h>
#define N 51
using namespace std;
int n,a[N],ans;
int dp[N][N][N][N];
inline int read()
{
int x=0,f=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){x=x*10+c-'0';c=getchar();}
return x*f;
}
int main()
{
n=read();
for(int i=1; i <= n; ++i) a[i]=read(),dp[i][i][a[i]][a[i]]=1;
for(int len=2; len <= n; ++len) for(int i=1; i+len-1 <= n; ++i)//当前区间
{
int j=i+len-1;
for(int l=1; l <= 50; ++l) for(int L=1; L+l-1 <= 50; ++L)//当前值域
{
int R=L+l-1;
ans=dp[i][j][L][R];
ans=max(ans,max(dp[i+1][j][L][R],dp[i][j-1][L][R]));
ans=max(ans,max(dp[i][j][L+1][R],dp[i][j][L][R-1]));
dp[i][j][L][R]=ans;
//copy小区间的答案
dp[i][j][min(L,a[i])][R]=max(dp[i][j][min(L,a[i])][R],dp[i+1][j][L][R]+(a[i] <= L));
dp[i][j][L][max(R,a[j])]=max(dp[i][j][L][max(R,a[j])],dp[i][j-1][L][R]+(a[j] >= R));
dp[i][j][min(L,a[i])][max(R,a[j])]=max(dp[i][j][min(L,a[i])][max(R,a[j])],dp[i+1][j-1][L][R]+(a[j] >= R)+(a[i] <= L));
//a[i]与a[j]不交换
dp[i][j][min(L,a[j])][R]=max(dp[i][j][min(L,a[j])][R],dp[i+1][j-1][L][R]+(a[j] <= L));
dp[i][j][L][max(R,a[i])]=max(dp[i][j][L][max(R,a[i])],dp[i+1][j-1][L][R]+(a[i] >= R));
dp[i][j][min(L,a[j])][max(R,a[i])]=max(dp[i][j][min(L,a[j])][max(R,a[i])],dp[i+1][j-1][L][R]+(a[i] >= R)+(a[j] <= L));
//a[i]与a[j]交换
}
}
cout<<dp[1][n][1][50]<<endl;
return 0;
}