码迷,mamicode.com
首页 > 其他好文 > 详细

2016. 4.10 NOI codevs 动态规划练习

时间:2016-04-10 17:44:18      阅读:250      评论:0      收藏:0      [点我收藏+]

标签:

1.codevs1040 统计单词个数

1040 统计单词个数

2001年NOIP全国联赛提高组

 时间限制: 1 s
 空间限制: 128000 KB
 题目等级 : 黄金 Gold
题目描述 Description

给出一个长度不超过200的由小写英文字母组成的字母串(约定;该字串以每行20个字母的方式输入,且保证每行一定为20个)。要求将此字母串分成k份(1<k<=40),且每份中包含的单词个数加起来总数最大(每份中包含的单词可以部分重叠。当选用一个单词之后,其第一个字母不能再用。例如字符串this中可包含this和is,选用this之后就不能包含th)(管理员注:这里的不能再用指的是位置,不是字母本身。比如thisis可以算做包含2个is)。
单词在给出的一个不超过6个单词的字典中。
要求输出最大的个数。

输入描述 Input Description

第一行为一个正整数(0<n<=5)表示有n组测试数据
每组的第一行有二个正整数(p,k)
p表示字串的行数;
k表示分为k个部分。
接下来的p行,每行均有20个字符。
再接下来有一个正整数s,表示字典中单词个数。(1<=s<=6)
接下来的s行,每行均有一个单词。

输出描述 Output Description

每行一个整数,分别对应每组测试数据的相应结果。

 

样例输入 Sample Input

1
1 3
thisisabookyouareaoh
4
is
a
ok
sab

样例输出 Sample Output

7

数据范围及提示 Data Size & Hint

this/isabookyoua/reaoh

分类标签 Tags 点此展开 

