某蒟蒻のDP做题合集

题目来自:https://www.luogu.com.cn/contest/27854

A.最大正方形

Description

在一个n*m的只包含0和1的矩阵里找出一个不包含0的最大正方形,输出边长。

Solution

f[i][j]表示以节点i,j为右下角,可构成的最大正方形的边长。只有a[i][j]==1时,节点i,j才能作为正方形的右下角。

可推出转移方程:if(a[i][j]==1) f[i][j]=min(min(f[i][j-1],f[i-1][j]),f[i-1][j-1])+1;

 1 #include<bits/stdc++.h> 
 2 using namespace std;
 3 const int N=110;
 4 int a[N][N],n,m,f[N][N],ans;
 5 int main(){ 
 6     scanf("%d%d",&n,&m);
 7     for(int i=1;i<=n;i++)
 8         for(int j=1;j<=m;j++){
 9             scanf("%d",&a[i][j]);
10             if(a[i][j]==1) f[i][j]=min(min(f[i][j-1],f[i-1][j]),f[i-1][j-1])+1;
11             ans=max(ans,f[i][j]);
12         }
13     printf("%d\n",ans);
14 }
最大正方形

这个代码不开O2要跑46ms左右……原来有一发34ms的,结果交了两边一模一样的代码竟然全部WA掉了……咋肥四鸭。

B.[HAOI2016]食物链

Description

如图所示为某生态系统的食物网示意图,据图回答第1小题现在给你n个物种和m条能量流动关系,求其中的食物链条数。物种的名称为从1到n编号M条能量流动关系形如a1 b1a2 b2a3 b3......am-1 bm-1 am bm其中ai bi表示能量从物种ai流向物种bi,注意单独的一种孤立生物不算一条食物链。

Solution  待填坑……

C.[USACO08MAR]River Crossing S

Description

Flowering shrubs以及他的N(1<=N<=2,500)头奶牛打算过一条河, 他们划船过河,在整个渡河过程中,Fs必须始终在木筏上。 在这个基础上,木筏上的奶牛数目每增加1, Fs把木筏划到对岸就得花更多的时间。 当FJ一个人坐在木筏上, 他把木筏划到对岸需要K(1<=K<=1000)分钟。当木筏搭载的奶牛数目从i-1增加到i时,Fs得多花M[i](1<=M[i]<=1000)分钟才能把木筏划过河。 那么,Fs最少要花多少时间,才能把所有奶牛带到对岸呢? 当然,这个时间得包括Fs一个人把木筏从对岸划回来接下一批的奶牛的时间。

Solution

f[i]表示运送i头牛的最短时间。初始化f数组即为前缀和。运送n头奶牛所用的最短时间=min(先送k头的最短时间+再送剩下n-k头的最短时间+m)。

可推出转移方程:f[i]=min(f[i],f[j]+f[i-j]+m);

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=2510;
 4 int n,m,f[N],x;
 5 signed main(){
 6     scanf("%d%d",&n,&m),f[0]=m;
 7     for(int i=1;i<=n;i++)
 8         scanf("%d",&x),f[i]=x+f[i-1];
 9     for(int i=1;i<=n;i++)
10         for(int j=1;j<=i;j++)
11             f[i]=min(f[i],f[j]+f[i-j]+m);
12     printf("%d\n",f[n]);
13     return 0;
14 }
[USACO08MAR]River Crossing S

D.最大正方形II

Description

图上有一个矩阵,由N*M个格子组成,这些格子由两种颜色构成,黑色和白色。请找到面积最大的且内部是黑白交错(即两个相连的正方形颜色不能相同)的正方形。

Solution

f[i][j]表示右下角在(i,j)长度最大正方形。

对于每一个f[i][j],我们都希望去考虑他的子结构

包括:1.内部填充  2.向左边延伸  3.向右边延伸

