dp 小结

http://www.cnblogs.com/Rivendell/p/4079582.html

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;
struct use{
    int kind;
    double mon;
}b[100001];
double f[100001][101]={0};
int a[101]={0};
int main()
{
    int i,j,n,m,k;
    scanf("%d%d",&m,&n);
    memset(f,127,sizeof(f));
    for (i=1;i<=m;++i)
      scanf("%d",&a[i]);
    for (i=1;i<=n;++i)
      scanf("%d%lf",&b[i].kind,&b[i].mon);
    for(i=1;i<=n;++i)
      f[i][0]=0; 
    for (i=1;i<=n;++i)
      for (j=1;j<=m;++j)
      {
          f[i][j]=f[i-1][j];
        if (b[i].kind==a[j])
            f[i][j]=min(f[i][j],f[i-1][j-1]+b[i].mon);
      }
    if (f[n][m]>0x7fffffff) printf("Impossible\n");
    else
      printf("%.2f\n",f[n][m]);
}
复制代码
RZUC Code

 

 

CODEVS1959 拔河游戏

题目描述  Description

一个学校举行拔河比赛,所有的人被分成了两组,每个人必须(且只能够)在其中的一组,要求两个组的人数相差不能超过1,且两个组内的所有人体重加起来尽可能地接近。

 

思路:一个二维费用(bool)背包问题,一个是元素个数(相当于体积),一个是体重。先求出总重的一半和元素个数一半,进行背包。

 

复制代码
#include<iostream>
#include<cstdio>
using namespace std;
bool f[101][45001]={false};
int a[101]={0};
int main()
{
    int i,j,sum=0,n,k,q,cha,summ;
    cin>>n;
    if (n%2==0) k=n/2;
    else k=n/2+1;
    for (i=1;i<=n;++i)
    {
      cin>>a[i];
      sum+=a[i];
    }
    if (summ%2==0) summ=sum/2;
    else summ=sum/2+1;  
    for (i=0;i<=1;++i)
      f[i][0]=true;
    for (i=1;i<=n;++i)
      for (j=summ;j>=a[i];--j)
        for (q=k;q>=1;--q)
        f[q][j]=f[q][j]||f[q-1][j-a[i]];
    for (j=summ;j>=0;--j)
      if (f[k][j])
      {
          cout<<min(j,sum-j)<<" "<<max(j,sum-j)<<endl;
        break;
      }  
}
复制代码
RZUC Code

 

 

CODEVS1060 搞笑世界杯

题目描述  Description

    随着世界杯小组赛的结束,法国,阿根廷等世界强队都纷纷被淘汰,让人心痛不已. 于是有

人组织了一场搞笑世界杯,将这些被淘汰的强队重新组织起来和世界杯一同比赛.你和你的朋

友欣然去购买球票.不过搞笑世界杯的球票出售方式也很特别,它们只准备了两种球票.A 类

票------免费球票 B 类票-------双倍价钱球票.购买时由工作人员通过掷硬币决定,投到正面

的买A类票, 反面的买B类票.并且由于是市场经济,主办方不可能倒贴钱,所以他们总是准备

了同样多的A类票和B类票.你和你的朋友十分幸运的排到了某场精彩比赛的最后两个位置.

这时工作人员开始通过硬币售票.不过更为幸运的是当工作人员到你们面前时他发现已无需

再掷硬币了,因为剩下的这两张票全是免费票。

 

    你和你的朋友在欣喜之余,想计算一下排在队尾的两个人同时拿到一种票的概率是多少

(包括同时拿A 类票或B类票) 假设工作人员准备了2n 张球票,其中n 张A类票,n 张B类票,并且排在队伍中的人每人必须且只能买一张球票(不管掷到的是该买A 还是该买B).

 

思路:先说一个比较坑爹的事情,读入的是2n,不是n。f[i][j]表示第i人买票时买了j张A票的概率。对于j有三种情况:

  1)j=0:f[i][j]=f[i-1][j]*0.5;   2)0<j<n:f[i][j]=(f[i-1][j]+f[i-1][j-1])*0.5;  3)j=n:f[i][j]=f[i-1][j]+f[i-1][j-1]*0.5。

初始化f[0][0]=1;输出f[n*2-2][n]*2(因为最后两张可以全是A或B);

 

复制代码
#include<iostream>
#include<cstdio>
using namespace std;
double f[3000][3000]={0};
int main()
{
    int n,i,j;
    cin>>n;
    n/=2;
    f[0][0]=1;
    for (i=1;i<=2*n;++i)
      for (j=0;j<=n;++j)
      {
          if (j>i) continue;
          if (j==0) f[i][j]=f[i-1][j]*0.5;
          else
          {
              if (j<n) f[i][j]=(f[i-1][j]+f[i-1][j-1])*0.5;
              else f[i][j]=f[i-1][j]+f[i-1][j-1]*0.5;
          }
      }
    printf("%.4f\n",f[n*2-2][n]*2);
}
复制代码
RZUC 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<cmath>
using namespace std;
int f[3000]={0},sum1[3000]={0},sum2[3000]={0};
int main()
{
    int n,m,i,j,x;
    memset(f,127/3,sizeof(f));
    scanf("%d%d",&n,&m);
    for (i=1;i<=n;++i)
    {
      sum1[i]=sum1[i-1];
      sum2[i]=sum2[i-1];
      scanf("%d",&x);
      if (x==1) ++sum1[i];
      else ++sum2[i];
    }
    f[0]=0;
    for (i=1;i<=n;++i)
      for (j=0;j<i;++j)
        if (abs((sum1[i]-sum1[j])-(sum2[i]-sum2[j]))<=m||
           sum1[i]-sum1[j]==0||sum2[i]-sum2[j]==0)
          f[i]=min(f[i],f[j]+1);
    cout<<f[n]<<endl;
}
复制代码
RZUC Code

 

 

CODEVS1491 取物品

题目描述  Description

现在有n个物品(有可能相同),请您编程计算从中取k个有多少种不同的取法。

 

思路:多重背包问题,将数字作为元素,出现的次数为个数。注意选取个数时候的循环要循环到1,一开始循环到了0,结果wa了。。。作死。。。

 

复制代码
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int a[31],f[31]={0},w[31]={0};
int main()
{
    int n,k,i,j,p;
    cin>>n>>k;
    for (i=1;i<=n;++i)
      cin>>a[i];
    sort(a+1,a+n+1);
    ++w[0];
    ++w[w[0]];
    for (i=2;i<=n;++i)
    {
      if (a[i]==a[i-1])
        ++w[w[0]];
      else
      {
          ++w[0];
          ++w[w[0]];
      }
      if (w[w[0]]>k) w[w[0]]=k;
    }
    f[0]=1;
    for (i=1;i<=w[0];++i)
      for (j=k;j>=0;--j)
        for (p=w[i];p>=1;--p)
          if (j>=p)
            f[j]=f[j]+f[j-p];
    cout<<f[k]<<endl;
}
复制代码
RZUC Code

 

 

小V1032

免费馅饼(NOI98)
  SERKOI最新推出了一种叫做“免费馅饼”的游戏:游戏在一个舞台上进行。舞台的宽度为W格,天幕的高度为H格,游戏者占一格。开始时游戏者站在舞台的正中央,手里拿着一个托盘。下图为天幕的高度为4格时某一个时刻游戏者接馅饼的情景。
游戏开始后,从舞台天幕顶端的格子中不断出现馅饼并垂直下落。游戏者左右移动去接馅饼。游戏者每秒可以向左或向右移动一格或两格,也可以站在原地不动。 
  馅饼有很多种,游戏者事先根据自己的口味,对各种馅饼依次打了分。同时,在8-308电脑的遥控下,各种馅饼下落的速度也是不一样的,下落速度以格/秒为单位。  
  当馅饼在某一秒末恰好到达游戏者所在的格子中,游戏者就收集到了这块馅饼。
  写一个程序,帮助我们的游戏者收集馅饼,使得所收集馅饼的分数之和最大。
  注意:同等情况下优先走编号小的点。
::点击图片在新窗口中打开::
 