代码:
不知道为什么?把网站的测试数据下到自己电脑上以后,测试全部能通过,可是在线评测不是“运行错误”,就是“超时”。
技术分享
/*基本思路:预处理一个 g[i][j]表示i--j这个区间内有多少个单词?我是用的 
strstr, 函数完成的,寻找字串的位置、
怎么满足题目中要求的“当选用一个单词之后,其第一个字母不能再用,这里的不能再用指的是位置”?
我是设置了head这个标志位,既然这个首字母不能再用了,那么短的单词来充当这个位置,一定比长单词好,所以先把单词字典按照len排序,
DP方程:     f[j][i]=max(f[j][i],f[t][i-1]+g[t+1][j]);
把前j个分为i份的最优值,是通过枚举把前t个分为i-1份,和剩下的部分分为1份来得到的 
*/
#include<iostream>
using namespace std;
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LEN 250
#define K 41
int n,p,k,s1;
struct ZD{
    char s[LEN];
    int len;
    bool operator <(const ZD &q)
    const{return len<q.len;}
};
ZD zd[7];
int lenclac,f[LEN][K],g[LEN][LEN];
bool head[LEN];
char clac[LEN];
void input()
{
    memset(head,0,sizeof(head));
    memset(clac,0,sizeof(clac));
    memset(zd,0,sizeof(zd));
    memset(g,0,sizeof(g));
    memset(f,0,sizeof(f));
    scanf("%d%d",&p,&k);
    for(int i=0;i<p;++i)
    scanf("%s",clac+20*i+1);
    lenclac=strlen(clac+1);
    scanf("%d",&s1);
    for(int i=1;i<=s1;++i)
    {
        scanf("%s",zd[i].s+1);
        zd[i].len=strlen(zd[i].s+1);
    }
    clac[0]=1;
    clac[lenclac+1]=\0;
    /*如果这里不给clac[0]赋值的话,那么下面的strcpy会出错*/
}
void chuli()
{
    sort(zd+1,zd+s1+1);
    for(int i=1;i<=s1;++i)
    {
        char cpy[LEN];
        strcpy(cpy,clac);
        int p;
        while(1)
        {
            p=strstr(cpy,zd[i].s+1)-cpy;
            if(p<0) break;
            if(head[p])
            {
            for(int j=1;j<=p;++j)
            cpy[j]=1;
            continue;}/*不加这个赋值是1,会陷入死循环*/
            head[p]=true;
            for(int j=1;j<=p;++j)
            cpy[j]=1;
            for(int j=p+zd[i].len-1;j<=lenclac;++j)
            {
                g[p][j]++;
                f[j][1]++;
            }
        }
    }
}
void DP()
{
    for(int i=2;i<=k;++i)
      for(int j=i;j<=lenclac;++j)
      {
          for(int t=i-1;t<=j-1;++t)/*t这里必须循环到j-1而不是j,一开始就犯了这个错误,必须保证前j个分成i份才可以,循环到j,那可能是前j个分成了i-1份,*/
          {
              f[j][i]=max(f[j][i],f[t][i-1]+g[t+1][j]);
          }
      }
}
int main()
{
 
    scanf("%d",&n);
    while(n--)
    {
        input();
        chuli();
        DP();
        printf("%d\n",f[lenclac][k]);
    }
    return 0;
}
View Code
技术分享
/*
把字符串ss[0..len-1]划分为k部分的最优值,需考虑
把前i个字符划分成j个部分的最优值
f(i,j) =Max{f(i-x,j-1)+后x个字符中的单词个数}     (i>=j且x>=1,i-x>=j-1)
即1<=x<i-j
对于区间[ii..jj]中含有的单词个数,逐个统计以s[kk](ii<=kk<=jj)开头的单词即可, 
*/
#include <stdio.h>
#define maxlen 210
#define maxk 41
#define maxs 10
int n,p,k,s,f[maxlen][maxk],len;
char ss[maxlen],tt[maxlen],w[maxs][maxlen]; 
void init(){
    scanf("%d%d",&p,&k);
    len=0;
    for(int i=0;i<p;++i){
        scanf("%s",tt);
        for(int j=0;tt[j];++j){
            ss[len]=tt[j]; ++len;
        }
    }
    ss[len]=\0;
    scanf("%d",&s);
    for(int i=0;i<s;++i) scanf("%s",w[i]);
    for(int i=0;i<len;++i)
    for(int j=1;j<=k;++j) f[i][j]=0;
}
int calc(int x,int y){    //count of words in ss[x..y]
    int ans=0;
    for(int i=x;i<=y;++i){
        int j,cur;
        for(j=0;j<s;++j){
            for(cur=0;w[j][cur];++cur)
                if(i+cur>y||w[j][cur]!=ss[i+cur]) break;
            if(w[j][cur]==\0){
                ++ans; break;
            }
        }
    }
    return ans;
}
int main(){
    scanf("%d",&n);
    for(int nn=0;nn<n;++nn){
        init();    
        for(int i=1;i<=len;++i) f[i][1]=calc(0,i-1);
        for(int j=2;j<=k;++j){
            for(int i=j;i<=len;++i){
                for(int x=1;x<i-j;++x){
                    int tmp=calc(i-x,i-1);
                    if(f[i][j]<f[i-x][j-1]+tmp)
                        f[i][j]=f[i-x][j-1]+tmp;
                }
            }
        }
        printf("%d\n",f[len][k]);
    }
    return 0;
}
teacher‘s
2. codevs 1163 访问艺术馆--树形DP经典例题

1163 访问艺术馆

 时间限制: 1 s

空间限制: 128000 KB

 题目等级 : 大师 Master
题目描述 Description

    皮尔是一个出了名的盗画者,他经过数月的精心准备,打算到艺术馆盗画。艺术馆的结构,每条走廊要么分叉为二条走廊,要么通向一个展览室。皮尔知道每个展室里藏画的数量,并且他精确地测量了通过每条走廊的时间,由于经验老道,他拿下一副画需要5秒的时间。你的任务是设计一个程序,计算在警察赶来之前(警察到达时皮尔回到了入口也算),他最多能偷到多少幅画。

技术分享
输入描述 Input Description

第1行是警察赶到得时间,以s为单位。第2行描述了艺术馆得结构,是一串非负整数,成对地出现:每一对得第一个数是走过一条走廊得时间,第2个数是它末端得藏画数量;如果第2个数是0,那么说明这条走廊分叉为两条另外得走廊。数据按照深度优先得次序给出,请看样例

输出描述 Output Description

输出偷到得画得数量

样例输入 Sample Input

60

7 0 8 0 3 1 14 2 10 0 12 4 6 2

样例输出 Sample Output

2

数据范围及提示 Data Size & Hint

s<=600

走廊的数目<=100