然后对i,j这个点特殊处理一下即可(显然只有(a[i][j]==a[i-1][j])!=(a[i-1][j]==a[i][j-1]才合法)

转移方程:if(!(a[i][j]^a[i-1][j-1]^a[i-1][j]^a[i][j-1])) f[i][j]=min(f[i-1][j-1],f[i-1][j],f[i][j-1])+1;

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=1510;
 4 int n,m,a[N][N],f[N][N],x[N][N],y[N][N],ans;
 5 int main(){
 6     scanf("%d%d",&n,&m);
 7     for(int i=1;i<=n;i++)
 8         for(int j=1;j<=m;j++)
 9             scanf("%d",&a[i][j]);
10     for(int i=1;i<=n;i++)
11         for(int j=1;j<=m;j++)
12             if(!(a[i][j]^a[i-1][j-1]^a[i-1][j]^a[i][j-1])) f[i][j]=min(f[i-1][j-1],min(f[i-1][j],f[i][j-1]))+1,ans=max(ans,f[i][j]);
13     printf("%d\n",ans+1);
14     return 0;
15 }
最大正方形II

E.导弹拦截

Description

某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统。但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发炮弹都不能高于前一发的高度。某天,雷达捕捉到敌国的导弹来袭。由于该系统还在试用阶段,所以只有一套系统,因此有可能不能拦截所有的导弹。

输入导弹依次飞来的高度(雷达给出的高度数据是50000的正整数),计算这套系统最多能拦截多少导弹,如果要拦截所有导弹最少要配备多少套这种导弹拦截系统。

Solution

此蒟蒻太蒟了只会O(n^2)做法。

第一问很明显是求最长不上升子序列.设f[i]为以第i个数为开头的最长不上升子序列的长度。f[i]=max(f[i],f[j]+1);

1 for(int i=n;i>=1;i--){
2     f[i]=1;
3     for(int j=i+1;j<=n;j++)
4         if(a[j]<=a[i]) f[i]=max(f[i],f[j]+1);7     ans1=max(ans1,f[i]);
8 }

第二问相当于求最长上升子序列。f[i]表示以i结尾的最长上升子序列的长度。

1 for(int i=1;i<=n;i++){
2     f[i]=1;
3     for(int j=1;j<i;j++)
4         if(a[j]<a[i])  f[i]=max(f[i],f[j]+1);
5     ans2=max(ans2,f[i]); 
6 }

把两个代码合一起就ok了,神仙们阔以自己写。这里放一个奇怪的代码。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=1e5+5;
 4 int cnt,a[N],f[N],p,x,y,k;
 5 int main(){
 6     while(~scanf("%d",&x)) a[++cnt]=x;
 7     k=1,f[1]=a[1];
 8     for(int i=2;i<=cnt;i++){
 9         p=0;
10         for(int j=1;j<=k;j++)
11             if(f[j]>=a[i]){
12               if(p==0) p=j;
13               else if(f[j]<f[p]) p=j;
14             }
15         if(p==0) f[++k]=a[i];
16         else f[p]=a[i];
17     }
18     y=1;
19     for(int i=1;i<=cnt;i++){
20         x=1;
21         for(int j=1;j<i;j++)
22             if(a[j]>=a[i]&&f[j]+1>x) x=f[j]+1;
23         f[i]=x;
24         if(x>y) y=x;
25     }
26     printf("%d\n%d\n",y,k);
27     return 0;
28 }
导弹拦截

F.传球游戏

Description

上体育课的时候,小蛮的老师经常带着同学们一起做游戏。这次,老师带着同学们一起做传球游戏。

游戏规则是这样的:n个同学站成一个圆圈,其中的一个同学手里拿着一个球,当老师吹哨子时开始传球,每个同学可以把球传给自己左右的两个同学中的一个(左右任意),当老师再次吹哨子时,传球停止,此时,拿着球没有传出去的那个同学就是败者,要给大家表演一个节目。

聪明的小蛮提出一个有趣的问题:有多少种不同的传球方法可以使得从小蛮手里开始传的球,传了m次以后,又回到小蛮手里。两种传球方法被视作不同的方法,当且仅当这两种方法中,接到球的同学按接球顺序组成的序列是不同的。比如有三个同学1号、2号、3号,并假设小蛮为1号,球传了33次回到小蛮手里的方式有1->2->3->1和1->3->2->1,共2种。

Solution

f[i][j] 表示球传了i次后又回到位置1的总方法数。

第一个人单独处理:第2个人+第n个人。中间的人为左右之和。最后一个人单独处理。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=35;
 4 int n,m,f[N][N];
 5 int main(){
 6     //freopen(".in","r",stdin);
 7     //freopen(".out","w",stdout);
 8     scanf("%d%d",&n,&m);
 9     f[0][1]=1;
10     for(int i=1;i<=m;i++){
11         f[i][1]=f[i-1][2]+f[i-1][n];
12         for(int j=2;j<n;j++)
13             f[i][j]=f[i-1][j-1]+f[i-1][j+1];
14         f[i][n]=f[i-1][1]+f[i-1][n-1];
15     }
16     printf("%d\n",f[m][1]);
17     return 0;
18 }
传球游戏

G.挖地雷

Description

在一个地图上有N个地窖(N<=200),每个地窖中埋有一定数量的地雷。同时,给出地窖之间的连接路径,并规定路径都是单向的,也不存在可以从一个地窖出发经过若干地窖后又回到原来地窖的路径。某人可以从任一处开始挖地雷,然后沿着指出的连接往下挖(仅能选择一条路径),当无连接时挖地雷工作结束。设计一个挖地雷的方案,使他能挖到最多的地雷。

Solution

题目规定所有路径都是单向的,所以满足无后效性原则和最优化原理。设W[i]为第i个地窖所藏有的地雷数,A[i][j]表示第i个地窖与第j个地窖之间是否有通路,F[i]为从第i个地窖开始最多可以挖出的地雷数,则有如下递归式:

F[i]=max{W[i]+ F[j]} (i<j<=n , A[i][j]=true)

边界:F[n]=W[n]

于是就可以通过递推的方法,从后F(n)往前逐个找出所有的F[i],再从中找一个最大的即为问题2的解。对于具体所走的路径(问题1),可以通过一个向后的链接来实现。

代码中,保存从第i个地窖起能挖到最大地雷数,k地窖是i地窖最优路径

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 const int N=205;
 5 int f[N],w[N],c[N],n,x,y,l,k,mx;
 6 bool a[N][N];
 7 signed main(){
 8     scanf("%lld",&n);
 9     for(int i=1;i<=n;i++)
10         scanf("%lld",&w[i]);
11     for(int i=1;i<n;i++)
12         for(int j=i+1;j<=n;j++)
13             scanf("%d",&a[i][j]);
14     f[n]=w[n];
15     for(int i=n-1;i>=1;i--){
16         l=0,k=0;
17         for(int j=i+1;j<=n;j++)
18             if(a[i][j]&&f[j]>l) l=f[j],k=j;
19         f[i]=l+w[i],c[i]=k; 
20     }
21     k=1;
22     for(int i=2;i<=n;i++)
23         if(f[i]>f[k]) k=i;
24     mx=f[k],printf("%lld",k),k=c[k];
25     while(k!=0) printf(" %lld",k),k=c[k];
26     printf("\n%lld\n",mx);
27     return 0;
28 }
挖地雷

H.守望者的逃离

Description

恶魔猎手尤迪安野心勃勃,他背叛了暗夜精灵,率领深藏在海底的娜迦族企图叛变。守望者在与尤迪安的交锋中遭遇了围杀,被困在一个荒芜的大岛上。为了杀死守望者,尤迪安开始对这个荒岛施咒,这座岛很快就会沉下去。到那时,岛上的所有人都会遇难。守望者的跑步速度为17m/s17m/s,以这样的速度是无法逃离荒岛的。庆幸的是守望者拥有闪烁法术,可在1s1s内移动60m60m,不过每次使用闪烁法术都会消耗魔法值1010点。守望者的魔法值恢复的速度为44/s/s,只有处在原地休息状态时才能恢复。

现在已知守望者的魔法初值MM,他所在的初始位置与岛的出口之间的距离SS,岛沉没的时间TT。你的任务是写一个程序帮助守望者计算如何在最短的时间内逃离荒岛,若不能逃出,则输出守望者在剩下的时间内能走的最远距离。注意:守望者跑步、闪烁或休息活动均以秒(s)(s)为单位,且每次活动的持续时间为整数秒。距离的单位为米(m)(m)

Solution  待填坑……

I.医院设置

Description

设有一棵二叉树,如图:

其中,圈中的数字表示结点中居民的人口。圈边上数字表示结点编号,现在要求在某个结点上建立一个医院,使所有居民所走的路程之和为最小,同时约定,相邻接点之间的距离为 11。如上图中,若医院建在1 处,则距离和 =4+12+2\times20+2\times40=136=4+12+2×20+2×40=136;若医院建在 33 处,则距离和 =4\times2+13+20+40=81=4×2+13+20+40=81

Solution

跑个Floyd就没了。设f[i][j]表示结点i到结点j的最短路。从小到大枚举转结点,检查f[i][k]+f[k][j]<f[i][j]是否成立,若成立,则说明从i到k再到j的路径比i直接到j的路径更短,f[i][j]=f[i][k]+f[k][j],当我们遍历完所有结点k,那么f[i][j]就确定了。

(代码里的a数组就是f数组)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=110,inf=0x3f3f3f3f;
 4 int a[N][N],v[N],n,x,y,ans,sum;
 5 int main(){
 6     scanf("%d",&n),ans=inf;
 7     memset(a,inf,sizeof(a));
 8     for(int i=1;i<=n;i++){
 9         scanf("%d%d%d",&v[i],&x,&y);
10         a[x][i]=1,a[i][x]=1,a[y][i]=1,a[i][y]=1;
11     }
12     for(int k=1;k<=n;k++)
13         for(int i=1;i<=n;i++)
14             for(int j=1;j<=n;j++)
15                   if(i!=j&&j!=k) a[i][j]=min(a[i][j],a[i][k]+a[k][j]);
16     for(int i=1;i<=n;i++){
17         sum=0;
18         for(int j=1;j<=n;j++)
19             if(i!=j) sum+=a[i][j]*v[j];
20         ans=min(ans,sum);
21     }
22     printf("%d\n",ans);
23     return 0;
24 }
医院设置

J. [USACO2.3]Money System / [USACO07OCT]Cow Cash G

Description

母牛们不但创建了它们自己的政府而且选择了建立了自己的货币系统。由于它们特殊的思考方式,它们对货币的数值感到好奇。

传统地,一个货币系统是由 1,5,10,20,25,50,1001,5,10,20,25,50,100 的单位面值组成的。

母牛想知道有多少种不同的方法来用货币系统中的货币来构造一个确定的数值。

举例来说, 使用一个货币系统 (1,2,5,10, \ldots)(1,2,5,10,) 产生 1818 单位面值的一些可能的方法是:18 \times 1, 9 \times 2, 8 \times 2+2 \times 1, 3 \times 5+2+118×1,9×2,8×2+2×1,3×5+2+1,等等。

写一个程序来计算有多少种方法用给定的货币系统来构造一定数量的面值。保证总数在 6464 位带符号整数的范围内。

Solution

f[i]表示面值为i时方案数,0初始为1,因为唯一的方案就是不放钱。就是个完全背包。

 1 #include<bits/stdc++.h>
 2 #define int long long
 3 using namespace std;
 4 const int N=10010,M=30;
 5 int m,n,f[N],w[M];
 6 signed main(){
 7     scanf("%lld%lld",&m,&n),f[0]=1;
 8     for(int i=1;i<=m;i++)
 9         scanf("%lld",&w[i]);
10     for(int i=1;i<=m;i++)
11         for(int j=w[i];j<=n;j++)
12             f[j]+=f[j-w[i]];
13     printf("%lld\n",f[n]);
14     return 0;
15 }
[USACO2.3]Money System / [USACO07OCT]Cow Cash G

 K.摆花

Description

小明的花店新开张,为了吸引顾客,他想在花店的门口摆上一排花,共m盆。通过调查顾客的喜好,小明列出了顾客最喜欢的n种花,从1到n标号。为了在门口展出更多种花,规定第i种花不能超过ai盆,摆花时同一种花放在一起,且不同种类的花需按标号的从小到大的顺序依次摆列。

试编程计算,一共有多少种不同的摆花方案。

Solution 

设f[i][j]表示前i个数总和为j的方案数。

f[i][j]=$\sum\limits_{k=0}^{a_{i}}$f[i-1][j-k],其中k是枚举当前第i个数的取值。

时间复杂度:O(nm*ai)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=105,mod=1e6+7;
 4 int n,m,a[N],f[N][N];
 5 int main(){
 6     scanf("%d%d",&n,&m),f[0][0]=1;
 7     for(int i=1;i<=n;i++)
 8         scanf("%d",&a[i]);
 9     for(int i=1;i<=n;i++)
10         for(int j=0;j<=m;j++)
11             for(int k=0;k<=min(j,a[i]);k++)
12                 f[i][j]=(f[i][j]+f[i-1][j-k])%mod;
13     printf("%d\n",f[n][m]);
14     return 0; 
15 }
摆花

L.合唱队形

Description

N位同学站成一排,音乐老师要请其中的(N-K)位同学出列,使得剩下的KK位同学排成合唱队形。

合唱队形是指这样的一种队形:设K位同学从左到右依次编号为1,2,…,K,他们的身高分别为T1​,T2​,…,TK​, 则他们的身高满足T1​<...<Ti​>Ti+1​>…>TK​(1≤i≤K)。

你的任务是,已知所有N位同学的身高,计算最少需要几位同学出列,可以使得剩下的同学排成合唱队形。

Solution

按照由左而右和由右而左的顺序,将n个同学的身高排成数列。设 a 为身高序列,其中a[i]为同学i的身高;b 为由左而右身高递增的人数序列,其中 b[i]为同学1‥同学i间(包括同学i)身高满足递增顺序的最多人数。显然b[i]=max{b[j]|同学j的身高<同学i的身高}+1;c为由右而左身高递增的人数序列,其中c[i]为同学n‥同学i间(包括同学i)身高满足递增顺序的最多人数。显然c[i]=max{c[j]|同学j的身高<同学i的身高}+1;

由上述状态转移方程可知,计算合唱队形的问题具备了最优子结构性质(要使b[i]和c[i]最大,子问题的解b[j]和c[k]必须最大(1≤j≤i-1 ,i+1≤k≤n))和重迭子问题的性质(为求得b[i]和c[i],必须一一查阅子问题的解b[1]‥b[i-1]和c[i+1]‥c[n]),因此可采用动态程序设计的方法求解。 显然,合唱队的人数为max{b[i]+c[i]}-1(公式中同学i被重复计算,因此减1),n减去合唱队人数即为解。

 1 #include<bits/stdc++.h>
 2 #define int long long 
 3 using namespace std;
 4 const int N=110;
 5 int n,a[N],b[N],c[N],ans;
 6 signed main(){
 7     scanf("%lld",&n);
 8     for(int i=1;i<=n;i++)
 9         scanf("%lld",&a[i]);
10     for(int i=1;i<=n;i++){
11         b[i]=1;
12         for(int j=1;j<i;j++)
13             if((a[i]>a[j])&&(b[j]+1>b[i])) b[i]=b[j]+1;
14     }
15     for(int i=n;i>=1;i--){
16         c[i]=1;
17         for(int j=i+1;j<=n;j++)
18             if((a[j]<a[i])&&(c[j]+1>c[i])) c[i]=c[j]+1;
19     }
20     for(int i=1;i<=n;i++)
21         ans=max(ans,b[i]+c[i]);
22     printf("%lld\n",n-ans+1);
23     return 0;
24 }
合唱队形

M.求m区间内的最小值

Description

一个含有n项的数列(n<=2000000),求出每一项前的m个数到它这个区间内的最小值。若前面的数不足m项则从第1个数开始,若前面没有数则输出0。

Solution

分两种情况讨论(i为读入的数量):

  • 当i≤m时,直接输出即可,无需变动。
  • 当i>m时,我们就需要弹出。显然,如果队列里的最小值要么在区间内,要么在区间外。于是我们对这个最小值的位置进行判断。不在这个区间内就弹出。可能有连续几个最小值不在区间内,因此要进行while循环。

区间是[i-m,i),左闭右开。因此在将刚读入的值存入队列时,先输出再进队。

i=1时特判输出0。因为队列为空,无法输出。特判完了之后别忘了把第一个值入队。

代码中,i>m才进入,并且为了保险,要判断队列是否为空,接着才判断是否在这个区间内。区间左闭右开,则先输出再入队。

默认以pair的first作为排序关键字。first为元素的值,second为元素的位置。

(开O2能过……)

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 int n,m,x;
 4 priority_queue<pair<int,int>,vector<pair<int,int> >,greater<pair<int,int> > >q;
 5 int main(){
 6     scanf("%d%d",&n,&m);
 7     for(int i=1;i<=n;i++){
 8         scanf("%d",&x);
 9         if(i==1){
10             printf("0\n"),q.push(make_pair(x,i));
11             continue;
12         }
13         while(i>m&&!q.empty()&&q.top().second<i-m) q.pop();
14         printf("%d\n",q.top().first);
15         q.push(make_pair(x,i));
16     }
17     return 0;
18 }
求m区间内的最小值

N.[USACO08FEB]Eating Together S

Description

FJ的奶牛们在吃晚饭时很傻。他们把自己组织成三组(方便编号为1, 2和3),坚持一起用餐。当他们在谷仓排队进入喂食区时,麻烦就开始了。

每头奶牛都随身带着一张小卡片,小卡片上刻的是Di(1≤Di≤3)表示她属于哪一组。所有的N(1≤N≤30000)头奶牛排队吃饭,但他们并不能按卡片上的分组站好。

FJ的工作并不是那么难。他只是沿着牛的路线走下去,把旧的号码标出来,换上一个新的。通过这样做,他创造了一群奶牛,比如111222333或333222111,奶牛的就餐组按他们的晚餐卡片按升序或降序排列。

FJ就像任何人一样懒惰。他很好奇:怎样他才能进行适当的分组,使得他只要修改最少次数的数字?由于奶牛们已经很长时间没有吃到饭了,所以“哞哞”的声音到处都是,FJ只好更换卡号,而不能重新排列已经排好队的奶牛。

Solution

找一次最长不上升序列 再找一次最长不下降序列 记录下长度 用总数减去较大的那个就好了。

然后开个O2就卡过去了……

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=30010;
 4 int n,s,a[N],f[N],d[N],mx;
 5 int main(){
 6     scanf("%d",&n);
 7     for(int i=1;i<=n;i++)
 8         scanf("%d",&a[i]);
 9     for(int i=1;i<=n;i++){
10         for(int j=1;j<i;j++){ 
11             if(a[i]>=a[j]) f[i]=max(f[i],f[j]);
12             if(a[i]<=a[j]) d[i]=max(d[i],d[j]);
13         } 
14         f[i]++,mx=max(mx,f[i]);
15         d[i]++,mx=max(mx,d[i]);
16     }
17     printf("%d\n",n-mx);
18     return 0;
19 }
[USACO08FEB]Eating Together S

O.[USACO06FEB]Treats for the Cows G/S

Description

FJ has purchased N (1 <= N <= 2000) yummy treats for the cows who get money for giving vast amounts of milk. FJ sells one treat per day and wants to maximize the money he receives over a given period time.

The treats are interesting for many reasons:The treats are numbered 1..N and stored sequentially in single file in a long box that is open at both ends. On any day, FJ can retrieve one treat from either end of his stash of treats.Like fine wines and delicious cheeses, the treats improve with age and command greater prices.The treats are not uniform: some are better and have higher intrinsic value. Treat i has value v(i) (1 <= v(i) <= 1000).Cows pay more for treats that have aged longer: a cow will pay v(i)*a for a treat of age a.Given the values v(i) of each of the treats lined up in order of the index i in their box, what is the greatest value FJ can receive for them if he orders their sale optimally?

The first treat is sold on day 1 and has age a=1. Each subsequent day increases the age by 1.

Solution 待填坑

P.魔族密码

Description

风之子刚走进他的考场,就……

花花:当当当当~~偶是魅力女皇——花花!!^^(华丽出场,礼炮,鲜花)

风之子:我呕……(杀死人的眼神)快说题目!否则……-_-###

花花:……咦好冷我们现在要解决的是魔族的密码问题(自我陶醉:搞不好魔族里面还会有人用密码给我和菜虫写情书咧,哦活活,当然是给我的比较多拉*^_^*)。魔族现在使用一种新型的密码系统。每一个密码都是一个给定的仅包含小写字母的英文单词表,每个单词至少包含1个字母,至多75个字母。如果在一个由一个词或多个词组成的表中,除了最后一个以外,每个单词都被其后的一个单词所包含,即前一个单词是后一个单词的前缀,则称词表为一个词链。例如下面单词组成了一个词链:

i int integer

但下面的单词不组成词链:

integer

intern 现在你要做的就是在一个给定的单词表中取出一些词,组成最长的词链,就是包含单词数最多的词链。将它的单词数统计出来,就得到密码了。

风之子:密码就是最长词链所包括的单词数阿……

花花:活活活,还有,看你长得还不错,给你一个样例吧:

Solution

先把所有的字符串用map记录个数

然后再把每个字符串截取,用x存子串的总个数

最后与ans取最大值

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=2010;
 4 string s[N],k;
 5 int n,ans,x;
 6 map<string,int>f;
 7 int main(){
 8     scanf("%d",&n);
 9     for(int i=1;i<=n;i++)
10         cin>>s[i],f[s[i]]++;
11     for(int i=1;i<=n;i++){
12         x=0,k="";
13         for(int j=0;j<s[i].size();j++)
14             k+=s[i][j],x+=f[k];
15         ans=max(ans,x);
16     }
17     printf("%d\n",ans);
18     return 0;
19 }
魔族密码

Q.[SDOI2005]区间

Description

现给定n个闭区间[ai, bi],1<=i<=n。这些区间的并可以表示为一些不相交的闭区间的并。你的任务就是在这些表示方式中找出包含最少区间的方案。你的输出应该按照区间的升序排列。这里如果说两个区间[a, b]和[c, d]是按照升序排列的,那么我们有a<=b<c<=d。

请写一个程序:

读入这些区间;

计算满足给定条件的不相交闭区间;

把这些区间按照升序输出。

Solution 待填坑……

R.编辑距离

Description

设A和B是两个字符串。我们要用最少的字符操作次数,将字符串A转换为字符串B。这里所说的字符操作共有三种:

1、删除一个字符;

2、插入一个字符;

3、将一个字符改为另一个字符;

皆为小写字母

Solution

f[i][j]表示将A串前i个字符改为B串前j个字符需要的步数。

删:可以看做把A串最后一个字符删去后不再考虑这个字符,f[i][j]=min(f[i][j],f[i-1][j]+1);

加:可以看做与B串最后一个字符抵消后不再考虑这个字符,f[i][j]=min(f[i][j],f[i][j-1]+1);

改:可以看做删和加的集合 抵消了A、B串最后的两个字符,f[i][j]=min(f[i][j],f[i-1][j-1]+1); 当然若A、B串最后一个字符相同就可以不用操作了

 1 #include<bits/stdc++.h>
 2 #define int long long 
 3 using namespace std;
 4 const int N=4010;
 5 int n,m,f[N][N],k;
 6 char a[N],b[N];
 7 signed main(){
 8     //freopen(".in","r",stdin);
 9     //freopen(".out","w",stdout); 
10     scanf("%s%s",a+1,b+1),n=strlen(a+1),m=strlen(b+1);
11     for(int i=0;i<=n;i++) f[i][0]=i;
12     for(int i=0;i<=m;i++) f[0][i]=i;
13     for(int i=1;i<=n;i++){
14         for(int j=1;j<=m;j++){
15             k=1;
16             if(a[i]==b[j]) k=0;
17             f[i][j]=min(min(f[i-1][j]+1,f[i][j-1]+1),f[i-1][j-1]+k);
18         }
19     }
20     printf("%lld\n",f[n][m]);
21     return 0;
22 }
编辑距离

 (此处省略4题

W.方格取数

Description

设有 N×N 的方格图 (N9),我们将其中的某些方格中填入正整数,而其他的方格中则放入数字 00。如下图所示(见样例):

A
 0  0  0  0  0  0  0  0
 0  0 13  0  0  6  0  0
 0  0  0  0  7  0  0  0
 0  0  0 14  0  0  0  0
 0 21  0  0  0  4  0  0
 0  0 15  0  0  0  0  0
 0 14  0  0  0  0  0  0
 0  0  0  0  0  0  0  0
                         B

某人从图的左上角的 A 点出发,可以向下行走,也可以向右走,直到到达右下角的 B 点。在走过的路上,他可以取走方格中的数(取走后的方格中将变为数字 0)。
此人从 A 点到 B 点共走两次,试找出 2 条这样的路径,使得取得的数之和为最大。

Solution

一个四重循环枚举两条路分别走到的位置。

由于每个点均从上或左继承而来,故内部有四个if,分别表示两个点从上上、上左、左上、左左继承来时,加上当前两个点所取得的最大值。

a[i][j]表示(i,j)格上的值,sum[i][j][h][k]表示第一条路走到(i,j),第二条路走到(h,k)时的最优解。

例如,sum[i][j][h][k] = max{sum[i][j][h][k], sum[i-1][j][h-1][k] + a[i][j] + a[h][k]},表示两点均从上面位置走来。

当(i,j) <> (h,k))时 sum[i][j][h][k]:=max{sum[i-1][j][h-1][k],sum[i][j-1][h][k-1],sum[i-1][j][h][k-1],sum[i][j-1][h-1][k]}+a[i][j]+a[h][k];

当(i,j) = (h,k)时 sum[i][j][h][k]:=max{sum[i-1][j][h-1][k],sum[i][j-1][h][k-1],sum[i-1][j][h][k-1],sum[i][j-1][h-1][k]}+a[i][j];

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=55;
 4 int a[N][N],sum[N][N][N][N],n,x,y,z,h,k,tmp1,tmp2;
 5 int main(){
 6     scanf("%d%d%d%d",&n,&x,&y,&z);
 7     while(x&&y&&z) a[x][y]=z,scanf("%d%d%d",&x,&y,&z);
 8     for(int i=1;i<=n;i++)
 9         for(int j=1;j<=n;j++)
10             for(int h=1;h<=n;h++)
11                 for(int k=1;k<=n;k++){
12                     tmp1=max(sum[i-1][j][h-1][k],sum[i][j-1][h][k-1]);
13                     tmp2=max(sum[i-1][j][h][k-1],sum[i][j-1][h-1][k]);
14                     sum[i][j][h][k]=max(tmp1,tmp2)+a[i][j];
15                     if(i!=h&&j!=k) sum[i][j][h][k]+=a[h][k];
16                 }
17     printf("%d\n",sum[n][n][n][n]);
18     return 0;
19 }
方格取数

X.传纸条

Description

小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。一次素质拓展活动中,班上同学安排做成一个 m 行 n 列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。幸运的是,他们可以通过传纸条来进行交流。纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标 (1,1),小轩坐在矩阵的右下角,坐标 (m,n)。从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。

在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙。反之亦然。

还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用 0 表示),可以用一个 [0,100] 内的自然数来表示,数越大表示越好心。小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。现在,请你帮助小渊和小轩找到这样的两条路径。

