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[];
int lenclac,f[LEN][K],g[LEN][LEN];
bool head[LEN];
char clac[LEN];
void input()
{
memset(head,,sizeof(head));
memset(clac,,sizeof(clac));
memset(zd,,sizeof(zd));
memset(g,,sizeof(g));
memset(f,,sizeof(f));
scanf("%d%d",&p,&k);
for(int i=;i<p;++i)
scanf("%s",clac+*i+);
lenclac=strlen(clac+);
scanf("%d",&s1);
for(int i=;i<=s1;++i)
{
scanf("%s",zd[i].s+);
zd[i].len=strlen(zd[i].s+);
}
clac[]='';
clac[lenclac+]='\0';
/*如果这里不给clac[0]赋值的话,那么下面的strcpy会出错*/
}
void chuli()
{
sort(zd+,zd+s1+);
for(int i=;i<=s1;++i)
{
char cpy[LEN];
strcpy(cpy,clac);
int p;
while()
{
p=strstr(cpy,zd[i].s+)-cpy;
if(p<) break;
if(head[p])
{
for(int j=;j<=p;++j)
cpy[j]='';
continue;}/*不加这个赋值是1,会陷入死循环*/
head[p]=true;
for(int j=;j<=p;++j)
cpy[j]='';
for(int j=p+zd[i].len-;j<=lenclac;++j)
{
g[p][j]++;
f[j][]++;
}
}
}
}
void DP()
{
for(int i=;i<=k;++i)
for(int j=i;j<=lenclac;++j)
{
for(int t=i-;t<=j-;++t)/*t这里必须循环到j-1而不是j,一开始就犯了这个错误,必须保证前j个分成i份才可以,循环到j,那可能是前j个分成了i-1份,*/
{
f[j][i]=max(f[j][i],f[t][i-]+g[t+][j]);
}
}
}
int main()
{ scanf("%d",&n);
while(n--)
{
input();
chuli();
DP();
printf("%d\n",f[lenclac][k]);
}
return ;
}
/*
把字符串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=;
for(int i=;i<p;++i){
scanf("%s",tt);
for(int j=;tt[j];++j){
ss[len]=tt[j]; ++len;
}
}
ss[len]='\0';
scanf("%d",&s);
for(int i=;i<s;++i) scanf("%s",w[i]);
for(int i=;i<len;++i)
for(int j=;j<=k;++j) f[i][j]=;
}
int calc(int x,int y){ //count of words in ss[x..y]
int ans=;
for(int i=x;i<=y;++i){
int j,cur;
for(j=;j<s;++j){
for(cur=;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=;nn<n;++nn){
init();
for(int i=;i<=len;++i) f[i][]=calc(,i-);
for(int j=;j<=k;++j){
for(int i=j;i<=len;++i){
for(int x=;x<i-j;++x){
int tmp=calc(i-x,i-);
if(f[i][j]<f[i-x][j-]+tmp)
f[i][j]=f[i-x][j-]+tmp;
}
}
}
printf("%d\n",f[len][k]);
}
return ;
}

teacher's

2. codevs 1163 访问艺术馆--树形DP经典例题

1163 访问艺术馆

时间限制: 1 s

空间限制: 128000 KB

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

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

2016. 4.10 NOI codevs 动态规划练习-LMLPHP

输入描述 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[];
};
Node node[N];
int f[N][T];
int t,n=;
void build(int k)
{
++n;
int j=n;
scanf("%d%d",&node[n].tim,&node[n].val);
node[n].tim*=;/*计算上回去的时间,就直接把时间存成2倍*/
if(node[n].val)
{
node[n].child[]=node[n].child[]=;
return ;
}
node[j].child[]=n+;
build(n+);
node[j].child[]=n+;/*注意这里是node[j],储存在这个递归阶段的父节点,而不是n,因为n是全局变量,所以随时会变化*/
build(n+);
}
void input()
{
scanf("%d",&t);
int x,y;
build();
}
int DFS(int k,int ti)
{
int tt=ti;
if(f[k][tt]>) return f[k][tt];
ti-=node[k].tim;/*计算到达该点后当前的剩余时间*/
if(ti<=) return ;
if(node[k].val)/*如果是叶节点,就偷画*/
{
int c=;
int l=node[k].val;
while(ti>=&&l>=)/*注意这里不是node[k].val--,因为我们更新的时候,一个叶节点会被访问多次,这次减没了,下次来的时候就不对了*/
{
ti-=;
l--;//
c++;
}
return f[k][tt]=c;
}
for(int i=;i<=ti;++i)/*如果这不是叶节点,就把时间分为两份,一份去左孩子,一份去右孩子*/
{
int ans=;
ans+=DFS(node[k].child[],i);
ans+=DFS(node[k].child[],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(,t));
return ;
}

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[];
int n;
int main()
{
scanf("%d",&n);
f[]=;
f[]=;
for(int i=;i<=n;++i)
f[i]=*f[i-]+f[i-];
printf("%d",f[n]);
return ;
}

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[],b[],lena,lenb;
#include<cstring>
bool f[][];
int main()
{
scanf("%s%s",a+,b+);
lena=strlen(a+);
lenb=strlen(b+);
f[][]=true;
int l=;
while(a[l++]=='*')
{
f[l-][]=true;/*考虑前缀*的例子*/
}
for(int i=;i<=lena;++i)
{
bool ok=false;/*必须给ok初值是false,否则默认是true,会造成错误*/
for(int j=;j<=lenb;++j)
{
ok=ok||f[i-][]||f[i-][j];
if(a[i]=='?')
f[i][j]=f[i-][j-];
else if(a[i]=='*')
f[i][j]=ok;
else f[i][j]=f[i-][j-]&&(a[i]==b[j]);
}
}
if(f[lena][lenb]) printf("matched\n");
else printf("not matched\n");
return ;
}
代码二(思路是相同的):
#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+,b+);
lena=strlen(a+);
lenb=strlen(b+);
f[][]=true;
for(int i=;i<=lena;++i)
{
bool ok=f[i-][];/*这里是处理前缀*的情况*/
if(a[i]=='*') f[i][]=ok;
for(int j=;j<=lenb;++j)
{
ok=ok||f[i-][j];/*因为ok本身已经有了f[i-1][0],所以不必再加了*/
if(a[i]=='?')
f[i][j]=f[i-][j-];
else if(a[i]=='*')
f[i][j]=ok;
else f[i][j]=f[i-][j-]&&(a[i]==b[j]);
}
}
if(f[lena][lenb]) printf("matched\n");
else printf("not matched\n");
return ;
}
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行点数之差达到最小。

