线性DP
线性DP
最长上升子序列
输入一个长度为n的数组,求该数组的最长上升子序列(模板)复杂度\(O(n^2)\)
#include<bits/stdc++.h>
using namespace std;
#define N 100005
int n,m,a[N],f[N];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=n;++i)
for(int j=0;j<i;++j)
if(a[j]<a[i])
f[i]=max(f[i],f[j]+1);
int ans=0;
for(int i=1;i<=n;++i)
ans=max(ans,f[i]);
printf("%d",ans);
}
线段树或者树状数组优化可以到\(O(nlogn)\)的复杂度。
给出树状数组优化的模板代码。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+5;
int n,a[N],s[N],c[N],dp[N];
inline void add(int x,int y)
{
for(;x<=s[0];x+=x&-x) c[x]=max(c[x],y);
}
inline int ask(int x)
{
int res=0;
for(;x;x-=x&-x) res=max(res,c[x]);
return res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]),s[i]=a[i];
sort(s+1,s+n+1);
s[0]=unique(s+1,s+n+1)-s-1;
for(int i=1;i<=n;++i) a[i]=lower_bound(s+1,s+s[0]+1,a[i])-s;
for(int i=1;i<=n;++i) dp[i]=ask(a[i]-1)+1,add(a[i],dp[i]);
printf("%d",ask(s[0]));
}
分级
给定长度为N的序列A,构造一个长度为N的序列B,满足:
1、B非严格单调,即B1≤B2≤…≤BN或B1≥B2≥…≥BN。
2、最小化 S=∑Ni=1|Ai-Bi|。
只需要求出这个最小值S。
第一行包含一个整数N。
接下来N行,每行包含一个整数Ai
#include<bits/stdc++.h>
using namespace std;
#define maxn 2005
int n,t,a[maxn],b[maxn];
long long val,ans=0x7ffffffffff,f[maxn][maxn];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d",&a[i]);
b[i]=a[i];
}
sort(b+1,b+n+1);
t=unique(b+1,b+n+1)-b-1;
for(int i=1;i<=n;++i)
{
val=f[i-1][1];
for(int j=1;j<=t;++j)
{
val=f[i-1][j]<val?f[i-1][j]:val;
f[i][j]=val+abs(a[i]-b[j]);
}
}
for(int i=1;i<=t;i++) ans=f[n][i]<ans?f[n][i]:ans;
memset(f,0,sizeof(f));
for(int i=1;i<=n;++i)
{
val=f[i-1][t];
for(int j=t;j;--j)
{
val=f[i-1][j]<val?f[i-1][j]:val;
f[i][j]=val+abs(a[i]-b[j]);
}
}
for(int i=1;i<=t;i++) ans=f[n][i]<ans?f[n][i]:ans;
printf("%lld",ans);
}
低买
给定一段时间内股票的每日售价(正16位整数)。
你可以选择在任何一天购买股票。
每次你选择购买时,当前的股票价格必须严格低于你之前购买股票时的价格。
编写一个程序,确定你应该在哪些天购进股票,可以使得你能够购买股票的次数最大化。
例如,下面是一个股票价格时间表:
Day 1 2 3 4 5 6 7 8 9 10 11 12
Price 68 69 54 64 68 64 70 67 78 62 98 87
如果每次购买都必须遵循当前股票价格严格低于之前购买股票时的价格,那么投资者最多可以购买四次该股票。
买进方案之一为:
Day 2 5 6 10
Price 69 68 64 62
输入格式
第1行包含整数 N,表示给出的股票价格的天数。
第2至最后一行,共包含 N 个整数,每行10个,最后一行可能不够10个,表示 N 天的股票价格。
同一行数之间用空格隔开。
输出格式
输出占一行,包含两个整数,分别表示最大买进股票次数以及可以达到最大买进次数的方案数。
如果两种方案的买入日序列不同,但是价格序列相同,则认为这是相同的方案(只计算一次)。
数据范围
1≤N≤5000
输入样例1:
12
68 69 54 64 68 64 70 67 78 62
98 87
输出样例1:
输出样例1:
4 2
输入样例2:
输入样例2:
5
4 3 2 1 1
输出样例2:
输出样例2:
4 1
#include<bits/stdc++.h>
using namespace std;
int n,ans,tot,f[5005],a[5005],num[5005];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=n;++i)
{
f[i]=1;
for(int j=1;j<i;++j)
if(a[j]>a[i])
f[i]=max(f[i],f[j]+1);
ans=max(ans,f[i]);
}
for(int i=1;i<=n;i++)
{
if(f[i]==1) num[i]=1;
for(int j=1;j<i;j++)
{
if(a[j]>a[i]&&f[i]==f[j]+1)num[i]+=num[j];
else if(a[i]==a[j]&&f[i]==f[j])num[i]=0;
}
if(f[i]==ans) tot+=num[i];
}
printf("%d %d",ans,tot);
}
最长公共子序列
输入字符串a,b,求两个字符串的最长公共子序列(模板)
#include<bits/stdc++.h>
using namespace std;
char a[1010],b[1010];
int c[1010][1010];
int main(){
gets(a);
gets(b);
int la=strlen(a),lb=strlen(b);
for(int i=1;i<=la;i++){
for(int j=1;j<=lb;j++){
c[i][j]=max(c[i-1][j],c[i][j-1]);
if(a[i-1]==b[j-1]) c[i][j]=max(c[i][j],c[i-1][j-1]+1);}}
cout<<c[la][lb];
}
最长公共上升子序列
熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。
小沐沐先让奶牛研究了最长上升子序列,再让他们研究了
最长公共子序列,现在又让他们研究最长公共上升子序列了。
小沐沐说,对于两个数列A和B,如果它们都包含一段位置不
一定连续的数,且数值是严格递增的,那么称这一段数是两
个数列的公共上升子序列,而所有的公共上升子序列中最长
的就是最长公共上升子序列了。
奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。
不过,只要告诉奶牛它的长度就可以了。
数列A和B的长度均不超过3000。
输入格式
第一行包含一个整数N,表示数列A,B的长度。
第二行包含N个整数,表示数列A。
第三行包含N个整数,表示数列B。
输出格式
输出一个整数,表示最长公共子序列的长度。
数据范围
1≤N≤3000,序列中的数字均不超过2^31
#include<bits/stdc++.h>
using namespace std;
#define maxn 3005
int n,val,a[maxn],b[maxn],f[maxn];
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1;i<=n;++i) scanf("%d",&b[i]);
for(int i=1;i<=n;i++)
{
val=0;
for(int j=1;j<=n;j++)
{
if(f[j]>val&&b[j]<a[i]) val=f[j];
if(a[i]==b[j]) f[j]=val+1;
}
}
int ans=0;
for(int i=1;i<=n;i++) ans=f[i]>ans?f[i]:ans;
printf("%d",ans);
}
旅行
爱丽丝和鲍勃想去旅行。
他们每个人制定了一条旅行路线,每条路线包含一个
按给定顺序访问的城市列表,一个城市可能会多次出
现在同一路线中。
因为他们想要一起去旅行,所以必须在旅行路线上达
成一致。
他们两个都不想改变他们的路线上的城市顺序或者在
路线上额外添加城市。
因此,他们只能移除各自路线中的一些城市,使得旅
行路线达成一致,并且尽可能的长。
该地区共有26个城市,用小写字母’a’到’z’表示。
输入格式
输入包含两行,第一行是爱丽丝的路线城市列表,第
二行是鲍勃的路线城市列表。
每个列表由1到80个小写字母组成,其间没有空格。
输出格式
按升序顺序输出所有满足条件的路线列表。
每个路线列表占一行。
输入样例:
abcabcaa
acbacba
输出样例:
输出样例:
ababa
abaca
abcba
acaba
acaca
acbaa
acbca
先找出最大公共子序列的长度,再通过预处理在i位置a~z字母最末的位置向前查找字母
#include<bits/stdc++.h>
using namespace std;
char a[85],b[85];
int f[85][85],la[85][28],lb[85][28];
priority_queue<string>q;
void print(int i,int j,int tot,string s)
{
if(!tot)
{
q.push(s);
return;
}
if(!i||!j) return;
for(int k=0;k<26;++k)
{
int x=la[i][k],y=lb[j][k];
if(f[x][y]==tot)
print(x-1,y-1,tot-1,char('a'+k)+s);
}
}
void pri(int x)
{
if(!x) return;
string ans=q.top();
q.pop();
pri(x-1);
cout<<ans<<endl;
}
int main()
{
scanf("%s%s",a+1,b+1);
int lla=strlen(a+1),llb=strlen(b+1);
for(int i=1;i<=lla;++i)
for(int j=1;j<=llb;++j)
{
f[i][j]=max(f[i-1][j],f[i][j-1]);
if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
}
for(int i=1;i<=lla;++i)
for(int j=0;j<26;++j)
if(a[i]=='a'+j) la[i][j]=i;
else la[i][j]=la[i-1][j];
for(int i=1;i<=llb;++i)
for(int j=0;j<26;++j)
if(b[i]=='a'+j) lb[i][j]=i;
else lb[i][j]=lb[i-1][j];
print(lla,llb,f[lla][llb],"");
pri(q.size());
}
择优累加
无固定模板,每次 \(F[i][j]\)从前驱中择优转移
杨老师的照相排列
有 N 个学生合影,站成左端对齐的 k 排,每排分别有 N1,N2,…,Nk 个人。 (N1≥N2≥…≥Nk)
第1排站在最后边,第 k 排站在最前边。
学生的身高互不相同,把他们从高到底依次标记为 1,2,…,N。
在合影时要求每一排从左到右身高递减,每一列从后到前身高也递减。
问一共有多少种安排合影位置的方案?
下面的一排三角矩阵给出了当 N=6,k=3,N1=3,N2=2,N3=1 时的全部16种合影方案。注意身高最高的是1,最低的是6。
123 123 124 124 125 125 126 126 134 134 135 135 136 136 145 146
45 46 35 36 34 36 34 35 25 26 24 26 24 25 26 25
6 5 6 5 6 4 5 4 6 5 6 4 5 4 3 3
输入格式
输入包含多组测试数据。
每组数据两行,第一行包含一个整数k表示总排数。
第二行包含k个整数,表示从后向前每排的具体人数。
当输入k=0的数据时,表示输入终止,且该数据无需处理。
输出格式
每组测试数据输出一个答案,表示不同安排的数量。
每个答案占一行。
数据范围
1≤k≤5,学生总人数不超过30人。
输入样例:
1
30
5
1 1 1 1 1
3
3 2 1
4
5 3 3 1
5
6 5 4 3 2
2
15 15
0
输出样例:
1
1
16
4158
141892608
9694845
1.将学生按身高从高到矮排序,保证每次加入的人都比前面的矮
2.枚举每排的人数
即\(f[a][b][c][d][e]\)可由\(f[a-1][b][c][d][e]\),\(f[a][b-1][c][d][e]\)\(f[a][b][c-1][d][e]\),\(f[a][b][c][d-1][e]\),\(f[a][b][c][d][e-1]\)
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
const int N = 31;
int n;
LL f[N][N][N][N][N];
int main()
{
while (cin >> n, n)
{
int s[5] = {0};
for (int i = 0; i < n; i ++ ) cin >> s[i];
memset(f, 0, sizeof f);
f[0][0][0][0][0] = 1;
for (int a = 0; a <= s[0]; a ++ )
for (int b = 0; b <= min(a, s[1]); b ++ )
for (int c = 0; c <= min(b, s[2]); c ++ )
for (int d = 0; d <= min(c, s[3]); d ++ )
for (int e = 0; e <= min(d, s[4]); e ++ )
{
LL &x = f[a][b][c][d][e];
if (a && a - 1 >= b) x += f[a - 1][b][c][d][e];
if (b && b - 1 >= c) x += f[a][b - 1][c][d][e];
if (c && c - 1 >= d) x += f[a][b][c - 1][d][e];
if (d && d - 1 >= e) x += f[a][b][c][d - 1][e];
if (e) x += f[a][b][c][d][e - 1];
}
cout << f[s[0]][s[1]][s[2]][s[3]][s[4]] << endl;
}
return 0;
}
移动服务
一个公司有三个移动服务员,最初分别在位置1,2,3处。
如果某个位置(用一个整数表示)有一个请求,那么公司
必须指派某名员工赶到那个地方去。
某一时刻只有一个员工能移动,且不允许在同样的位置出
现两个员工。
从 p 到 q 移动一个员工,需要花费 c(p,q)。
这个函数不一定对称,但保证 c(p,p)=0。
给出N个请求,请求发生的位置分别为 p1~pN。
公司必须按顺序依次满足所有请求,且过程中不能去其他
额外的位置,目标是最小化公司花费,请你帮忙计算这个最小花费。
输入格式
第1行有两个整数L,N,其中L是位置数量,N是请求数量,每
个位置从1到L编号。
第2至L+1行每行包含L个非负整数,第i+1行的第j个数表示
c(i,j) ,并且它小于2000。
最后一行包含N个整数,是请求列表。
一开始三个服务员分别在位置1,2,3。
输出格式
输出一个整数M,表示最小花费。
数据范围
3≤L≤200,
1≤N≤1000
输入样例:
5 9
0 1 1 1 1
1 0 2 3 2
1 1 0 4 1
2 1 5 0 1
4 2 3 4 0
4 2 4 1 5 4 3 2 1
输出样例:
5
状态表示:
\(f[i][x][y]\)表示已经处理完前i个请求,且三个服务员分别在p[i], x, y的所有方案的集合;
\(f[i][x][y]\)的值是集合中所有方案的花费的最小值;
状态计算:
这里状态之间的拓扑关系比较特殊,f[i][x][y]所依赖的状态枚举起来不太方便,但f[i][x][y]被依赖的很容易枚举,只有3类:
位于p[i]的服务员出发前往p[i + 1],此时状态变成\(f[i + 1][x][y] = f[i][x][y] + w[p[i]][p[i + 1]]\);
位于x的服务员出发前往p[i + 1],此时状态变成\(f[i + 1][p[i]][y] = f[i][x][y] + w[x][p[i + 1]]\);
位于y的服务员出发前往p[i + 1],此时状态变成\(f[i + 1][x][p[i]] = f[i][x][y] + w[y][p[i + 1]]\);
最终答案从f[m][1...n][1...n]取最小值即可。
#include<bits/stdc++.h>
using namespace std;
int l,n,a[1005],c[205][205];
long long f[2][205][205];
int main()
{
scanf("%d%d",&l,&n);
for(int i=1;i<=l;++i)
for(int j=1;j<=l;++j)
scanf("%d",&c[i][j]);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
memset(f,0x3f,sizeof(f));
f[0][1][2]=0;
a[0]=3;
for(int k=0;k<n;++k)
{
for(int i=1;i<=l;++i)
for(int j=1;j<=l;++j)
{
int x=a[k];
long long v=f[k%2][i][j];
if(x==i||x==j||i==j) continue;
//所处位置不合法
int u=a[k+1];
f[(k+1)%2][i][j]=min(f[(k+1)%2][i][j],v+c[x][u]);
f[(k+1)%2][x][j]=min(f[(k+1)%2][x][j],v+c[i][u]);
f[(k+1)%2][i][x]=min(f[(k+1)%2][i][x],v+c[j][u]);
}
for(int i=1;i<=l;++i)
for(int j=1;j<=l;++j)
f[k%2][i][j]=f[0][0][0];
}
long long ans=f[0][0][0];
int x=a[n];
for(int i=1;i<=l;++i)
for(int j=1;j<=l;++j)
{
if(i==j||x==i||x==j) continue;
ans=min(ans,f[n%2][i][j]);
}
printf("%lld",ans);
}
传纸条
小渊和小轩是好朋友也是同班同学,他们在一起总有谈不完的话题。
一次素质拓展活动中,班上同学安排坐成一个 mm 行 nn 列的矩阵,而小渊和小轩被安排在矩阵对角线的两端,因此,他们就无法直接交谈了。
幸运的是,他们可以通过传纸条来进行交流。
纸条要经由许多同学传到对方手里,小渊坐在矩阵的左上角,坐标(1,1),小轩坐在矩阵的右下角,坐标(m,n)。
从小渊传到小轩的纸条只可以向下或者向右传递,从小轩传给小渊的纸条只可以向上或者向左传递。
在活动进行中,小渊希望给小轩传递一张纸条,同时希望小轩给他回复。
班里每个同学都可以帮他们传递,但只会帮他们一次,也就是说如果此人在小渊递给小轩纸条的时候帮忙,那么在小轩递给小渊的时候就不会再帮忙,反之亦然。
还有一件事情需要注意,全班每个同学愿意帮忙的好感度有高有低(注意:小渊和小轩的好心程度没有定义,输入时用0表示),可以用一个0-100的自然数来表示,数越大表示越好心。
小渊和小轩希望尽可能找好心程度高的同学来帮忙传纸条,即找到来回两条传递路径,使得这两条路径上同学的好心程度之和最大。
现在,请你帮助小渊和小轩找到这样的两条路径。
输入格式
第一行有2个用空格隔开的整数 m和 n,表示学生矩阵有 m 行 n 列。
接下来的 mm行是一个 m∗n 的矩阵,矩阵中第 i 行 j 列的整数表示坐在第 i行 j 列的学生的好心程度,每行的 n个整数之间用空格隔开。
输出格式
输出一个整数,表示来回两条路上参与传递纸条的学生的好心程度之和的最大值。
数据范围
1≤n,m≤50
输入样例:
3 3
0 3 9
2 8 5
5 7 0
输出样例:
34
从左上到右下还是从右下到左上不影响结果
枚举两个人的位置
#include<bits/stdc++.h>
using namespace std;
int n,m;
int v[55][55],f[55][55][55][55];
// (i,j) 是第一条路径坐标 (k,l) 是第二条
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++) scanf("%d",&v[i][j]);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
for(int k=1;k<=n;k++)
for(int l=j+1;l<=m;l++)
{
int w1=f[i-1][j][k][l-1],w2=f[i-1][j][k-1][l];
int w3=f[i][j-1][k][l-1],w4=f[i][j-1][k-1][l];
f[i][j][k][l]=max(w1,max(w2,max(w3,w4)))+v[i][j]+v[k][l];
//我们用贪心思想可得两条路径肯定不相交 (能取数就取数)
//那么令 l=(j+1,m) 即满足了此条件 (保证了第二条路线一定在第一条路线下面)
//不用判重因为不会重两条路径不相交
}
printf("%d",f[n][m-1][n-1][m]);
//dp 是达不到 (n,m) 的,但 (n,m) 等价于 (n-1,m),(n,m-1) (因为 v(n,m)==0)
return 0;
}
I-区域
在 N*M 的矩阵中,每个格子有一个权值,要求寻找一个包含 K 个格子的凸连通块(连通块中间没有空缺,并且轮廓是凸的),使这个连通块中的格子的权值和最大。
注意:凸连通块是指:连续的若干行,每行的左端点列号先递减、后递增,右端点列号先递增、后递减。
求出这个最大的权值和,并给出连通块的具体方案,输出任意一种方案即可。
输入格式
第一行包含三个整数N,M和K。
接下来N行每行M个整数,表示N*M的矩阵上每个格子的权值(均不超过1000)。
输出格式
第一行输出“Oil : X”,其中X为最大权值和。
接下来K行每行两个整数xi和yi,用来描述所有格子的具体位置,每个格子位于第xi行,第yi列。
数据范围
1≤N,M≤15
0≤K≤N∗M
输入样例:
2 3 4
10 20 30
40 2 3
输出样例:
Oil : 100
1 1
1 2
1 3
2 1
考虑到一个凸多边形一定是连续的多行组成。可以考虑每行选哪几个格子。
设 0为单调伸长, 1 为单调伸短。
设 \(f[i][j][l][r][x (0/1)][y (0/1)]\)$为第 i 行,已经选出j个格子,第ii行选择了[l,r][l,r] 区间的最大值。左右端点x,y分别为 单调伸长 / 单调伸短 的最大权值。
状态转移:
若 x=0,y=0则 \(f[i][j][l][r][x][y]=max(f[i−1][j−(r−l+1)][l′][r′][0][0])+cost(i,l,r)\) ,其中l<=l′<=r′<=r,j>=r−l+1。
若 x=0,y=1则$ f[i][j][l][r][x][y]=max(f[i−1][j−(r−l+1)][l′][r′][0][0/1])+cost(i,l,r)$,其中l<=l′<=r<=r′,j>=r−l+1l<=l′<=r<=r′,j>=r−l+1。
若 x=1,y=0 则$ f[i][j][l][r][x][y]=max(f[i−1][j−(r−l+1)][l′][r′][0/1][0])+cost(i,l,r)$,其中l′<=l<=r′<=r,j>=r−l+1。
若 x=1,y=1则 \(f[i][j][l][r][x][y]=max(f[i−1][j−(r−l+1)][l′][r′][0/1][0/1])+cost(i,l,r)\),其中l′<=l<=r<=r′,j>=r−l+1。
初始状态: \(f[i][0][l][r][0][0]=0(0<=i<=n,1<=l<=r<=m)\),其余为负无穷。
目标:\(maxf[i][k][l][r][0/1][0/1](1<=i<=n)\)
输出方案只需通过状态转移延展即可。cost(i,l,r)cost(i,l,r) 用前缀和计算
#include<bits/stdc++.h>
using namespace std;
#define N 16
#define G 16*16
int n,m,k,ans,c[N][N],f[N][G][N][N][2][2];
//1:减少 0:增多 行 格 左 右 状态
//1右0左
struct op
{
int i,j,l,r,x,y;
}pre[N][G][N][N][2][2],an;
void print(op x)
{
if(!x.j) return;
print(pre[x.i][x.j][x.l][x.r][x.x][x.y]);
for(int i=x.l;i<=x.r;++i) printf("%d %d\n",x.i,i);
return;
}
int main()
{
scanf("%d %d %d",&n,&m,&k);
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
scanf("%d",&c[i][j]),c[i][j]+=c[i][j-1];
for(int i=1;i<=n;i++)
for(int j=1;j<=k;j++)
for(int l=1;l<=m;l++)
for(int r=1;r<=m;r++)
{
if(j<r-l+1) continue;
int gz=r-l+1,cost=c[i][r]-c[i][l-1];
//1 0
for(int ll=l;ll<=r;++ll)
for(int rr=ll;rr<=r;++rr)
{
int &v=f[i][j][l][r][1][0],val=f[i-1][j-gz][ll][rr][1][0]+cost;
if(val>v)
{
v=val;
pre[i][j][l][r][1][0]=(op){i-1,j-gz,ll,rr,1,0};
}
}
//1 1(10,11)
for(int ll=l;ll<=r;++ll)
for(int rr=r;rr<=m;++rr)
for(int y=0;y<=1;++y)
{
int &v=f[i][j][l][r][1][1],val=f[i-1][j-gz][ll][rr][1][y]+cost;
if(val>v)
{
v=val;
pre[i][j][l][r][1][1]=(op){i-1,j-gz,ll,rr,1,y};
}
}
//0 0
for(int ll=1;ll<=l;++ll)
for(int rr=l;rr<=r;++rr)
for(int x=0;x<=1;++x)
{
int &v=f[i][j][l][r][0][0],val=f[i-1][j-gz][ll][rr][x][0]+cost;
if(val>v)
{
v=val;
pre[i][j][l][r][0][0]=(op){i-1,j-gz,ll,rr,x,0};
}
}
//0 1
for(int ll=1;ll<=l;++ll)
for(int rr=r;rr<=m;++rr)
for(int x=0;x<=1;++x)
for(int y=0;y<=1;++y)
{
int &v=f[i][j][l][r][0][1],val=f[i-1][j-gz][ll][rr][x][y]+cost;
if(val>v)
{
v=val;
pre[i][j][l][r][0][1]=(op){i-1,j-gz,ll,rr,x,y};
}
}
if(j==k)
{
for(int x=0;x<=1;++x)
for(int y=0;y<=1;++y)
{
if(f[i][j][l][r][x][y]>ans)
{
ans=f[i][j][l][r][x][y];
an=(op){i,j,l,r,x,y};
}
}
}
}
printf("Oil : %d\n",ans);
print(an);
}
饼干
圣诞老人共有M个饼干,准备全部分给N个孩子。
每个孩子有一个贪婪度,第 i 个孩子的贪婪度为 g[i]。
如果有 a[i] 个孩子拿到的饼干数比第 i 个孩子多,那么第 i 个孩子会产生 g[i]*a[i]的怨气。
给定N、M和序列g,圣诞老人请你帮他安排一种分配方式,使得每个孩子至少分到一块饼干,并且所有孩子的怨气总和最小。
输入格式
第一行包含两个整数N,M。
第二行包含N个整数表示g1g1~gNgN。
输出格式
第一行一个整数表示最小怨气总和。
第二行N个空格隔开的整数表示每个孩子分到的饼干数,若有多种方案,输出任意一种均可。
数据范围
1≤N≤30
N≤M≤5000
1≤gi≤107
输入样例:
3 20
1 2 3
输出样例:
2
2 9 9
按怒气从大到小排序
对于第i个孩子:
1.如果获得的饼干数j大于1,则等价于在获得1个饼干,从1~i的孩子每个人再获得j-1个饼干,怒气总和不变
2.如果获得饼干数为1
枚举到前面第k个人,k~i都获得了一个饼干
\(f[i][j]\)=min{\(f[i][j-1]\),min0<=k<i{\(f[k][j-(i-k)]\)+k*∑k+1<=p<=i\(g[p]\)}
#include<bits/stdc++.h>
using namespace std;
int n,m,k,q,ans,an[35];
struct opp
{
int val,pi,pj;
}f[35][5050];
struct op
{
int g,i;
}a[35];
bool cmp(op x,op y)
{
return x.g>y.g;
}
void print(int i,int j,int h)
{
if(!i) return;
print(f[i][j].pi,f[i][j].pj,h+1);
for(int k=f[i][j].pi+1;k<=i;++k) an[a[i].i]=h;
}
int main()
{
scanf("%d %d",&n,&m);
for(int i=1;i<=n;i++) a[i].i=i,scanf("%d",&a[i].g);
sort(a+1,a+n+1,cmp);
for(int i=1;i<=n;i++) a[i].g+=a[i-1].g;
q=m%n+n;
ans=m/n-1;
memset(f,0x3f,sizeof(f));
for(int i=1;i<=n;++i) f[i][0]=(opp){0,0,0};
for(int i=1;i<=n;++i)
for(int j=i;j<=q;++j)
{
f[i][j]=f[i][j-i];
for(int k=1;k<i;++k)
{
int val=f[k][j-(i-k)].val+k*(a[i].g-a[k].g);
if(val<f[i][j].val) f[i][j]=(opp){val,k,j-(i-k)};
}
// cout<<i<<" "<<j<<" "<<f[i][j].val<<" "<<f[i][j].pi<<" "<<f[i][j].pj<<endl;
}
printf("%d\n",f[n][q]);
print(n,q,1);
for(int i=1;i<=n;i++) printf("%d ",an[i]+ans);
}
乌龟棋
小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。
乌龟棋的棋盘只有一行,该行有 N 个格子,每个格子上一个分数(非负整数)。
棋盘第 1 格是唯一的起点,第 N 格是终点,游戏要求玩家控制一个乌龟棋子
从起点出发走到终点。
乌龟棋中共有 M 张爬行卡片,分成 4 种不同的类型(M 张卡片中不一定包含所有 4 种类型的卡片),每种类型的卡片上分别标有 1、2、3、4 四个数字之一,表示使用这种卡片后,乌龟棋子将向前爬行相应的格子数。
游戏中,玩家每次需要从所有的爬行卡片中选择一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。
游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到该格子相应的分数。
玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的分数总和。
很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到
一种卡片使用顺序使得最终游戏得分最多。
现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到多少分吗?
输入格式
输入文件的每行中两个数之间用一个空格隔开。
第 1 行 2 个正整数 N 和 M,分别表示棋盘格子数和爬行卡片数。
第 2 行 N 个非负整数,a1,a2,……,aN,其中 ai表示棋盘第 i 个格子上的分数。
第 3 行 M 个整数,b1,b2,……,bM,表示 M 张爬行卡片上的数字。
输入数据保证到达终点时刚好用光 M 张爬行卡片。
输出格式
输出只有 1 行,包含 1 个整数,表示小明最多能得到的分数。
数据范围
1≤N≤350, 1≤M≤120, 0≤ai≤100, 1≤bi≤4, 每种爬行卡片的张数不会超过 40。
暴力枚举
#include<bits/stdc++.h>
using namespace std;
#define N 45
int n,m,card[5],a[355],f[N][N][N][N];
int main()
{
freopen("chess.in","r",stdin);
freopen("chess.out","w",stdout);
scanf("%d %d",&n,&m);
memset(f,0x80,sizeof(f));
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
for(int i=1,x;i<=m;++i) scanf("%d",&x),card[x]++;
f[0][0][0][0]=0;
for(int i=0;i<=card[1];++i)
for(int j=0;j<=card[2];++j)
for(int s=0;s<=card[3];++s)
for(int k=0;k<=card[4];++k)
{
int &u=f[i][j][s][k],v=1+i+2*j+3*s+4*k;
if(i) u=max(u,f[i-1][j][s][k]);
if(j) u=max(u,f[i][j-1][s][k]);
if(s) u=max(u,f[i][j][s-1][k]);
if(k) u=max(u,f[i][j][s][k-1]);
u+=a[v];
}
printf("%d",f[card[1]][card[2]][card[3]][card[4]]);
return 0;
}
减操作
给定一个整数数组a1,a2,…,an。
定义数组第 i 位上的减操作:把ai和ai+1换成ai-ai+1。
用con(a,i)表示减操作,可以表示为:
con(a,i)=[a1,a2,…,ai?1,ai?ai+1,ai+2,…,an]
长度为 n 的数组,经过 n?1 次减操作后,就可以得到一个整数 t。
例如数组[12,10,4,3,5]经过如下操作可得到整数4:
con([12,10,4,3,5],2) = [12,6,3,5]
con([12,6,3,5] ,3) = [12,6,-2]
con([12,6,-2] ,2) = [12,8]
con([12,8] ,1) = [4]
现在给定数组以及目标整数,求完整操作过程。
输入格式
第1行包含两个整数n和t。
第2..n+1行:第i行包含数组中的第 i 个整数ai。
输出格式
输出共n-1行,每行包含一个整数,第 i 行的整数表示第
i 次减操作的操作位置。
数据范围
1≤n≤100,
?10000≤t≤10000,
1≤ai≤100
输入样例:
5 4
12
10
4
3
5
输出样例:
2
2
1
1
a1一定为正,a2一定为负,用SPFA快速更新到ans的同时保存符号和前驱。
ans数组储存a1~an的符号。
1.加号变成减号
if(ans[i])
{
printf("%d\n",i-cnt-1);
cnt++;
}
2.输出减号
所有的减号都可以直接用a1去减ai得到,所以可以直接输出1。
#include<bits/stdc++.h>
using namespace std;
#define N 105
#define T 10001
int n,m,a[N],pre[N][T];
//int last[N],cnt[N],c[N];
bool f[N][T<<2],ans[N];
//1+ 0-
void dp()
{
f[1][a[1]+T]=1;
pre[2][a[1]-a[2]+T]=a[1]+T;
deque<int>x,y;
x.push_back(2);
y.push_back(a[1]-a[2]+T);
//SPFA快速更新到ans
while(x.size())
{
int xx=x.front(),yy=y.front();
x.pop_front();y.pop_front();
f[xx+1][yy+a[xx+1]]=1;
pre[xx+1][yy+a[xx+1]]=yy;
f[xx-1][yy-a[xx+1]]=0;
pre[xx+1][yy-a[xx+1]]=yy;
if(xx+1==n&&(yy+a[xx+1]==m+T||yy-a[xx+1]==m+T)) break;
x.push_back(xx+1);
x.push_back(xx+1);
y.push_back(yy+a[xx+1]);
y.push_back(yy-a[xx+1]);
}
}
void print()
{
for(int i=n,p=m+T;i;p=pre[i][p],--i)
ans[i]=f[i][p];
//存储符号
int cnt=0;
for(int i=2;i<=n;++i)
if(ans[i])
{
printf("%d\n",i-cnt-1);
cnt++;
}
for(int i=2;i<=n;++i)
if(!ans[i]) printf("1\n");
}
int main()
{
// int
scanf("%d %d",&n,&m);
for(int i=1;i<=n;++i) scanf("%d",&a[i]);
dp();
print();
}
陨石的秘密
公元11380年,一颗巨大的陨石坠落在南极。
于是,灾难降临了,地球上出现了一系列反常的现象。
当人们焦急万分的时候,一支中国科学家组成的南极考察队赶到了出事地点。
经过一番侦察,科学家们发现陨石上刻有若干行密文,每一行都包含5个整数:
1 1 1 1 6
0 0 6 3 57
8 0 11 3 2845
著名的科学家SS发现,这些密文实际上是一种复杂运算的结果。
为了便于大家理解这种运算,他定义了一种SS表达式:
1. SS表达式是仅由’{‘,’}’,’[‘,’]’,’(’,’)’组成的字符串。
2. 一个空串是SS表达式。
3. 如果A是SS表达式,且A中不含字符’{‘,’}’,’[‘,’]’,则(A)是SS表达式。
4. 如果A是SS表达式,且A中不含字符’{‘,’}’,则[A]是SS表达式。
5. 如果A是SS表达式,则{A}是SS表达式。
6. 如果A和B都是SS表达式,则AB也是SS表达式。
例如
()(())[]
{()[()]}
{{[[(())]]}}
都是SS表达式。
而
()([])()
[()
不是SS表达式。
一个SS表达式E的深度D(E)定义如下:
例如(){()}[]的深度为2。
密文中的复杂运算是这样进行的:
设密文中每行前4个数依次为L1,L2,L3,D,
求出所有深度为D,含有L1对{},L2对[],L3
对()的SS串的个数,并用这个数对当前的年份
11380求余数,这个余数就是密文中每行的第5
个数,我们称之为神秘数。
密文中某些行的第五个数已经模糊不清,而这
些数字正是揭开陨石秘密的钥匙。
现在科学家们聘请你来计算这个神秘数。
输入格式
共一行,4个整数 L1,L2,L3,D。
输出格式
共一行,包含一个整数,即神秘数。
数据范围
0≤L1,L2,L3≤10,
0≤D≤30
输入样例:
1 1 1 2
输出样例:
输出样例:
8
#include<bits/stdc++.h>
using namespace std;
#define p 11380
#define N 15
#define M 35
int L1,L2,L3,D;
int f[M][N][N][N];
int main()
{
scanf("%d %d %d %d",&L1,&L2,&L3,&D);
if(!D)
{
if(L1||L2||L3) printf("%d",0);
else printf("%d",1);
return 0;
}
for(int d=0;d<=D;++d) f[d][0][0][0]=1;
for(int d=1;d<=D;++d)
for(int l1=0;l1<=L1;++l1)
for(int l2=0;l2<=L2;++l2)
for(int l3=0;l3<=L3;++l3)
if(l1||l2||l3)
{
int &val=f[d][l1][l2][l3];
for(int q1=1;q1<=l1;++q1)
for(int q2=0;q2<=l2;++q2)
for(int q3=0;q3<=l3;++q3)
val=(val+f[d-1][q1-1][q2][q3]*f[d][l1-q1][l2-q2][l3-q3]%p)%p;
for(int q2=1;q2<=l2;++q2)
for(int q3=0;q3<=l3;++q3)
val=(val+f[d-1][0][q2-1][q3]*f[d][l1][l2-q2][l3-q3]%p)%p;
for(int q3=1;q3<=l3;++q3)
val=(val+f[d-1][0][0][q3-1]*f[d][l1][l2][l3-q3]%p)%p;
}
printf("%d\n",(f[D][L1][L2][L3]-f[D-1][L1][L2][L3]+p)%p);
}
[COCI 2010] KABOOM
十分难的一道dp。
题目描述
译自 COCI 2010.02 T5「KABOOM」
Luka 在实验室里发现了一条奇怪的胶带。胶带分为 NN 段,从左到右依次编号为 1\ldots N1…N。胶带厚度忽略不计。
胶带只能在两段的交点处弯折,且只能折叠 180°。
显然胶带有两面。胶带的一面涂满了粘性超大的胶,另一面则只有前 AA 段和后 BB 段涂了粘性超大的胶。
请问 Luka 有多少种折叠方式使他能还原现场(Luka 的手不会粘住胶带,但如果两个胶面粘一起了 Luka 就撕不开了)。答案对 1030110301 取模。
输入格式
第一行,三个整数 N,A,BN,A,B。
输出格式
一行一个整数,表示答案。
输入输出样例
输入 #1 复制
4 1 1
输出 #1 复制
6
输入 #2 复制
5 2 2
输出 #2 复制
1
输入 #3 复制
6 1 2
输出 #3 复制
7
说明/提示
样例说明 1
p1.png p2.png
数据范围与提示
1\le A+B\le N\le 1000,1≤A+B≤N≤1000, A>0,A>0, B>0B>0.
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e3+5,mod=10301;
int n,a,b,ans;
int dp[N][N],sum[N];
signed main()
{
scanf("%lld %lld %lld",&n,&a,&b);
for(int i=0;i<=n;++i) dp[i][i]=1;
for(int i=0;i<=n;++i)
{
int sum=0;
for(int j=i;j>=1;--j)
{
sum=(sum+dp[i-j][j])%mod;
dp[i][j]=(dp[i][j]+sum)%mod;
}
}
for(int i=1;i<=n;++i) sum[i]=(sum[i-1]+dp[i][a])%mod;
for(int i=b;i<=n-a;++i) ans=(ans+sum[n-i]*dp[i][b]%mod)%mod;
printf("%lld\n",ans);
}
/*
当纸带剩余长度为i,其中前j段为胶带的时候,向后折的长度s必须大于等于j
折了s之后,纸带长度为i-s,其中前s段为胶带
所以从长度为i-s,其中前s段为胶带的状态可以还原到长度为i前j段为胶带的状态
所以就有
dp[i][j]+=sum(dp[i-s][s]),s∈[j,i]
sum:剩了i的空间时候,有多少搭配方案
f[i][a]:剩了i的空间,前a段是胶带,在此处可以理解为
剩了i的空间时,开始折,可以折出多少种方案
*/

浙公网安备 33010602011771号