思路:二维数组f表示时间为i位置为j时最大的分数,way用来记录路径,输出的时候递归,比较麻烦。。。
 
 
复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
int a[1500][1000]={0},f[1500][1000],way[1500][1000]={0};
void work(int t,int x)
{
    if (t>1) work(t-1,way[t][x]);
    cout<<x-way[t][x]<<endl;
}
int main()
{
    int w,h,i,j,k,x,t,p,v,e,maxt=0,mat,mai,man=0;
    cin>>w>>h;
    while (cin>>t>>x>>v>>p)
    {
        if ((h-1)%v==0) e=(h-1)/v+t;
        else e=(h-1)/v+t+1;
        a[e][x]=a[e][x]+p;
        if (e>maxt) maxt=e;
    }
    for (i=0;i<=1499;++i)
      for (j=0;j<=99;++j)
        f[i][j]=-1000000000; 
    f[0][w/2+1]=0;
    for (i=1;i<=maxt;++i)
      for (j=1;j<=w;++j)
        for (k=-2;k<=2;++k)
          if (j+k>=1&&j+k<=w)
          {
                if (f[i][j]<f[i-1][j+k]+a[i][j])
                {
                    f[i][j]=f[i-1][j+k]+a[i][j];
                    way[i][j]=j+k;
                }
          }
    for (i=1;i<=maxt;++i)
      for (j=1;j<=w;++j)
        if (f[i][j]>man)
        {
            man=f[i][j];
            mat=i;
            mai=j;
        }
    cout<<man<<endl; 
    if (man!=0) 
      work(mat,mai);
}
复制代码
RZUC Code

 

 

小V1035邮局

一些村庄被建在一条笔直的高速公路边上。我们用一条坐标轴来描述这条高速公路,每一个村庄的坐标都是整数。没有两个村庄坐标相同。两个村庄间的距离,定义为它们坐标值差的绝对值。
人们需要在一些村庄建立邮局——当然,并不是每一个村庄都必须建立邮局。邮局必须被建在村庄里,因此它的坐标和它所在的村庄坐标相同。每个村庄使用离它最近的那个邮局,建立这些邮局的原则是:所有村庄到各自所使用的邮局的距离总和最小。
你的任务是编写一个程序,在给定了每个村庄的坐标和将要建立的邮局数之后,按照上述原则,合理地选择这些邮局的位置。
 
思路:a数组预处理i到j仅有一个邮局时的最小代价,f数组前i个村放j个邮局的最小代价,然后更新。。。最后又是输出方案。递归
 
复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath> 
using namespace std;
int a[500][500]={0},x[500]={0},f[500][100]={0},way[500][100]={0};
void work(int p,int q)
{
    if (q!=1) work(way[p][q],q-1);
    cout<<x[(way[p][q]+1+p)/2]<<" ";
}

int main()
{
    int i,j,k,n,m,mid;
    ios::sync_with_stdio(false);
    cin>>n>>m;
    memset(f,127/3,sizeof(f));
    for (i=1;i<=n;++i)
    {
      cin>>x[i];
    }
    for (i=1;i<=n-1;++i)
      for (j=i+1;j<=n;++j)
      {
            mid=(i+j)/2;
            for (k=i;k<=j;++k)
              a[i][j]=a[i][j]+abs(x[k]-x[mid]);
      }
    for (i=1;i<=n;++i)
      f[i][1]=a[1][i];
    for (j=2;j<=m;++j)
      for (i=j+1;i<=n;++i)
        for (k=j-1;k<=i-1;++k)
        {
            if (f[k][j-1]+a[k+1][i]<f[i][j])
            {
                f[i][j]=f[k][j-1]+a[k+1][i];
                way[i][j]=k;
            }
        }
    cout<<f[n][m]<<endl;
    work(n,m);
    cout<<endl;
}
复制代码
RZUC Code

 

 

小V1080多人背包

DD 和好朋友们要去爬山啦!他们一共有 K 个人,每个人都会背一个包。这些包的容量是相同的,都是 V。可以装进背包里的一共有 N 种物品,每种物品都有给定的体积和价值。

在 DD 看来,合理的背包安排方案是这样的:

每个人背包里装的物品的总体积恰等于包的容量。 
每个包里的每种物品最多只有一件,但两个不同的包中可以存在相同的物品。 
任意两个人,他们包里的物品清单不能完全相同。 
在满足以上要求的前提下,所有包里的所有物品的总价值最大是多少呢?

 

思路:可以将k个人看做一个人,就是一个人前k优化方案,原来的一维背包变为二维的,表示体积为i的第j优解,用两个队列更新,每次选两个队列中较大的一个,放入f[i][j]。

 

复制代码
#include<cstdio>
#include<iostream>
using namespace std;
int f[5001][51]={0},a[201]={0},w[201]={0},x,y,qx[51]={0},qy[51]={0};
int main()
{
    int k,v,n,i,j,t;
    long long ans=0;
    cin>>k>>v>>n;
    for (i=1;i<=n;++i)
      cin>>a[i]>>w[i];
    for (j=0;j<=v;++j)
      for (t=0;t<=k;++t)
        f[j][t]=-210000000;
    f[0][1]=0;
    for (i=1;i<=n;++i)
      for (j=v;j>=a[i];--j)
      {
         x=y=1;
         for (t=1;t<=k;++t)
         {
            qx[t]=f[j][t];
            qy[t]=f[j-a[i]][t];
         }
         for (t=1;t<=k;++t)
         {
              if (qx[x]==-210000000&&qy[y]==-210000000) break;
              f[j][t]=qx[x];
              ++x;
              if (qy[y]+w[i]>f[j][t])
              {
                --x;
                f[j][t]=qy[y]+w[i];
                ++y;
              }
         }
      }
    for (i=1;i<=k;++i)
      ans=ans+f[v][i];
    cout<<ans<<endl;
}
复制代码
RZUC Code

 

HAOI2015树上染色

题目大意:给树上n个点染k个黑色,求黑点之间、白点之间距离和的最大值。

思路:感觉是树形dp,但是觉得转移的时候很难受。后来听到fye大神的背包,就开始想背包方向想,发现多重背包可以解决。我们dfs一个孩子之后就可以进行背包了。但是赋初值的时候,要给f[u][0]和f[u][1]都赋为0,因为对于u这个根节点,我们可以涂两种颜色,初始值中的f[u][0]表示把它涂成白色,而f[u][1]表示涂成黑色,其他都是-∞就可以了。

View Code

 

SCOI2008着色方案

题目大意:有k种不同的颜色,每种颜色有ci种,求相邻涂不同颜色的方案数。

思路:一开始并不知道该怎么写,后来在其他大神的讲解下,才注意到ci<=5,然后听说用六维数组,然后就开始写,发现挂了。。。又听了他们的讲解。发现动态转移中六个变量a、b、c、d、e、i,分别保存剩余一个、两个、三个……五个的染色种类数和上一个涂的颜色是1~5中的哪一个,因为是上一次的,所以我们在下一层比较的时候要-1。然后用记忆化搜索。f[a][b][c][d][e][i]=(a-(i==2))*f[a-1][b][c][d][e][1]+(b-(i==3))*f[a+1][b-1][c][d][e][2]+……e*f[a][b][c][d+1][e-1][5]。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#define inf 1000000007
using namespace std;
long long f[16][16][16][16][16][6]={0};
int ci[16]={0},num[6]={0},k;
bool visit[16][16][16][16][16][6]={false};
long long dp(int a,int b,int c,int d,int e,int co)
{
    long long ans=0;
    int i;
    if (a<0||b<0||c<0||d<0||e<0) return 0;
    if (!a&&!b&&!c&&!d&&!e) return 1;
    if (visit[a][b][c][d][e][co]) return f[a][b][c][d][e][co];
    ans=(ans+(a-(co==2))*dp(a-1,b,c,d,e,1))%inf;
    ans=(ans+(b-(co==3))*dp(a+1,b-1,c,d,e,2))%inf;
    ans=(ans+(c-(co==4))*dp(a,b+1,c-1,d,e,3))%inf;
    ans=(ans+(d-(co==5))*dp(a,b,c+1,d-1,e,4))%inf;
    ans=(ans+e*dp(a,b,c,d+1,e-1,5))%inf;
    visit[a][b][c][d][e][co]=true;
    return f[a][b][c][d][e][co]=ans;
}
int main()
{
    int i,j;
    scanf("%d",&k);
    for (i=1;i<=k;++i) 
    {
       scanf("%d",&ci[i]);
       ++num[ci[i]];
    }
    printf("%lld\n",dp(num[1],num[2],num[3],num[4],num[5],0));
}
复制代码
View Code

 