题目分析:很明显,这是一棵二叉树,所以我们采用递归建树的方法。

              DP方程:因为这是一棵树,所以不是线性循环,使用记忆化搜索是比较容易实现的。

              对于每一个点:if这是叶节点,判断能拿到多少画

                                 if这不是叶节点,就把当前的时间平分给左右结点,(从0--tt)循环,统计出最大值

技术分享
/*首先,根据数据建立二叉树
定义f(tot,k)表示
在剩余时间是tot的情况下,出发去第k个结点为根的子树中,
能得到的画的最大数量。
时间tot的构成:
    到达及离开子树的时间:    2*t[k].v
    分别在左右子树中的时间: x    y
*/
#include<iostream>
using namespace std;
#include<cstdio>
#include<cstring>
#define T 601
#define N 201
struct Node{
    int tim,val,child[2];
};
Node node[N];
int f[N][T];
int t,n=0;
void build(int k)
{
    ++n;
    int j=n;
    scanf("%d%d",&node[n].tim,&node[n].val);
    node[n].tim*=2;/*计算上回去的时间,就直接把时间存成2倍*/
    if(node[n].val)
    {
        node[n].child[0]=node[n].child[1]=0;
        return ;
    }
    node[j].child[0]=n+1;
    build(n+1);
    node[j].child[1]=n+1;/*注意这里是node[j],储存在这个递归阶段的父节点,而不是n,因为n是全局变量,所以随时会变化*/ 
    build(n+1);
}
void input()
{
    scanf("%d",&t);
    int x,y;
    build(1);
}
int DFS(int k,int ti)
{
    int tt=ti;
    if(f[k][tt]>0) return f[k][tt];
    ti-=node[k].tim;/*计算到达该点后当前的剩余时间*/
    if(ti<=0) return 0;
    if(node[k].val)/*如果是叶节点,就偷画*/
    {
        int c=0;
        int l=node[k].val;
        while(ti>=5&&l>=1)/*注意这里不是node[k].val--,因为我们更新的时候,一个叶节点会被访问多次,这次减没了,下次来的时候就不对了*/
        {
            ti-=5;
            l--;//
            c++;
        }
        return f[k][tt]=c;
    }
    for(int i=0;i<=ti;++i)/*如果这不是叶节点,就把时间分为两份,一份去左孩子,一份去右孩子*/
    {
        int ans=0;
        ans+=DFS(node[k].child[0],i);
        ans+=DFS(node[k].child[1],ti-i);
        f[k][tt]=max(f[k][tt],ans);/*循环ti次更新出最大值,这也就是前面说的叶节点多次访问,不能把node[k].val--,否则会陷入死循环,一直return和进入*/
    }
    return f[k][tt];
}
int main()
{
    input();
    printf("%d\n", DFS(1,t));
    return 0;
}
View Code

3.NOI 4982:踩方格

4982:踩方格--一个简单但是需要动脑子想方程的题

总时间限制: 
1000ms
 
内存限制: 
65536kB
描述

有一个方格矩阵,矩阵边界在无穷远处。我们做如下假设:
a.    每走一步时,只能从当前方格移动一格,走到某个相邻的方格上;
b.    走过的格子立即塌陷无法再走第二次;
c.    只能向北、东、西三个方向走;
请问:如果允许在方格矩阵上走n步,共有多少种不同的方案。2种走法只要有一步不一样,即被认为是不同的方案。

输入
允许在方格上行走的步数n(n <= 20)
输出
计算出的方案数量
样例输入
2
样例输出
7
代码+分析:
技术分享
/*这里采用倒推法:以后遇到没法直接寻找题目,或者题目条件不是很全,一般都是有很简单的方程,仅仅与n有关的方程*/
/*设l[i],r[i],u[i],设为最后一步向左,右,上走到第i个格子的方案数目,那么它的前一步,根据题目中说的“走过的格子立即塌陷无法再走第二次”,可以得出
l[i]=u[i-1]+l[i-1],r[i]=r[i-1]+u[i-1],u[i]=u[i-1]+l[i-1]+r[i-1],(可以看出u[i]=f[i-1]); 
 f[i]= u[i]+ l[i]+ r[i]; 
 =2*(u[i-1]+r[i-1]+l[i-1])+u[i-1](代入上式)
 所以f[i]=2*[i-1]+f[i-2]  
*/
#include<cstdio>
long long int f[50];
int n;
int main()
{
    scanf("%d",&n);
    f[1]=3;
    f[2]=7;
    for(int i=3;i<=n;++i)
    f[i]=2*f[i-1]+f[i-2];
    printf("%d",f[n]);
    return 0;
 } 