Solution

题目的本质是寻找两条不相交路径,使经过的位置权值和最大。我们可以让两条路径同时从起点出发,并记录当前走到哪里。

因为只能向右下走,所以,我们可以用f[i][j[k]来记录状态。其中i为当前两条路径已经走到第几条由右上到左下的对角线,j和k表示两条路径目前处于对角线的什么位置。

可以发现,不管向右还是向上,都会移动到下一条对角线中。

所以,我们同时枚举两条路径是向右还是向下走,并加上对应格子的权值进行转移即可。

代码中,l是枚举对角线,y是枚举第一条,i枚举的是第二条。

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=55;
 4 int n,m,v[N][N],f[110][N][N],x,j;
 5 int main(){
 6     scanf("%d%d",&m,&n);
 7     for(int i=1;i<=m;i++)
 8         for(int j=1;j<=n;j++)
 9             scanf("%d",&v[i][j]);
10     for(int l=2;l<=n+m;l++)
11         for(int y=1;y<=m&&y<l;y++)
12             for(int i=1;i<=m&&i<l;i++){
13                 x=l-y,j=l-i;
14                 f[l][i][y]=max(max(f[l-1][i][y],f[l-1][i-1][y]),max(f[l-1][i][y-1],f[l-1][i-1][y-1]));
15                 f[l][i][y]+=v[i][j];
16                 if(!(i==y&&j==x)) f[l][i][y]+=v[y][x]; 
17             } 
18     printf("%d\n",f[m+n][m][m]);
19     return 0;
20 }
传纸条

 (此处省略4题

].金明的预算方案

右转→https://www.cnblogs.com/maoyiting/p/12526948.html

这里放个代码

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=70;
 4 int m,n,v[N],p[N],q[N],x,num[N],s[32000],f[N][5][3],ff[N];
 5 int main(){
 6     scanf("%d%d",&m,&n);
 7     for(int i=1;i<=n;i++){
 8         scanf("%d%d%d",&v[i],&p[i],&q[i]);
 9         if(!q[i]){
10             num[i]++;
11             f[i][1][1]=v[i]*p[i];
12             f[i][1][2]=v[i];
13         }
14         else{
15             num[q[i]]++;
16             f[q[i]][num[q[i]]][1]=v[i]*p[i];
17             f[q[i]][num[q[i]]][2]=v[i];
18         }
19     }
20     for(int i=1;i<=n;i++){
21         if(num[i]==1) ff[i]=1;
22         if(num[i]==2) ff[i]=2;
23         if(num[i]>=2) f[i][2][1]+=f[i][1][1];
24         f[i][2][2]+=f[i][1][2];
25         if(num[i]==3){
26             ff[i]=4;
27             f[i][3][1]+=f[i][1][1],f[i][3][2]+=f[i][1][2];
28             f[i][4][1]=f[i][2][1]+f[i][3][1]-f[i][1][1];
29             f[i][4][2]=f[i][2][2]+f[i][3][2]-f[i][1][2];
30         }
31     }
32     for(int i=1;i<=n;i++) if(num[i]>0)
33         for(int j=m;j>=0;j--)
34             for(int k=1;k<=ff[i];k++)
35                 if(f[i][k][2]<=j) s[j]=max(s[j],s[j-f[i][k][2]]+f[i][k][1]);
36     printf("%d\n",s[m]);
37     return 0;
38 }
金明的预算方案

(此处省略2题没办法博主太蒟了

`.没有上司的舞会

Description

某大学有 n 
个职员,编号为 1...n

他们之间有从属关系,也就是说他们的关系就像一棵以校长为根的树,父结点就是子结点的直接上司。

现在有个周年庆宴会,宴会每邀请来一个职员都会增加一定的快乐指数 rir_iri

,但是呢,如果某个职员的直接上司来参加舞会了,那么这个职员就无论如何也不肯来参加舞会了。

所以,请你编程计算,邀请哪些职员可以使快乐指数最大,求最大的快乐指数。

Solution

我们对于每一个点i,用f[i][0]来表示不取ii时的最大值,f[i][1]表示取ii时的最大值。

对于每一个点i,则有状态转移方程:

f[i][1]=h[i]+f[j][0]

f[i][0]=Max(f[j][1],f[j][0])

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=100000+5;
 4 int f[N][2],a[N],hd[N],vis[N],cnt;
 5 struct edge{
 6     int nxt,to;
 7 }e[N];
 8 void add(int x,int y){
 9     e[++cnt]=(edge){hd[x],y},hd[x]=cnt;
10 } 
11 void dfs(int x,int fa){
12     f[x][0]=0,f[x][1]=a[x];
13     for(int i=hd[x];i;i=e[i].nxt){
14         int y=e[i].to;
15         if(y!=fa){
16             dfs(y,x);
17             f[x][0]+=max(f[y][0],f[y][1]);
18             f[x][1]+=f[y][0];
19         }
20         
21     }
22 }
23 int main()
24 {
25     int n,root;
26     scanf("%d",&n);
27     for(int i=1;i<=n;i++) cin>>a[i];
28     for(int i=1;i<=n-1;i++){
29         int x,y;
30         scanf("%d%d",&x,&y);
31         add(y,x),add(x,y);
32         vis[x]=1;
33     }
34     for(int i=1;i<=n;i++)
35         if(vis[i]==0){root=i;break;}
36     dfs(root,0);
37     cout<<max(f[root][0],f[root][1])<<endl;
38     return 0;
39 }
没有上司的舞会

(此处省略2题

c.乌龟棋

我写的不好,还是康这个吧:https://www.luogu.com.cn/blog/loi-syc/p1541-wu-jun-qi

(跳过1题

e.[NOI1995]石子合并

Description

在一个圆形操场的四周摆放 NN 堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。

试设计出一个算法,计算出将 NN 堆石子合并成 11 堆的最小得分和最大得分。

Solution

康这篇吧→https://www.luogu.com.cn/blog/100036/luogu-p1880-noi-1995-dan-zi-ge-bing-ou-jian-dp

这个代码有点问题,那就在这里放一个奇怪的代码吧

 1 #include<bits/stdc++.h>
 2 using namespace std;
 3 const int N=1010,M=0x3fffffff;
 4 int b[N][N],c[N][N],s[N],m1=M,m2=-M,n,t;
 5 int main(){
 6     scanf("%d",&n); 
 7     for(int i=1;i<=n;i++)
 8         scanf("%d",&s[i]),s[i+n]=s[i];
 9     for(int i=1;i<n*2;i++)
10         s[i]=s[i-1]+s[i];
11     for(int i=2;i<=n;i++)
12         for(int j=1;j+i-1<n*2;j++){
13             t=i+j-1;
14             b[j][t]=M,c[j][t]=-M;
15             for(int k=j;k<t;k++){
16                 b[j][t]=min(b[j][t],b[j][k]+b[k+1][t]+s[t]-s[j-1]);
17                 c[j][t]=max(c[j][t],c[j][k]+c[k+1][t]+s[t]-s[j-1]);
18             } 
19         }
20     for(int i=1;i<=n;i++)
21         m1=min(m1,b[i][i+n-1]),m2=max(m2,c[i][i+n-1]);
22     printf("%d\n%d\n",m1,m2);
23     return 0;
24 }
石子合并

 (此处省略2题杜老师天下第一

 [NOI1995]石子合并

猜你喜欢

转载自www.cnblogs.com/mao1t/p/dp.html