HAOI2007上升序列

题目大意:给定一个序列,求任意长度的上升子序列,要求字典序最小(这里的字典序是位置最小)

思路:用nlogn的做法求最长上升子序列,然后从头往后扫m遍,找后面的值大的同时f数组满足相应条件的值输出。求f数组的时候,用lower_bound wa了,但用upper_bound就ac了。。。

View Code

 

bzoj1019 SHOI汉诺塔

题目大意:给定移动优先级的汉诺塔游戏,求多少步走完。(AB表示从A移到B)

思路:一开始只会暴搜,但是n到30,肯定超时。后来才知道是dp。借鉴普通汉诺塔的思路,我们从一个柱子移到另一个,就是先移动i-1个,然后把最大的移走,然后再把这i-1个移到最大的上面。这里的优先级就要求我们可能要多移动几次柱子,从而满足要求。我们用fi[i][j]表示i上有j个圆盘移走的步数,gi[i][j]表示i上j个圆盘移到的柱子,a为当前的柱子,b为i-1个移到的柱子,c就是第三个柱子,c=3-a-b。我们分情况讨论一下:如果gi[b][i-1]=c,我们只需要把i-1个从a移到b,最大的一个从a上移走,然后把b上的i-1个移到c,这样最后移到了c;如果gi[b][i-1]=a,我们需要把i-1个从a移到b,最大的一个从a上移走,然后把i-1个从b移到a,把最大的一个从c移到b,然后把i-1个从a移到b上就可以了,这样最后移到了b。这样我们就可以写出dp了,同时初始化保证了每个柱子上的圆盘都是按优先级移动,之后的每一步都是满足优先级的。

这道题目题意很明确,同时有是常见模型汉诺塔问题的变化,值得反思。

复制代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<cstdio>
#define maxnode 35
using namespace std;
int move[7][2]={0},gi[3][maxnode]={0};
long long fi[3][maxnode]={0};
int main()
{
    int n,i,j,a,b,c;
    char ch;
    scanf("%d",&n);
    for (i=1;i<=6;++i)
      for (j=0;j<=1;++j)
      {
          while(scanf("%c",&ch)==1){if (ch>='A'&&ch<='C') break;}
          move[i][j]=ch-'A';
      }
    for (i=0;i<3;++i)
      for (j=1;j<=6;++j)
          if (move[j][0]==i)
          {
              fi[i][1]=1;gi[i][1]=move[j][1];break;
          }
    for (i=2;i<=n;++i)
      for (a=0;a<3;++a)
      {
          b=gi[a][i-1];c=3-a-b;
          fi[a][i]=fi[a][i-1]+1;
          if (gi[b][i-1]==c)
          {
              fi[a][i]+=fi[b][i-1];gi[a][i]=c;
          }
          else
          {
              fi[a][i]+=fi[b][i-1]+1+fi[a][i-1];gi[a][i]=b;
          }
      }
    printf("%lld\n",fi[0][n]);
}
复制代码
View Code

 

bzojHH去散步

题目大意:已知n个点m条边,问从a走k步到b有多少种方案。走的过程中不能往回走。

思路:如果没有往回走这个要求,我们只需要建立n*n的矩阵,对两点之间加边,然后矩乘一下就可以了。加上这个条件,我们就需要换一下矩阵,建立一个2m*2m的矩阵,表示一条边连向下一条边,然后我们对有公共点的并且不是相反边的加给矩阵,然后矩乘k-1次,最后找到从a开始的终点在b的矩阵中的位置加给答案就可以了。

复制代码
#include<iostream> 
#include<cstdio> 
#include<cstring> 
#include<algorithm> 
#define p 45989 
using namespace std; 
struct use{ 
    int num[125][125]; 
}m1,map; 
int m,point[25]={0},next[125]={0},st[125]={0},en[125]={0},tot=0; 
bool f[125][125]={false}; 
void add(int x,int y) 
{ 
    ++tot;next[tot]=point[x];point[x]=tot;st[tot]=x;en[tot]=y; 
    ++tot;next[tot]=point[y];point[y]=tot;st[tot]=y;en[tot]=x; 
} 
use cheng(use m1,use m2) 
{ 
    int i,j,k;use m3; 
    for (i=1;i<=m*2;++i) 
      for (j=1;j<=m*2;++j) 
      { 
        m3.num[i][j]=0; 
        for (k=1;k<=m*2;++k) m3.num[i][j]=(m3.num[i][j]+((m1.num[i][k]*m2.num[k][j])%p))%p; 
      } 
    return m3; 
} 
use mi(use m2,int t) 
{ 
    if (t==1) return m2; 
    use m3=mi(m2,t/2); 
    if (t%2==0) return cheng(m3,m3); 
    else return cheng(m3,cheng(m3,m2)); 
} 
int main() 
{ 
    int n,t,a,b,i,j,k,ai,bi,ans=0,sum=0; 
    scanf("%d%d%d%d%d",&n,&m,&t,&a,&b);++a;++b; 
    for (i=1;i<=m;++i) 
    { 
        scanf("%d%d",&ai,&bi);++ai;++bi; 
        add(ai,bi);f[i*2-1][i*2]=f[i*2][i*2-1]=true; 
    } 
    for (i=1;i<=m*2;++i) 
      for (j=1;j<=m*2;++j) 
        if (en[i]==st[j]&&!f[i][j]) ++map.num[i][j]; 
    if (t>1) m1=mi(map,t-1); 
    for (j=1;j<=2*m;++j) 
    { 
        sum=0; 
        for (k=1;k<=2*m;++k) 
             sum=(sum+((st[k]==a?1:0)*m1.num[k][j])%p)%p; 
        if (en[j]==b) ans=(ans+sum)%p; 
    } 
    printf("%d\n",ans); 
}
复制代码
View Code

 

bzoj1260 涂色

题目大意:给定一个大写字母的序列,每次可以染连续一段同种颜色,求最少几次能把空的序列染出给定序列。

