写在之前,题单来自jackyan
1 https://codeforces.com/problemset/problem/467/C
【题意】给定一个长度为n的序列,找到k个长度为m的子串(不是子序列),求能得到的每个子串相加后的最大值。特别,子串下标不能重复且不能交叉 。
i
i
i 表示下标
j
j
j 表示子串个数
不新增子串,那最大值与上一次的状态相同;
新增子串,那最大值为以当前下标
i
i
i 结尾的
[
i
−
m
+
1
,
i
]
[i-m+1,i]
[i−m+1,i] 长度的子串,自然
d
p
[
i
−
m
]
[
j
−
1
]
dp[i-m][j-1]
dp[i−m][j−1]为不增加这个子串的最大值,然后再用前缀和加上该区间的值。
两种情况比较取最大(注意第二种情况
i
i
i 的取值范围)
d
p
[
i
]
[
j
]
=
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
−
m
]
[
j
−
1
]
+
b
[
i
]
−
b
[
i
−
m
]
dp[i][j]=max(dp[i-1][j],dp[i-m][j-1]+b[i]-b[i-m]
dp[i][j]=max(dp[i−1][j],dp[i−m][j−1]+b[i]−b[i−m]
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=5050;
ll a[N],b[N];
ll dp[N][N];
int main(){
int n,m,k;
cin>>n>>m>>k;
for(int i=1;i<=n;i++){
cin>>a[i];
b[i]=b[i-1]+a[i];
}
int cnt=0;
for(int i=1;i<=n;i++){
for(int j=1;j<=k;j++){
if(i-m>=0){
dp[i][j]=max(dp[i-1][j],dp[i-m][j-1]+b[i]-b[i-m]);
}
else dp[i][j]=dp[i-1][j];
}
}
cout<<dp[n][k]<<endl;
return 0;
}
2 http://codeforces.com/problemset/problem/706/C
【题意】给定
n
n
n 个字符串,若第
i
i
i 个字符串翻转则需要加上权值
w
i
w_i
wi,若从第一个字符串开始,保证当前字典序一定不比下一个大,问满足此条件,权值和最小是多少。不满足输出-1.
首先想了个离散化,离散化了字符串,错在了前后可能有相同串出现。
因为每个状态只有翻转和不翻转两种情况,翻转权值加
w
i
w_i
wi,则可以得到状态转移方程:
{
d
p
[
i
]
[
0
]
=
m
i
n
(
d
p
[
i
]
[
0
]
,
d
p
[
i
−
1
]
[
0
]
)
s
[
i
]
≥
s
[
i
−
1
]
d
p
[
i
]
[
0
]
=
m
i
n
(
d
p
[
i
]
[
0
]
,
d
p
[
i
−
1
]
[
0
]
)
s
[
i
]
≥
r
s
[
i
−
1
]
d
p
[
i
]
[
0
]
=
m
i
n
(
d
p
[
i
]
[
0
]
,
d
p
[
i
−
1
]
[
0
]
)
r
s
[
i
]
≥
s
[
i
−
1
]
d
p
[
i
]
[
0
]
=
m
i
n
(
d
p
[
i
]
[
0
]
,
d
p
[
i
−
1
]
[
0
]
)
r
s
[
i
]
≥
r
s
[
i
−
1
]
\left\{ \begin{array}{rcl} dp[i][0]=min(dp[i][0],dp[i-1][0]) & & {s[i]\geq s[i-1]}\\ dp[i][0]=min(dp[i][0],dp[i-1][0]) & & {s[i]\geq rs[i-1]}\\ dp[i][0]=min(dp[i][0],dp[i-1][0]) & & {rs[i]\geq s[i-1]}\\ dp[i][0]=min(dp[i][0],dp[i-1][0]) & & {rs[i]\geq rs[i-1]} \end{array} \right.
⎩⎪⎪⎨⎪⎪⎧dp[i][0]=min(dp[i][0],dp[i−1][0])dp[i][0]=min(dp[i][0],dp[i−1][0])dp[i][0]=min(dp[i][0],dp[i−1][0])dp[i][0]=min(dp[i][0],dp[i−1][0])s[i]≥s[i−1]s[i]≥rs[i−1]rs[i]≥s[i−1]rs[i]≥rs[i−1]
其中
r
s
[
i
]
rs[i]
rs[i] 表示第
i
i
i 个字符串翻转之后的字符串。0代表不翻转,1代表翻转。
在定义正无穷时要定到1e15的级别,这道题的数据有很大的答案。
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100;
typedef long long ll;
const ll INF=1e15;
ll w[N];
string s[N],rs[N];
ll dp[N][2];
string change(string s){
reverse(s.begin(),s.end());
return s;
}
int main(){
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>w[i];
for(int i=1;i<=n;i++){
cin>>s[i];
rs[i]=change(s[i]);
}
// memset(dp,INF,sizeof(dp));
for(int i=1;i<=n;i++) dp[i][0]=INF,dp[i][1]=INF;
dp[1][0]=0;
dp[1][1]=w[1];
for(int i=2;i<=n;i++){
if(s[i]>=s[i-1]) dp[i][0]=min(dp[i][0],dp[i-1][0]);
if(s[i]>=rs[i-1]) dp[i][0]=min(dp[i][0],dp[i-1][1]);
if(rs[i]>=s[i-1]) dp[i][1]=min(dp[i][1],dp[i-1][0]+w[i]);
if(rs[i]>=rs[i-1]) dp[i][1]=min(dp[i][1],dp[i-1][1]+w[i]);
}
ll ans=min(dp[n][0],dp[n][1]);
if(ans==INF) cout<<-1<<endl;
else cout<<ans<<endl;
return 0;
}
3 https://codeforces.com/problemset/problem/611/C
【题意】有一个n*m的地图,地图上有“.”和“#”,当相邻的两块都为“.”时可以放置一个多米诺骨牌,现给定一个矩形区域,问在这个矩形区域内有多少种放置骨牌的方法。
如果直接做二维,碰到#时会交代不清楚,没法很好的转移状态,所以分为行dp和列dp进行转移,这样试一下样例发现可以很好的表示,如列dp,在一行上,如果第
i
i
i 个为".“和第
i
−
1
i-1
i−1 个为”.",可以直接写成
d
p
[
i
]
=
d
p
[
i
−
1
]
+
1
dp[i]=dp[i-1]+1
dp[i]=dp[i−1]+1 ;如果第
i
i
i 个为"#“或第
i
−
1
i-1
i−1 个为”#",可以直接写成
d
p
[
i
]
=
d
p
[
i
−
1
]
dp[i]=dp[i-1]
dp[i]=dp[i−1],表示没有新增的放置方式。
d
p
[
i
]
dp[i]
dp[i] 表示
1
i
1~i
1 i 最多有多少种放置方式。
#include<bits/stdc++.h>
using namespace std;
const int N=505;
typedef long long ll;
ll hdp[N][N],wdp[N][N];
int main(){
int h,w,q;
cin>>h>>w;
string g[N];
for(int i=0;i<=h;i++) g[0]+='#';
for(int i=1;i<=h;i++){
cin>>g[i];
g[i]="#"+g[i];
}
for(int i=1;i<=h;i++){
for(int j=1;j<=w;j++){
if(g[i][j]=='.'&&g[i-1][j]=='.')
hdp[i][j]++;
if(g[i][j]=='.'&&g[i][j-1]=='.')
wdp[i][j]++;
hdp[i][j]+=hdp[i-1][j];
wdp[i][j]+=wdp[i][j-1];
}
}
cin>>q;
int xa,ya,xb,yb;
while(q--){
cin>>xa>>ya>>xb>>yb;
ll ans=0;
for(int y=ya;y<=yb;y++)
ans+=(hdp[xb][y]-hdp[xa][y]);
for(int x=xa;x<=xb;x++)
ans+=(wdp[x][yb]-wdp[x][ya]);
cout<<ans<<endl;
}
return 0;
}