View Code

4.NOI 6252:带通配符的字符串匹配

6252:带通配符的字符串匹配

总时间限制: 
1000ms
 
内存限制: 
65536kB
描述

通配符是一类键盘字符,当我们不知道真正字符或者不想键入完整名字时,常常使用通配符代替一个或多个真正字符。通配符有问号(?)和星号(*)等,其中,“?”可以代替一个字符,而“*”可以代替零个或多个字符。 

你的任务是,给出一个带有通配符的字符串和一个不带通配符的字符串,判断他们是否能够匹配。 

例如,1?456 可以匹配 12456、13456、1a456,但是却不能够匹配23456、1aa456; 
2*77?8可以匹配 24457798、237708、27798。

输入
输入有两行,每行为一个不超过20个字符的字符串,第一行带通配符,第二行不带通配符
输出
如果两者可以匹配,就输出“matched”,否则输出“not matched”
样例输入
1*456?
11111114567
样例输出
matched
基本思路:
因为是两个字符创匹配的问题,所以定义状态为f[i][j]表示a串的前i个与b串的前j个是否匹配。
然后对a[i]的值进行讨论:

if(a[i]==‘?‘)
f[i][j]=f[i-1][j-1];
else if(a[i]==‘*‘)
f[i][j]=f[i-1][k](0<=k<=j)这里*比较特殊,如果a[i]==‘*‘的话,那么只要f[i-1]与j及其之前的所有取值有一个f[i-1][k]匹配就可以了,因为*可以代表0--j所有的字符。为了减小循环次数,我们可以设一个ok变量来储存着结果
else f[i][j]=f[i-1][j-1]&&(a[i]==b[j]);

值得注意的一点:

    注意a的前多项都是*的例子,那么这个时候f[i][0]就是true了,对于这种情况,我们必须考虑到

代码一:
技术分享
#include<iostream>
using namespace std;
#include<cstdio>
char a[31],b[31],lena,lenb;
#include<cstring>
bool f[31][31];
int main()
{
    scanf("%s%s",a+1,b+1);
    lena=strlen(a+1);
    lenb=strlen(b+1);
    f[0][0]=true;
    int l=1;
    while(a[l++]==*)
    {
        f[l-1][0]=true;/*考虑前缀*的例子*/
    }
    for(int i=1;i<=lena;++i)
     {
       bool ok=false;/*必须给ok初值是false,否则默认是true,会造成错误*/
       for(int j=1;j<=lenb;++j)
      {
        ok=ok||f[i-1][0]||f[i-1][j];
          if(a[i]==?)
          f[i][j]=f[i-1][j-1];
          else if(a[i]==*)
          f[i][j]=ok;
        else f[i][j]=f[i-1][j-1]&&(a[i]==b[j]);
      }
     }
    if(f[lena][lenb]) printf("matched\n");
    else printf("not matched\n");
    return 0;
 } 
View Code
代码二(思路是相同的):
技术分享
#include<iostream>
using namespace std;
#include<cstdio>
#define N 25
#include<cstring>
int lena,lenb,f[N][N];
char a[N],b[N];
int main()
{
    scanf("%s%s",a+1,b+1);
    lena=strlen(a+1);
    lenb=strlen(b+1);
    f[0][0]=true;
    for(int i=1;i<=lena;++i)
    {
        bool ok=f[i-1][0];/*这里是处理前缀*的情况*/
        if(a[i]==*) f[i][0]=ok;
        for(int j=1;j<=lenb;++j)
        {
            ok=ok||f[i-1][j];/*因为ok本身已经有了f[i-1][0],所以不必再加了*/
            if(a[i]==?)
            f[i][j]=f[i-1][j-1];
            else if(a[i]==*)
            f[i][j]=ok;
            else f[i][j]=f[i-1][j-1]&&(a[i]==b[j]);
        }
    }
    if(f[lena][lenb]) printf("matched\n");
    else printf("not matched\n");
    return 0;
}
View Code
5.洛谷P1282 多米诺骨牌

 P1282 多米诺骨牌--01背包法

  • 标签动态规划
  • 难度提高+/省选-
  • 通过/提交282/964