思路:这道题目是一个很简单的dp,但是做的时候却只写了暴搜。这是一个区间dp,从小区间穷举区间后,分情况讨论,如果区间两端相等,就取[i+1,j],[i,j-1],[i+1,j-1]+1中的最小值;如果不一样,就穷举中间断点k,取min([i,k][k+1,j])。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxnode 51
using namespace std;
char ss[maxnode];
int f[maxnode][maxnode]={0};
int main()
{
    int n,i,j,k;
    memset(f,127/3,sizeof(f));
    scanf("%s",&ss);n=strlen(ss);
    for (i=n;i>=1;--i) ss[i]=ss[i-1];
    for (i=1;i<=n;++i) f[i][i]=1;
    for (i=n-1;i>=1;--i)
      for (j=i+1;j<=n;++j)
      {
          if (ss[i]==ss[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 (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]);
}
复制代码
View Code

 

bzoj1089 严格n元树

题目大意:求深度为d的非叶子节点儿子数均为n的树的个数。

思路:设f[i]表示深度小于等于i的n元树的个数,f[i]=f[i-1]^n+1,最后输出f[d]-f[d-1]就可以了。因为高精度的问题所以用了python。

关于递推式,考虑加入一个根节点,那么它的n个儿子都有f[i-1]种选择,所以有f[i-1]^n种+1(都不选的)。

复制代码
n,d=map(int, raw_input().split())
if d==0:
    print 1
else:
    f=[1]
    for i in range(0, d+1):
        f.append(f[i]**n+1)
    print f[d]-f[d-1]
复制代码
View Code

 

bzoj1037 生日聚会

题目大意:给m个男孩和n个女孩排队,求任意区间男女之差不超过k个的方案数。

思路:设f[i][j][a][b]表示取i个男孩、j个女孩,且在以i+j为结尾的任意一段中,男比女最多多a个、女比男最多多b个的方案数,考虑最后一个取男或者女,更新答案。最后答案就是f[m][n]的和。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define p 12345678
using namespace std;
int f[200][200][25][25]={0};
int main()
{
    int n,m,k,i,j,a,b,ans=0;
    scanf("%d%d%d",&n,&m,&k);
    f[0][0][0][0]=1;
    for (i=0;i<=n;++i)
      for (j=0;j<=m;++j)
        for (a=0;a<=k;++a)
          for (b=0;b<=k;++b)
          {
              if (i<n) 
                f[i+1][j][a+1][max(b-1,0)]=(f[i+1][j][a+1][max(b-1,0)]+f[i][j][a][b])%p;
              if (j<m)
                f[i][j+1][max(a-1,0)][b+1]=(f[i][j+1][max(a-1,0)][b+1]+f[i][j][a][b])%p;
          }
    for (a=0;a<=k;++a)
      for (b=0;b<=k;++b) ans=(ans+f[n][m][a][b])%p;
    printf("%d\n",ans);
}
复制代码
View Code

 

bzoj1025 游戏

题目大意:给定n个数字,它们可以通过置换最终回到原数列,记次数为xi,求所有可能的次数。

思路:相当于求x1+x2+...+xi=n的lcm(x1,x2,...,xi)的种类数。我们只需要关注分解质因数后相应指数最大的就可以了。可以证明两两互质的时候(也就是每个数都是pi^ai且pi不同时)不会重复,所以f[i][j]表示用前i个质数和为j的方案数,dp转移一下就可以了,f[i][j]=f[i-1][j]+sigma(t=1~...)f[i-1][j-pi^t]。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxnode 1500
#define LL long long
using namespace std;
int prime[maxnode]={0};
LL f[maxnode][maxnode]={0};
bool flag[maxnode]={false};
void shai(int n)
{
    int i,j;
    for (i=2;i<=n;++i)
    {
        if (!flag[i]) prime[++prime[0]]=i;
        for (j=1;j<=prime[0]&&i*prime[j]<=n;++j)
        {
            flag[i*prime[j]]=true;
            if (i%prime[j]==0) break;
        }
    }
}
int main()
{
    int n,i,j,t,mi;LL ans=0;
    scanf("%d",&n);
    shai(n);f[0][0]=1;
    for (i=1;i<=prime[0];++i)
      for (j=0;j<=n;++j)
      {
          f[i][j]=f[i-1][j];
        for (mi=prime[i];mi<=j;mi*=prime[i])
            f[i][j]+=f[i-1][j-mi];
      }
    for (i=0;i<=n;++i) ans+=f[prime[0]][i];
    printf("%I64d\n",ans);
}
复制代码
View Code

 

bzoj2298 problem a

题目大意:给定一些人的描述(ai个人的分比我高,bi个人的分比我低),判断最少几个人说谎(可能同分)。

思路:对于每个人建立一个[bi+1,n-ai]的线段,对于每一个这样的线段它的权值是min(区间长度,完全相同的区间个数),那么就转化成了求端点处也不能重复的线段覆盖最大权值问题,用n-f[n]就可以了。注意这里的取min()、右端点在左端点左边时要忽略。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxnode 100005
using namespace std;
struct use{
    int li,ri,va;
}edge[maxnode]={0},ai[maxnode]={0};
int f[maxnode]={0};
int cmp(const use &x,const use &y){return (x.li==y.li ? x.ri<y.ri : x.li<y.li);}
int cmp2(const use &x,const use &y){return x.ri<y.ri;}
int main()
{
    int n,i,j,aa,bb,tt=0,tot=0;
    scanf("%d",&n);
    for (i=1;i<=n;++i)
    {
        scanf("%d%d",&aa,&bb);
        if (aa+bb+1>n) continue;
        edge[++tt].li=bb+1;edge[tt].ri=n-aa;
    }
    sort(edge+1,edge+tt+1,cmp);
    for (i=1;i<=tt;++i)
    {
        j=i;
        while(j<n&&edge[j].li==edge[j+1].li&&edge[j].ri==edge[j+1].ri) ++j;
        ai[++tot].li=edge[i].li;ai[tot].ri=edge[i].ri;
        ai[tot].va=min(edge[i].ri-edge[i].li+1,j-i+1);i=j;
    }
    sort(ai+1,ai+tot+1,cmp2);j=1;
    for (i=1;i<=n;++i)
    {
        f[i]=f[i-1];
        while(j<=tot&&ai[j].ri==i) 
        {
           f[i]=max(f[i],f[ai[j].li-1]+ai[j].va);++j;
        }
    }
    printf("%d\n",n-f[n]);
}
复制代码
View Code

 

bzoj2431 逆序对数列

题目大意:给定n、k,求1~n排列中逆序对有k个的数量。

思路:f[i][j]表示前i个数有j个逆序对的种类,f[i][j]=sigma(k=0~min(j,i-1))f[i-1][j-k],这样是O(n^3),如果我们对第二维做个前缀和就可以是O(n^2)了。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxnode 2005
#define p 10000
using namespace std;
int f[maxnode][maxnode]={0},sum[maxnode][maxnode]={0};
int main()
{
    int n,k,i,j,t,up;
    scanf("%d%d",&n,&k);f[1][0]=1;
    for (i=0;i<=k;++i) sum[1][i]=1;
    for (i=2;i<=n;++i)
      for (j=0;j<=k;++j)
      {
          up=min(j,i-1);
          f[i][j]=((sum[i-1][j]-sum[i-1][j-up-1])%p+p)%p;
          sum[i][j]=(sum[i][j-1]+f[i][j])%p;
      }
    printf("%d\n",f[n][k]);
}
复制代码
View Code

 

bzoj1197 花仙子的魔法

题目大意:求施放m次魔法后n维空间中最多有几种花(每次施放魔法,都会以任意一个点为中心半径为r施放,与中心距离小于等于r的点这个二进制位上为1,否则为0)。

思路:设f[i][j]表示i维空间j次魔法。考虑新加入一个n维的空间,原来就有f[i][j-1]种了,可以划分出来的新的区域为相交空间(i-1维)、j-1个i-1维空间的分割方案,所以f[i][j]=f[i-1][j-1]+f[i][j-1]。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
LL f[20][155]={0};
int main()
{
    int i,j,n,m;scanf("%d%d",&n,&m);
    for (i=1;i<=n;++i) f[1][i]=2*i;
    for (i=2;i<=m;++i)
    {
      f[i][1]=2;
      for (j=2;j<=n;++j) f[i][j]=f[i][j-1]+f[i-1][j-1];
    }
    printf("%I64d\n",f[m][n]);
}
复制代码
View Code

 

bzoj4247 挂饰

题目大意:一开始你有一个挂钩,然后有n个挂饰,每个挂饰有一个价值和挂钩数量,求最大价值。

思路:f[i][j]表示取到第i个物品有j个空挂钩的最大价值。这样的话j可能到n^2,复杂度就是n^3,会tle,但是我们发现j取到n就可以了,不需要再往下取(不可能挂那么多个挂饰),所以复杂度为n^2,注意每次更新过来的时候,都是上一次的至少从1更新过来(不能在0个挂钩上挂东西),所以取个max(详见code)。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxnode 2005
using namespace std;
struct use{
    int ai,bi;
}wu[maxnode]={0};
int cmp(const use &x,const use &y){return x.ai>y.ai;}
int fi[maxnode][maxnode]={0};
int main()
{
    int n,m=0,i,j,ans=0;scanf("%d",&n);
    for (i=1;i<=n;++i) scanf("%d%d",&wu[i].ai,&wu[i].bi);
    memset(fi,128,sizeof(fi));fi[0][1]=0;
    sort(wu+1,wu+n+1,cmp);
    for (i=1;i<=n;++i)
      for (j=0;j<=n;++j)
        fi[i][j]=max(fi[i-1][j],fi[i-1][min(n,max(0,j-wu[i].ai)+1)]+wu[i].bi);
    for (i=0;i<=n;++i) ans=max(ans,fi[n][i]);
    printf("%d\n",ans);
}
复制代码
View Code

 

bzoj3594 方伯伯的玉米田

题目大意:给定一个序列,可以最多进行k次对一个区间+1的操作,问最后最少拔掉多少使剩下的为不下降。

思路:f[i][j]表示前i个j次提升(以i结尾)的最长的序列,f[i][j]=f[a][b]+1(a=0~i-1,b=0~j,ai[i]+j>=ai[a]+b),然后要用二维树状数组优化一下,第一维是高度,第二维是几次提升,注意这里dp更新的时候循环的顺序,不要让当前i的状态再去更新状态i。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int cc[6001][601]={0},ai[10005]={0};
int lowbit(int x){return x&(-x);}
int ask(int x,int y)
{
    int ans=0;
    for (int i=x;i;i-=lowbit(i))
      for (int j=y;j;j-=lowbit(j)) ans=max(ans,cc[i][j]);
    return ans;
}
void ins(int x,int y,int v)
{
    for (int i=x;i<=6000;i+=lowbit(i))
      for (int j=y;j<=600;j+=lowbit(j)) cc[i][j]=max(cc[i][j],v);
}
int main()
{
    int i,j,n,k,ci;scanf("%d%d",&n,&k);
    for (i=1;i<=n;++i) scanf("%d",&ai[i]);
    for (i=1;i<=n;++i)
      for (j=k;j>=0;--j){
          ci=ask(ai[i]+j,j+1)+1;ins(ai[i]+j,j+1,ci);
      }printf("%d\n",ask(6000,600));
}
复制代码
View Code

 

codevs1258 关路灯

题目大意:给定一排上的n个路灯,到最左点的距离和它们的功率,一个人从给定的某个路灯旁开始关灯,速度1m/s,求关完所有灯消耗的最小能量。

思路:fi[i][j][0]表示关上i~j的路灯停在左边的最小能量,fi[i][j][1]表示关上i~j的路灯停在右边的最小能量,gi[i][j]表示除i~j外的其它灯的功率和。然后区间dp更新。注意更新中从一边到另一边的距离和相应的功率。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxnode 1005
#define inf 1050000000LL
using namespace std;
struct use{int di,wi;}ai[maxnode]={0};
int fi[maxnode][maxnode][2]={0},gi[maxnode][maxnode]={0};
int cmp(const use&x,const use&y){return x.di<y.di;}
int main()
{
    int n,v,i,j,l,r,sum=0;scanf("%d%d",&n,&v);
    for (i=1;i<=n;++i)
      for (j=1;j<=n;++j) fi[i][j][0]=fi[i][j][1]=inf;
    fi[v][v][0]=fi[v][v][1]=0;
    for (i=1;i<=n;++i){scanf("%d%d",&ai[i].di,&ai[i].wi);sum+=ai[i].wi;}
    sort(ai+1,ai+n+1,cmp);
    for (i=1;i<=n;++i){
        gi[i][i]=sum-ai[i].wi;
        for (j=i+1;j<=n;++j) gi[i][j]=gi[i][j-1]-ai[j].wi;
    }
    for (i=2;i<=n;++i){
        for (l=1;l<=n-i+1;++l){
            r=l+i-1;
            fi[l][r][0]=min(fi[l+1][r][0]+gi[l+1][r]*(ai[l+1].di-ai[l].di),
                        min(fi[l+1][r][1]+gi[l+1][r]*(ai[r].di-ai[l].di),
                        min(fi[l][r-1][0]+gi[l][r-1]*(ai[r].di-ai[l].di)+gi[l][r]*(ai[r].di-ai[l].di),
                        fi[l][r-1][1]+gi[l][r-1]*(ai[r].di-ai[r-1].di)+gi[l][r]*(ai[r].di-ai[l].di))));
            fi[l][r][1]=min(fi[l+1][r][0]+gi[l+1][r]*(ai[l+1].di-ai[l].di)+gi[l][r]*(ai[r].di-ai[l].di),
                        min(fi[l+1][r][1]+gi[l+1][r]*(ai[r].di-ai[l].di)+gi[l][r]*(ai[r].di-ai[l].di),
                        min(fi[l][r-1][0]+gi[l][r-1]*(ai[r].di-ai[l].di),
                        fi[l][r-1][1]+gi[l][r-1]*(ai[r].di-ai[r-1].di))));
        }
    }printf("%d\n",min(fi[1][n][0],fi[1][n][1]));
}
复制代码
View Code

 

bzoj2660 最多的方案

题目大意:求把一个数用不同的Fibonacci数分解的不同方案。

思路:先把这个数分解成尽量靠右的Fibonacci数的和,因为fi[i]=fi[i-1]+fi[i-2],所以如果把选那些Fibonacci数压成01串的话,001和110的和是一样的,所以我们考虑一个10000001的串,它的和也是10000110、10011010、11101010的和,我们会发现多出来的这三种情况正好是这个1和上个1之间长度的一半。设dp[i][0/1]表示第i个贪心出来的Fibonacci数的下标选或者不选,那么dp[i][1]=dp[i-1][0]+dp[i-1][1],dp[i][0]=(ff[i]-ff[i-1])/2*dp[i-1][0]+(ff[i]-ff[i-1]-1)/2*dp[i-1][1],最后答案就是dp[][0]+dp[][1]。(注意Fibonacci数列问题的求解大多都要回归的递推式上!!!)

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
using namespace std;
LL fi[200]={0},dp[200][2]={0};
int ff[200]={0};
int main(){
    int i,j;LL n;scanf("%I64d",&n);
    fi[1]=1;fi[2]=2;
    for (fi[0]=3;fi[fi[0]-1]<=n;++fi[0]) fi[fi[0]]=fi[fi[0]-1]+fi[fi[0]-2];
    --fi[0];for (i=fi[0];i;--i) if (n>=fi[i]){ff[++ff[0]]=i;n-=fi[i];}
    for (i=1;i<=ff[0]/2;++i) swap(ff[i],ff[ff[0]+1-i]);
    dp[0][1]=1;j=ff[0];ff[0]=0;
    for (i=1;i<=j;++i){
        dp[i][1]=dp[i-1][0]+dp[i-1][1];
        dp[i][0]=(ff[i]-ff[i-1])/2*dp[i-1][0]+(ff[i]-ff[i-1]-1)/2*dp[i-1][1];
    }printf("%I64d\n",dp[j][0]+dp[j][1]);
}
复制代码
View Code

 

bzoj2302 problem c(!!!

题目大意:给定n个人,每个人一个编号,从1~n排座,先到编号处,如果有人就往后顺延,给定m个人的确定编号,求有多少种编号序列。

思路:先考虑无解,m个确定编号中相应编号的后缀和sum[i],如果比能安排的位置(n-i+1)多就是无解。然后考虑dp,fi[i][j]表示第i个人后面(包括第i个人)安排好了j个人,fi[i][j]=sigma(k=0~j)fi[i+1][j-k]*ci[j][k],考虑第i个人的确定能使k个人确定,j的循环范围就是最多能确定的人数n-i+1-sum[i],最后答案就是fi[1][n-m]。(这种dp太神了!!!)

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 305
#define LL long long
using namespace std;
int sum[maxm]={0},n,ai[maxm]={0};
LL fi[maxm][maxm],ci[maxm][maxm]={0};
bool judge(){
    int i,j;
    for (i=n;i;--i){
        sum[i]+=sum[i+1];
        if (sum[i]>n-i+1) return false;
    }return true;
}
int main(){
    int t,m,i,j,k,p,u,v;scanf("%d",&t);
    while(t--){
        scanf("%d%d%d",&n,&m,&p);memset(sum,0,sizeof(sum));
        memset(fi,0,sizeof(fi));memset(ai,0,sizeof(ai));
        for (i=1;i<=m;++i){scanf("%d%d",&u,&v);++sum[v];}
        if (!judge()) printf("NO\n");
        else{
            for (i=0;i<=n;++i){
               ci[i][0]=ci[i][i]=1;
               for (j=1;j<i;++j) ci[i][j]=(ci[i-1][j-1]+ci[i-1][j])%p;
            }fi[n+1][0]=1;
            for (i=n;i;--i)
                for (j=0;j<=n-i+1-sum[i];++j)
                    for (k=0;k<=j;++k)
                        fi[i][j]=(fi[i][j]+fi[i+1][j-k]*ci[j][k]%p)%p;
            printf("YES %I64d\n",fi[1][n-m]);
        }
    }
}
复制代码
View Code

 

bzoj1190 梦幻宝珠(!!!

题目大意:01背包,只是每个物品的体积为a*2^b(a<=10,b<=30)的形式。

思路:非常神的dp方法。考虑根据b的不同来分层,设fi[i][j]表示j*2^i+(w&(1<<i-1))的体积下最大价值,每一i层内部都是普通的背包,但是不同层之间是可以更新的,fi[i][j]=max(fi[i][j],fi[i][j-k]+fi[i-1][min(k*2+((w>>i-1)&1),1000)]),这里的j-k表示当前层用的体积,这个地方可以把fi[][]表示的意义都写出来进行理解,也可以举个栗子。这里的1000是可以用一个数组表示出更精确的上界的,但是为了省事,所以就直接用1000了。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define up 1000
using namespace std;
LL fi[50][1010];
int main(){
    int n,w,i,j,a,b,x,k;LL ans;
    while(scanf("%d%d",&n,&w)==2){
        if (n==-1&&w==-1) break;
        memset(fi,0,sizeof(fi));
        for (i=1;i<=n;++i){
            scanf("%d%d",&a,&x);b=0;
            while(~a&1){a>>=1;++b;}
            for (j=up;j>=a;--j) fi[b][j]=max(fi[b][j],fi[b][j-a]+(LL)x);
        }ans=0;
        for (i=0;i<=up;++i) ans=max(ans,fi[0][i]);
        for (i=1;i<=30&&(1<<i)<=w;++i)
          for (j=min(up,w>>i);j>=0;--j){
              for (k=0;k<=j;++k) 
              fi[i][j]=max(fi[i][j],fi[i][j-k]+fi[i-1][min(k*2+((w>>i-1)&1),up)]);
              ans=max(ans,fi[i][j]);
          }printf("%I64d\n",ans);
    }
}
复制代码
View Code

 

bzoj1296 粉刷匠

题目大意:给定n块长度为m的木板,要求刷上0、1两种颜色,每次可以选择一个连续区间粉刷某种颜色,每个方块最多粉刷一次,求最多能有多少个方格符合要求。

思路:对每一块单独考虑(最后分组背包一下就行了),设fi[i][j]表示终点到i粉刷j次的最多符合的,fi[i][j]=max(fi[k][j-1]+max(sum[i]-sum[k],i-k-sum[i]+sum[k])),sum[i]表示到i的1的个数。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 100
#define maxe 3000
using namespace std;
int sum[maxm]={0},fi[maxm][maxm]={0},dp[maxe]={0};
char ss[maxm];
int main(){
    int i,j,n,m,k,q,u,v,t;scanf("%d%d%d",&n,&m,&t);
    for (i=1;i<=n;++i){
        scanf("%s",&ss);memset(sum,0,sizeof(sum));
        memset(fi,0,sizeof(fi));
        for (j=0;j<m;++j) sum[j+1]=sum[j]+ss[j]-'0';
        for (u=1;u<=m;++u)
          for (j=1;j<=m;++j)
            for (k=0;k<j;++k){
                v=sum[j]-sum[k];
                fi[j][u]=max(fi[j][u],fi[k][u-1]+max(v,j-k-v));
            }
        for (j=t;j;--j)
          for (k=min(j,m);k;--k) dp[j]=max(dp[j],dp[j-k]+fi[m][k]);
    }printf("%d\n",dp[t]);
}
复制代码
View Code

 

bzoj1084 最大子矩阵

题目大意:给定一个n*m的子矩阵(最大是100×2的),求其中k个子矩阵(互不重合)和最大。

思路:因为数据范围很小,所以一开始考虑状压dp,后来发现直接更新好像比状压还好写,所以就分m的1、2进行dp。m=1的时候就是选k段子段和最大;m=2的时候,fi[i][j][t]表示第一行到i、第二行到j、选出t个子矩阵的最大和,然后转移更新一下就可以了。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 105
using namespace std;
int si[3][maxm]={0},fi[maxm][maxm][20]={0};
int main(){
    int n,m,k,i,j,t,p,up;scanf("%d%d%d",&n,&m,&k);
    if (m==1){
        for (i=1;i<=n;++i){scanf("%d",&si[1][i]);si[1][i]+=si[1][i-1];}
        for (i=1;i<=n;++i)
          for (up=min(k,i),t=1;t<=up;++t)
            for (j=t-1;j<i;++j)
              fi[i][i][t]=max(fi[i][i][t],max(fi[j][j][t],fi[j][j][t-1]+si[1][i]-si[1][j]));
    }else{
        for (i=1;i<=n;++i){
            scanf("%d%d",&si[1][i],&si[2][i]);
            si[1][i]+=si[1][i-1];si[2][i]+=si[2][i-1];
        }for (i=1;i<=n;++i)
          for (j=1;j<=n;++j){
              if (i==j){
                  up=min(i,k);
                  for (t=1;t<=up;++t)
                    for (p=t-1;p<i;++p)
                      fi[i][j][t]=max(fi[i][j][t],max(fi[p][p][t],
                      fi[p][p][t-1]+si[1][i]-si[1][p]+si[2][j]-si[2][p]));
              }for (up=min(i,k),t=1;t<=up;++t)
                for (p=t-1;p<i;++p)
                  fi[i][j][t]=max(fi[i][j][t],max(fi[p][j][t],fi[p][j][t-1]+si[1][i]-si[1][p]));
              for (up=min(j,k),t=1;t<=up;++t)
                for (p=t-1;p<j;++p)
                  fi[i][j][t]=max(fi[i][j][t],max(fi[i][p][t],fi[i][p][t-1]+si[2][j]-si[2][p]));
          }
    }printf("%d\n",fi[n][n][k]);
}
复制代码
View Code

 

bzoj2734 集合选数

题目大意:给定n,从1~n的整数中选出一些子集要求x存在则2x、3x均不存在,求集合个数(空集也算)。

思路:考虑一个数x,做一个向左是*3、向下是*2的不完全的矩阵,那么问题就变成了求选矩阵中不相邻元素的方案数,状压dp一下就可以了。注意这个x要从1~n穷举一下,如果没有访问过就要做一遍。

注意:如果有清数组的话,一定不能开太大,防止tle。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 100005
#define p 1000000001LL
#define LL long long
using namespace std;
int cnt[maxm][20]={0},tot=0;
LL fi[18][2050]={0};
bool visit[maxm]={false};
void pre(int n){
    int i,j,x,y;
    for (i=1;i<=n;++i){
        if (visit[i]) continue; ++tot;
        for (x=i,j=1;x<=n;++j,x*=2){
          ++cnt[tot][0];visit[x]=true;
          for (y=x;y<=n;y*=3){++cnt[tot][j];visit[y]=true;}
        }
    }
}
int main(){
    int n,i,j,t,k,up;LL ans=1LL,sum;
    scanf("%d",&n);pre(n);
    for (i=1;i<=tot;++i){
        memset(fi,0,sizeof(fi));
        fi[0][0]=1LL;sum=0;
        for (j=1;j<=cnt[i][0];++j){
          up=(1<<cnt[i][j==1? 1 : j-1])-1;
          for (t=up;t>=0;--t){
            if (t&(t<<1)) continue;
            int up2=(1<<cnt[i][j])-1;
            for (k=up-t;k;k=(up-t)&(k-1)){
                if (k&(k<<1)) continue;
                if (k>up2) continue;
                fi[j][k]=(fi[j][k]+fi[j-1][t])%p;
            }fi[j][0]=(fi[j][0]+fi[j-1][t])%p;
          }
        }for (j=(1<<cnt[i][cnt[i][0]])-1;j>=0;--j) sum=(sum+fi[cnt[i][0]][j])%p;
        ans=ans*sum%p;
    }printf("%I64d\n",ans);
}
复制代码
View Code

 

bzoj1801 中国象棋

题目大意:求n*m的棋盘中放炮的互不攻击的方案数。

思路:如果其中一个不超过8,我们可以状压dp。因为炮的特性,所以一行一列中最多有两个炮,所以我们可以设fi[i][j][k]表示第i行有j列为一个、k列为2个,然后考虑第i行放0、1、2个炮,1个的又分为从0到1和从1到2;2个的又分从00到11、从11到22、从01到12。分别转移,最后第n列的所有情况就是答案。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 105
#define LL long long
#define p 9999973
using namespace std;
LL fi[maxm][maxm][maxm]={0};
LL c(int x){return (LL)x*(LL)(x-1)/2%p;}
int main(){
    int n,m,i,j,k;LL ans=0;
    scanf("%d%d",&n,&m);if (n<m) swap(n,m);
    fi[0][0][0]=1;
    for (i=1;i<=n;++i)
      for (j=0;j<=m;++j)
        for (k=0;k<=m-j;++k){
            fi[i][j][k]=fi[i-1][j][k];
            if (j) fi[i][j][k]=(fi[i][j][k]+fi[i-1][j-1][k]*(LL)(m-j-k+1)%p)%p;
            if (k) fi[i][j][k]=(fi[i][j][k]+fi[i-1][j+1][k-1]*(LL)(j+1)%p)%p;
            if (j>=2) fi[i][j][k]=(fi[i][j][k]+fi[i-1][j-2][k]*c(m-j-k+2)%p)%p;
            if (k>=2) fi[i][j][k]=(fi[i][j][k]+fi[i-1][j+2][k-2]*c(j+2)%p)%p;
            if (k&&j) fi[i][j][k]=(fi[i][j][k]+fi[i-1][j][k-1]*(LL)j%p*(LL)(m-j-k+1)%p)%p;
            if (i==n) ans=(ans+fi[i][j][k])%p;
        }printf("%I64d\n",ans);
}
复制代码
View Code

 

bzoj1222 产品加工

题目大意:给定n个产品和他们在A、B、AB上加工的时间。求加工完所有的最小时间。

思路:fi[i]表示在A上加工i时间B上最少的时间,滚动数组更新一下就可以了。注意状态的设计。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 30005
using namespace std;
int ti[maxm][3],fi[2][maxm];
int main(){
    int i,j,n,minn,cur=0,sta=0,ans;scanf("%d",&n);
    memset(fi,127/3,sizeof(fi));ans=fi[0][0];
    for (i=1;i<=n;++i){
        minn=maxm;
        for (j=0;j<3;++j){
            scanf("%d",&ti[i][j]);
            ti[i][j]=(!ti[i][j] ? fi[0][0] : ti[i][j]);
            minn=min(minn,ti[i][j]);
        }sta+=minn;
    }fi[0][0]=0;
    for (i=1;i<=n;++i){
        cur^=1;
        for (j=sta;j>=0;--j){
            fi[cur][j]=ans;
            fi[cur][j]=min(fi[cur][j],fi[cur^1][j]+ti[i][1]);
            if (j>=ti[i][0])
                fi[cur][j]=min(fi[cur][j],fi[cur^1][j-ti[i][0]]);
            if (j>=ti[i][2])
                fi[cur][j]=min(fi[cur][j],fi[cur^1][j-ti[i][2]]+ti[i][2]);
        }
    }for (i=0;i<=sta;++i) ans=min(ans,max(i,fi[cur][i]));
    printf("%d\n",ans);
}
复制代码
View Code

 

bzoj1090 字符串折叠

题目大意:给定一个字符串和折叠的定义(连续的重复一段可以缩成x(s)的形式,X是重复次数,s是重复子段),求它最短折叠长度。

思路:区间dp,枚举中间断点,但如果后面是前面重复的,就可以再更新。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 105
using namespace std;
int fi[maxm][maxm];
char ss[maxm];
bool judge(int x1,int y1,int x2,int y2){
    int i,j;
    if ((y2-x2+1)%(y1-x1+1)) return false;
    for (j=x1,i=x2;i<=y2;++i,j=(j==y1?x1:j+1))
        if (ss[j]!=ss[i]) return false;
    return true;
}
int calc(int x){
    int i=0;
    while(x){++i;x/=10;}
    return i;
}
int main(){
    int i,j,k,l,u,v;scanf("%s",ss+1);
    l=strlen(ss+1);
    for (k=1;k<=l;++k)
      for (u=1;u+k-1<=l;++u){
          v=u+k-1;fi[u][v]=k;
          for (i=u;i<v;++i){
              fi[u][v]=min(fi[u][v],fi[u][i]+fi[i+1][v]);
              if (judge(u,i,i+1,v))
                fi[u][v]=min(fi[u][v],fi[u][i]+2+calc(k/(i-u+1)));
          }
      }printf("%d\n",fi[1][l]);
}
复制代码
View Code

 

poj1390 blocks(!!!

题目大意:消方块游戏,消去同颜色长度为x的区间得分为x^2,求最多得分。

思路:可以把相同颜色的区间合并作为色块,len[i]为长度,color[i]为颜色,设fi[i][j][k]表示i到j个色块后面还有k个和j色块颜色相同的块长。1)l=r时,就是sqr(len[j]+k);2)如果j和后面的合并,就是fi[i][j][k]=fi[i][j-1][0]+sqr(len[j]+k)^2;3)如果j和i~j间某一段合并,就是fi[i][j][k]=fi[i][s][k+len[j]]+fi[s+1][j-1][0]。

注意:dp状态的设计要考虑到所有可能情况。

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 205
using namespace std;
int fi[maxm][maxm][maxm],ai[maxm],color[maxm],len[maxm],cnt;
int sqr(int x){return x*x;}
int dp(int l,int r,int kk){
    if (fi[l][r][kk]) return fi[l][r][kk];
    if (l==r) return fi[l][r][kk]=sqr(len[l]+kk);
    fi[l][r][kk]=dp(l,r-1,0)+sqr(len[r]+kk);
    for (int i=l;i<r-1;++i)
        if (color[i]==color[r])
            fi[l][r][kk]=max(fi[l][r][kk],dp(l,i,kk+len[r])+dp(i+1,r-1,0));
    return fi[l][r][kk];
}
int main(){
    int n,i,j,t,x;scanf("%d",&t);
    for (x=1;x<=t;++x){
        scanf("%d",&n);memset(fi,0,sizeof(fi));
        for (i=1;i<=n;++i) scanf("%d",&ai[i]);
        color[cnt=1]=ai[1];len[1]=1;
        for (i=2;i<=n;++i){
            if (ai[i]==ai[i-1]) ++len[cnt];
            else{color[++cnt]=ai[i];len[cnt]=1;}
        }printf("Case %d: %d\n",x,dp(1,cnt,0));
    }
}
View Code

 

poj3612 Telephone Wire

题目大意:给定一个序列,可以对一个位置+x,代价为x^2;序列的最终代价还要加上相邻元素差的绝对值*c,求最小代价。

思路:高度<=100,所以有一种比较暴力的dp可以做到1e9的复杂度,但要寻求优化。fi[i][j]=min(fi[i-1][k]+|j-k|c+(j-ai[i])^2),化简之后就有1)j>=k时,fi[i][j]=(j-ai[i])^2+jc+min(-kc+fi[i-1][k]);2)j<k时,fi[i][j]=(j-ai[i])^2-jc+min(kc+fi[i-1][k])。所以我们可以用两个数组维护min里面的东西,然后转移的时候就少一个100了。