2016. 4.10 NOI codevs 动态规划练习-LMLPHP

对于图中的例子,只要将最后一个多米诺骨牌旋转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*];
void input()
{
int a,b;
scanf("%d",&n);
for(int i=;i<=n;++i)
{
scanf("%d%d",&a,&b);
w[i]=a-b;
}
}
void DP()
{
N=*n;/*进行平移的数,防止数组越界,5*n就是最大的差值了*/
memset(f,,sizeof(f));
f[][w[]+N]=;/*差值全部定义成a-b,那么对于前i张牌的状态,我们是可以直接得出的,这也是DP的边界*/
f[][-w[]+N]=;/**/
for(int i=;i<=n;++i)
for(int j=*n;j>=;--j)/*类似有01背包*/
{
if(j+w[i]>=&&j+w[i]<=*n)/*注意要下越界和上越界都判断,因为w[i]的正负是不一定的*/
f[i][j]=min(f[i][j],f[i-][j+w[i]]+);/*设t是前i-1个牌的某个翻法的差值推到f[i][j]这个状态,如果不翻牌,那么j=t+w[i],可以倒推出t的两个值,对应着翻牌与不翻牌*/
if(j-w[i]>=&&j-w[i]<=*n)
f[i][j]=min(f[i][j],f[i-][j-w[i]]);
}
if(f[n][*n]<Q) printf("%d\n",f[n][*n]);/*Q是我估计的最大翻转次数,这个用来判断当前的差值能不能通过翻牌得到,如果不能得到,一定比Q大,那么再向5*n的两侧找*/
else {
for(int i=*n-,j=*n+;j<=*n&&i>=;++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 ;
}
分析:
能否使用滚动数组,进行压缩空间?
答案是否定的。
让我们仔细看一下这个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大的数有关,
所以不能用。

  

05-20 20:51