题目描述

多米诺骨牌有上下2个方块组成,每个方块中有1~6个点。现有排成行的

上方块中点数之和记为S1,下方块中点数之和记为S2,它们的差为|S1-S2|。例如在图8-1中,S1=6+1+1+1=9,S2=1+5+3+2=11,|S1-S2|=2。每个多米诺骨牌可以旋转180°,使得上下两个方块互换位置。

编程用最少的旋转次数使多米诺骨牌上下2行点数之差达到最小。

技术分享

对于图中的例子,只要将最后一个多米诺骨牌旋转180°,可使上下2行点数之差为0。

输入输出格式

输入格式:

 

输入文件的第一行是一个正整数n(1≤n≤1000),表示多米诺骨牌数。接下来的n行表示n个多米诺骨牌的点数。每行有两个用空格隔开的正整数,表示多米诺骨牌上下方块中的点数a和b,且1≤a,b≤6。

 

输出格式:

 

输出文件仅一行,包含一个整数。表示求得的最小旋转次数。

 

输入输出样例

输入样例#1:
4
6 1
1 5
1 3
1 2
输出样例#1:
1

问题分析:

很明显,对于当前骨牌只有翻与不翻两种选择,就像是01背包取物品的时候的取与不取是相通的,那么我们就可以尝试用01背包解决,如果这个牌不翻,会是什么状态?如果翻了是什么状态?选择的标准就是犯的次数最少。

代码一:

未压缩空间版:

技术分享
#include<iostream>
using namespace std;
#include<cstdio>
#include<cstring>
#define INF 1010
#define Q 2000 
int n,N,w[INF];
int f[INF][INF*10];
void input()
{
    int a,b;
    scanf("%d",&n);
    for(int i=1;i<=n;++i)
    {
        scanf("%d%d",&a,&b);
        w[i]=a-b;
    }
}
void DP()
{
    N=5*n;/*进行平移的数,防止数组越界,5*n就是最大的差值了*/
    memset(f,127,sizeof(f));
    f[1][w[1]+N]=0;/*差值全部定义成a-b,那么对于前i张牌的状态,我们是可以直接得出的,这也是DP的边界*/
    f[1][-w[1]+N]=1;/**/
    for(int i=2;i<=n;++i)
      for(int j=10*n;j>=0;--j)/*类似有01背包*/
      {
          if(j+w[i]>=0&&j+w[i]<=10*n)/*注意要下越界和上越界都判断,因为w[i]的正负是不一定的*/
          f[i][j]=min(f[i][j],f[i-1][j+w[i]]+1);/*设t是前i-1个牌的某个翻法的差值推到f[i][j]这个状态,如果不翻牌,那么j=t+w[i],可以倒推出t的两个值,对应着翻牌与不翻牌*/
          if(j-w[i]>=0&&j-w[i]<=10*n)
          f[i][j]=min(f[i][j],f[i-1][j-w[i]]);
      }
    if(f[n][5*n]<Q) printf("%d\n",f[n][5*n]);/*Q是我估计的最大翻转次数,这个用来判断当前的差值能不能通过翻牌得到,如果不能得到,一定比Q大,那么再向5*n的两侧找*/
    else {
        for(int i=5*n-1,j=5*n+1;j<=10*n&&i>=1;++j,--i)
        {
            if((f[n][i]<Q||f[n][j]<Q))
            {
                printf("%d\n",min(f[n][i],f[n][j]));
                return ;
            }
        }
    }
    
}
int main()
{
    input();
    DP();
    return 0;
}
View Code
分析:
能否使用滚动数组,进行压缩空间? 答案是否定的。 让我们仔细看一下这个DP方程: if(j+w[i]>=0&&j+w[i]<=10*n) f[i][j]=min(f[i][j],f[i-1][j+w[i]]+1); if(j-w[i]>=0&&j-w[i]<=10*n) f[i][j]=min(f[i][j],f[i-1][j-w[i]]); 它的确符合只与上一层有关,但是遇上一层哪一个有关,就与01背包不同了,因为01背包倒序循环,
更新只与比二维j小的数有关,但是这个方程明显也可能与比二维j大的数有关,
所以不能用。

  

 

 

2016. 4.10 NOI codevs 动态规划练习

标签:

原文地址:http://www.cnblogs.com/c1299401227/p/5373398.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!