注意:对dp方程的化简可以优化复杂度。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 105
#define maxx 100005
using namespace std;
int fi[2][maxm],ai[maxx]={0},gi[maxm][2];
int sqr(int x){return x*x;}
int main(){
    int n,c,i,j,ans,cur;
    while(scanf("%d%d",&n,&c)==2){
        for (i=1;i<=n;++i) scanf("%d",&ai[i]);
        cur=0;memset(fi[cur],127/3,sizeof(fi[cur]));
        ans=fi[cur][0];
        for (i=ai[1];i<=100;++i) fi[cur][i]=sqr(i-ai[1]);
        for (i=2;i<=n;++i){
            cur^=1;memset(fi[cur],127/3,sizeof(fi[cur]));
            memset(gi,127/3,sizeof(gi));
            for (j=100;j;--j) gi[j][0]=min(gi[j+1][0],fi[cur^1][j]+j*c);
            for (j=1;j<=100;++j) gi[j][1]=min(gi[j-1][1],fi[cur^1][j]-j*c);
            for (j=ai[i];j<=100;++j)
              fi[cur][j]=sqr(j-ai[i])+min(gi[j][0]-j*c,gi[j][1]+j*c);
        }for (i=1;i<=100;++i) ans=min(ans,fi[cur][i]);
        printf("%d\n",ans);
    }
}
复制代码
View Code

 

