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 点此展开
划分型DP 动态规划 大陆地区 NOIP全国联赛提高组 2001年
代码:
不知道为什么?把网站的测试数据下到自己电脑上以后,测试全部能通过,可是在线评测不是“运行错误”,就是“超时”。
/*基本思路:预处理一个 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; }
/* 把字符串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; }
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; }
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; }
- 代码二(思路是相同的):
#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; }
- 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; }
分析:能否使用滚动数组,进行压缩空间? 答案是否定的。 让我们仔细看一下这个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大的数有关,所以不能用。