动态规划杂题

概率期望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;
}
Code

 

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;
}
Code

 

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;
}
Code

 

codevs1253

题目描述 Description

某人喜欢按照自己的规则去市场买菜,他每天都列一个买菜的清单,自由市场的菜码放也有一个顺序,该人有一个特点,就是按顺序买菜,从不走回头路,当然,她希望能花最好的钱买到所有的菜,你能帮帮他吗?

思路:比较简单的dp,f[i][j]表示市场上的菜单到i需购买的菜单到j所需的最少花费,若i=j,则更新这个点,否则f[i][j]=f[i-1][j]。初始化将f[i][0]=0;其他都付最大值,最后比较f[n][m]和0x7fffffff,若大于则是impossible,否则输出。
#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;
}
Code

 codevs1959

题目描述 Description

一个学校举行拔河比赛,所有的人被分成了两组,每个人必须(且只能够)在其中的一组,要求两个组的人数相差不能超过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;
}
Code

 

 codevs1043

题目描述 Description

设有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;
}
Code

 

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;
}
Code

 

 codevs3369

题目描述 Description

神牛有很多…当然…每个同学都有自己衷心膜拜的神牛.
某学校有两位神牛,神牛甲和神牛乙。新入学的N位同学们早已耳闻他们的神话。所以,已经衷心地膜拜其中一位了。
现在,老师要给他们分机房。
但是,要么保证整个机房都是同一位神牛的膜拜者,或者两个神牛的膜拜者人数差不超过M。
另外,现在N位同学排成一排,老师只会把连续一段的同学分进一个机房。老师想知道,至少需要多少个机房。

思路:预处理一下前i位置有多少个两个神牛的崇拜者(前缀和数组),然后就是dp部分了,f[i]表示分到i个人最少多少个教室,循环i和k(i-k表示最后一个教室的人数),更新f[i]的值。预处理:f[0]=0;
#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;
}
Code

 

codevs1090

题目描述 Description

设一个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;
}
Code

 

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;
}
Code

 

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;
} 
Code

 

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;
}
Code

 

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;
}
Code

 

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;
}
Code

 

 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;
}
Code

 

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;
}
Code

 

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]);
}
Code

 

 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;
}
Code

 

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;
}
Code

 

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;
}
Code

 

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;
}
Code

 

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;
}
Code

 

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;
} 
Code

 

bzoj4580: [Usaco2016 Open]248

思路: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;
}
Code

 

 

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;
}
Code

 

 

 

posted @ 2017-08-10 09:39  安月冷  阅读(725)  评论(0编辑  收藏  举报