hdu5564 Clarke and digits

题目大意:求7的倍数,长度在l~r之间,相邻两位之和不为k的数的个数。

思路:fi[i][j][k]表示i位选j余数为k的方案数,然后用矩阵优化一下。注意矩阵的初始。有一维是前缀和,和的转移是可以从0开始的,但是第一位不可以是0。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define up 10
#define si 7
#define maxm 75
#define p 1000000007
#define LL long long
using namespace std;
struct use{
    LL num[maxm][maxm];
}ji,lm;
int idx(int x,int y){return 10*y+x+1;}
use cheng(use a,use b){
    use c;int i,j,k;
    for (i=1;i<maxm;++i)
      for (j=1;j<maxm;++j){
          c.num[i][j]=0LL;
          for (k=1;k<maxm;++k) c.num[i][j]=(c.num[i][j]+a.num[i][k]*b.num[k][j]%p)%p;
      }return c;
}
use mi(use a,int x){
    if (x==1) return a;
    use mm=mi(a,x/2);
    if (x%2) return cheng(a,cheng(mm,mm));
    else return cheng(mm,mm);
}
int main(){
    int t,i,j,a,b,l,r,k;LL ans;
    scanf("%d",&t);
    while(t--){
        scanf("%d%d%d",&l,&r,&k);
        memset(ji.num,0,sizeof(ji.num));
        memset(lm.num,0,sizeof(lm.num));
        for (i=1;i<up;++i) lm.num[1][idx(i,i%si)]=1LL;
        for (i=0;i<up;++i)
          for (j=0;j<up;++j){
              if (i+j==k) continue;
              for (a=0;a<si;++a)
                ji.num[idx(i,a)][idx(j,(a*10+j)%si)]=1LL;
          }ji.num[71][71]=1LL;
        for (i=0;i<up;++i) ji.num[idx(i,0)][71]=1LL;
        use ci=cheng(lm,mi(ji,r));ans=ci.num[1][71];
        if (l>1){
            ci=cheng(lm,mi(ji,l-1));
            ans=((ans-ci.num[1][71])%p+p)%p;
        }printf("%I64d\n",ans);
    }
}
复制代码
View Code

 

bzoj1996 合唱队

题目大意:给定一个序列,从前往后,如果这个数小于之前的数就插入到队列的左边,如果大于就插入到右边。现给定最后的队列,求一开始有多少种序列满足要求。

思路:插入队列可以看做从队列里取,就是从左右两边取,所以fi[i][j][k]表示i~j的区间选(k=0左k=1右)时的方案数,只需要比较这个区间左右端点和能更新过来的区间左右端点的大小就可以了,因为长度为1的区间只计入一次答案,所以fi[i][i][0]=1,fi[i][i][1]=0就可以了。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define maxm 1005
#define LL long long
#define p 19650827
using namespace std;
LL fi[maxm][maxm][2]={0LL};
int ai[maxm];
void jia(LL &x,LL y){x=(x+y)%p;}
int main(){
    int n,i,j,k;scanf("%d",&n);
    for (i=1;i<=n;++i) scanf("%d",&ai[i]);
    for (i=1;i<=n;++i) fi[i][i][0]=1LL;
    for (k=2;k<=n;++k)
      for (i=1;i+k-1<=n;++i){
          j=i+k-1;
          if (ai[i]<ai[i+1]) jia(fi[i][j][0],fi[i+1][j][0]);
          if (ai[i]<ai[j]) jia(fi[i][j][0],fi[i+1][j][1]);
          if (ai[j]>ai[i]) jia(fi[i][j][1],fi[i][j-1][0]);
          if (ai[j]>ai[j-1]) jia(fi[i][j][1],fi[i][j-1][1]);
      }printf("%I64d\n",(fi[1][n][0]+fi[1][n][1])%p);
}
复制代码
View Code

 

bzoj4347 Nim z utrudnieniem

题目大意:给定n堆石子,从中取出d的倍数堆石子后进行nim游戏,问使后手必胜的取堆数的方案数。

思路:就是求取出d的倍数堆石子后余下的nim和为0的方案数,所以fi[i][j][k]表示前i堆取j(%d)堆石子余下的nim和为k的方案数,排序之后保证了k会单增,不会因为ai而去更新极大值,因为sigma ai[i]=m,所以总的复杂度就是md。这题卡内存,滚动数组也不能解决,所以要找新的方法。可以发现fi[i][j][k]的转移中只用到了fi[i-1][j-1][k]+fi[i-1][j][k^ai[i]],后一项是与当前j有关的,而这项的转移和被转移项的转移是可以同时更新且只有这个位置更新的,所以可以一起转移,要特殊处理0的时候(再开一个数组记录一下)。注意:如果n是d的倍数的时候要-1(一开始不能全取完)。

复制代码
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#define p 1000000007
#define N 500005
#define up 1050000
using namespace std;
int ai[N],fi[10][up]={0},gi[up];
int main(){
    int n,i,j,k,d,uu,x;
    scanf("%d%d",&n,&d);
    for (i=1;i<=n;++i) scanf("%d",&ai[i]);
    sort(ai+1,ai+n+1);fi[0][0]=1;
    for (i=1;i<=n;++i){
        uu=1;while(uu<=ai[i]) uu<<=1;
        for (k=0;k<uu;++k) gi[k]=(fi[d-1][k]+fi[0][k^ai[i]])%p;
        for (j=d-1;j;--j)
          for (k=0;k<uu;++k){
              if (k>(k^ai[i])) continue;
              x=fi[j][k];
              fi[j][k]=(fi[j-1][k]+fi[j][k^ai[i]])%p;
              fi[j][k^ai[i]]=(fi[j-1][k^ai[i]]+x)%p;
          }for (k=0;k<uu;++k) fi[0][k]=gi[k];
    }printf("%d\n",(fi[0][0]-(n%d==0)+p)%p);
}
复制代码
View Code

 

bzoj2423 最长公共子序列

题目大意:给定两个字符串求最长公共子序列的长度和个数。

思路:dp维护很简单,但是要注意个数的时候:如果fi[i][j]=fi[i-1][j-1],ij的方案里面要减去i-1j-1的方案。

复制代码
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define N 5005
#define p 100000000
using namespace std;
int fi[2][N]={0},gi[2][N]={0};
char s1[N],s2[N];
char in(){
    char ch=getchar();
    while((ch<'A'||ch>'Z')&&ch!='.') ch=getchar();
    return ch;}
void jia(int &x,int y){x=(x+y)%p;}
void jian(int &x,int y){x=((x-y)%p+p)%p;}
int main(){
    int n,i,j,l1=0,l2=0,cur=0;char ch;
    while((ch=in())!='.') s1[++l1]=ch;
    while((ch=in())!='.') s2[++l2]=ch;
    for (i=0;i<=l2;++i) gi[cur][i]=1; 
    for (i=1;i<=l1;++i){
        memset(fi[cur^=1],0,sizeof(fi[cur]));
        memset(gi[cur],0,sizeof(gi[cur]));
        gi[cur][0]=1;
        for (j=1;j<=l2;++j){
            if (fi[cur^1][j]>=fi[cur][j]){
                if (fi[cur^1][j]==fi[cur][j]) jia(gi[cur][j],gi[cur^1][j]);
                else{fi[cur][j]=fi[cur^1][j];gi[cur][j]=gi[cur^1][j];}
            }if (fi[cur][j-1]>=fi[cur][j]){
                if (fi[cur][j-1]==fi[cur][j]) jia(gi[cur][j],gi[cur][j-1]);
                else{fi[cur][j]=fi[cur][j-1];gi[cur][j]=gi[cur][j-1];}
            }if (s1[i]==s2[j]&&fi[cur^1][j-1]+1>=fi[cur][j]){
                if (fi[cur^1][j-1]+1==fi[cur][j]) jia(gi[cur][j],gi[cur^1][j-1]);
                else{fi[cur][j]=fi[cur^1][j-1]+1;gi[cur][j]=gi[cur^1][j-1];}
            }if (fi[cur][j]==fi[cur^1][j-1]) jian(gi[cur][j],gi[cur^1][j-1]);
        }
    }printf("%d\n%d\n",fi[cur][l2],gi[cur][l2]);
}
复制代码
View Code

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值