概述
普及~普及/提高
线性dp
P1002 [NOIP2002 普及组] 过河卒
P1002 [NOIP2002 普及组] 过河卒
题意:
一个二维方格,给定一个点为马的位置,以这个点为中心, 象棋中马能到的点都是无法到的点,这个马是固定的。卒在起点
(
0
,
0
)
(0,0)
(0,0),到终点
(
n
,
m
)
(n,m)
(n,m) 的路径有多少种。
思路:
起点
(
0
,
0
)
(0,0)
(0,0) 写转移方程不方便,需要写
i
f
if
if 判边界,因此把坐标都加
1
1
1,显然答案是不变的,把不能到的点标记上,然后转移即可
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e2 + 9;
const int mod = 1e9 + 7;
ll n, m;
ll f[maxn][maxn];
bool vis[maxn][maxn];
void work()
{
int a, b, c, d;cin >> a >> b >> c >> d;
++a,++b,++c,++d;
vis[c][d] = 1;
vis[c-1][d+2] = vis[c-1][d-2] = vis[c-2][d+1] = vis[c-2][d-1] = 1;
vis[c+1][d-2] = vis[c+1][d+2] = vis[c+2][d+1] = vis[c+2][d-1] = 1;
if(!vis[2][1]) f[2][1] = 1;
if(!vis[1][2]) f[1][2] = 1;
for(int i = 1; i <= a; ++i)
for(int j = 1; j <= b; ++j) if(!vis[i][j])
{
f[i][j] += f[i-1][j] + f[i][j-1];
}
cout << f[a][b];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P1057 [NOIP2008 普及组] 传球游戏
P1057 [NOIP2008 普及组] 传球游戏
题意:
n
n
n 个人围成一个圈,初始球在
1
1
1 号同学手上,每次可以选择将球传给左边或者右边的同学,在传球
m
m
m 次之后,计算球又回到
1
1
1 号同学手上的方法又多少种(两种方法不同:接到球的同学按接球顺序组成的序列是不同的
思路:
第一步:确定状态(动态变量)——原问题是什么,子问题是什么?
原问题:从
1
1
1 开始传球,第
m
m
m 步回到
1
1
1 号的情况数
子问题:从
1
1
1 开始传球第
i
i
i 步到达
j
j
j 号的情况数
d
p
[
i
]
[
j
]
dp[i] [j]
dp[i][j] 表示第
i
i
i 次传球后在
j
j
j 位置的情况数
第二步:确定状态转移方程和边界
d
p
[
i
]
[
j
]
=
d
p
[
i
−
1
]
[
j
−
1
]
+
d
p
[
i
−
1
]
[
j
+
1
]
dp[i] [j] = dp[i - 1] [j - 1] + dp[i - 1] [j + 1]
dp[i][j]=dp[i−1][j−1]+dp[i−1][j+1]
d
p
[
0
]
[
1
]
=
1
dp[0] [1] = 1
dp[0][1]=1,初始条件一号开始的时候是1
是个环,需要对边界
1
1
1 和
m
m
m 进行讨论
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
using namespace std;
const int maxn = 1e2 + 9;
const int mod = 1e9 + 7;
ll n, m, k;
ll f[maxn][maxn];// f[i][j]表示前j位添加了i个乘号时的最优解
string s;
ll cal(int i, int j)//计算一段的值
{
ll sum = 0;
for(int k = i; k <= j; ++k)
sum = sum * 10 + (s[k] - '0');
return sum;
}
void work()
{
cin >> n >> k >> s;
s = "@" + s;
for(int i = 1; i <= n; ++i) f[0][i] = cal(1, i);//边界,在没有乘号的情况下最优解总是前i位
for(int i = 1; i <= k; ++i)
{
for(int j = i + 1; j <= n; ++j)
{
for(int h = j - 1; h >= i; --h)
f[i][j] = max(f[i][j], f[i-1][h] * cal(h + 1, j));
}
}
cout << f[k][n];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P1018 [NOIP2000 提高组] 乘积最大(普及+/提高
P1018 [NOIP2000 提高组] 乘积最大
题意:
给定一个
n
n
n 和
k
k
k,以及一个数字串
s
s
s,
n
n
n 表示数字串的长度,
k
k
k 表示在这个数字串中插入
k
k
k 个乘号分成
k
+
1
k+1
k+1 份,求最大乘积
思路:
(这个题拿60分,明白动态规划咋写就行了,高精就懒得搞了
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
using namespace std;
const int maxn = 1e2 + 9;
const int mod = 1e9 + 7;
ll n, m, k;
ll f[maxn][maxn];// f[i][j]表示前j位添加了i个乘号时的最优解
string s;
ll cal(int i, int j)//计算一段的值
{
ll sum = 0;
for(int k = i; k <= j; ++k)
sum = sum * 10 + (s[k] - '0');
return sum;
}
// 1231
2
void work()
{
cin >> n >> k >> s;
s = "@" + s;
for(int i = 1; i <= n; ++i) f[0][i] = cal(1, i);//边界,在没有乘号的情况下最优解总是前i位
for(int i = 1; i <= k; ++i)
{
for(int j = 1; j <= n; ++j)
{
for(int h = j; h >= i; --h)
f[i][j] = max(f[i][j], f[i-1][h-1] * cal(h, j));
}
}
cout << f[k][n];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
other:
交换枚举顺序
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 200 + 9;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3f;
ll n, m;
ll f[maxn][10];
string s;
ll cal(int i, int j){
ll sum = 0;
for(int k = i; k <= j; ++k)
sum = sum * 10 + (s[k] - '0');
return sum;
}
void work()
{
cin >> n >> m;
cin >> s;s = "@" + s;
for(int i = 1; i <= n; ++i) f[i][0] = cal(1, i);
for(int i = 1; i <= n; ++i)
for(int j = i; j >= 2; --j)
for(int k = 1; k <= m; ++k)
f[i][k] = max(f[i][k], f[j-1][k-1] * cal(j, i));
cout << f[n][m];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
U187635 刷墙(easy) 21级新生赛的题目
U187635 刷墙(easy)
(21级新生赛的题目
题意:
给定一个被分成
n
n
n 块的墙壁,
m
m
m 个工人,
a
i
a_i
ai 表示墙块需要刷成的的颜色,刷墙块只能刷连续的子段,如果一个工人刷的一段连续的墙为 {
1
,
1
,
1
,
2
,
2
,
3
,
2
,
2
1,1,1,2,2,3,2,2
1,1,1,2,2,3,2,2},那么应该支付
3
2
+
2
2
+
1
2
+
2
2
=
18
3^2+2^2+1^2+2^2=18
32+22+12+22=18,也就是所有连续的相同颜色子段的长度的平方和。求刷完所有墙的最少花费
思路:
(这道题比一般的还是难一些的,和上边的乘积最大很像
考虑
f
[
i
]
[
j
]
f[i][j]
f[i][j] 为
j
j
j 个工人刷 前
i
i
i 块墙的花费
那么
f
[
i
]
[
j
]
=
m
i
n
(
f
[
k
]
[
j
−
1
]
(
k
∈
[
j
−
1
,
i
)
)
+
w
[
k
+
1
]
[
i
]
,
f
[
i
]
[
j
]
)
f[i][j]=min({f[k][j-1](kin[j-1,i))} + w[k+1][i],f[i][j])
f[i][j]=min(f[k][j−1](k∈[j−1,i))+w[k+1][i],f[i][j])
显然
w
w
w 数组需要预处理出来,才能进行状态转移
而
w
w
w 数组的预处理也需要用动态规划的思想解决:
考虑
w
[
i
]
[
j
]
w[i][j]
w[i][j] 为粉刷
[
i
,
j
]
[i,j]
[i,j] 这段墙的花费,我们可以枚举左端点,考虑以
i
i
i 为起点的递推到
n
n
n,当前连续的长度为
c
n
t
cnt
cnt,如果下一个也相同,那么 根据
(
c
n
t
+
1
)
2
−
c
n
t
2
=
2
∗
c
n
t
+
1
(cnt+1)^2-cnt^2=2*cnt+1
(cnt+1)2−cnt2=2∗cnt+1 即可得到比
w
[
i
]
[
j
]
w[i][j]
w[i][j] 多出的花费,
w
[
i
]
[
j
]
=
w
[
i
]
[
j
−
1
]
+
2
∗
c
n
t
+
1
w[i][j]=w[i][j-1]+2*cnt+1
w[i][j]=w[i][j−1]+2∗cnt+1,不相等则多出的花费就是
1
1
1。
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e3 + 9;
const int mod = 1e9 + 7;
ll n, m;
int a[maxn];
ll f[2009][29];
ll w[maxn][maxn];
void work()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i) cin >> a[i], w[i][i] = 1;
for(int i = 1; i <= n; ++i)
for(int j = i + 1, cnt = 0; j <= n; ++j)
{
if(a[j] == a[j-1]) ++cnt;
else cnt = 0;
w[i][j] = w[i][j-1] + cnt * 2 + 1;
}
memset(f, 0x3f, sizeof(f));
f[0][0] = 0;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= min(1ll * i, m); ++j){// 后两个枚举顺序可变
for(int k = j - 1; k < i; ++k)
f[i][j] = min(f[i][j], f[k][j-1] + w[k + 1][i]);
}
}
cout << f[n][m];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
牛客题—过桥
牛客题—过桥
题意:
n
n
n 个浮块按照
1
−
n
1-n
1−n 标号,每块上边有一个数字
a
i
a_i
ai,如果是正数,那么它可以移动到第
i
+
k
(
k
∈
[
1
,
a
[
i
]
]
i+k(kin[1,a[i]]
i+k(k∈[1,a[i]] 块浮块上,负数表示它可以移动到
<
=
i
+
a
[
i
]
<=i+a[i]
<=i+a[i] 的任意一块浮块,当
i
+
a
[
i
]
<
1
i+a[i]<1
i+a[i]<1 则传送到
1
1
1。每次花费一秒,求从
1
1
1 到
n
n
n 的最短时间
思路:
这题虽然暴力建图就能过,但是这是道dp题
首先不考虑
a
i
a_i
ai 是负数的情况,那么它从
1
1
1 传到
i
i
i 的步数显然是逐渐递增的,
f
[
i
]
f[i]
f[i] 是一个非递减数组。当出现
a
i
<
0
a_i<0
ai<0 时,它只能往回跳,
f
[
j
]
=
m
i
n
(
f
[
j
]
,
f
[
i
]
+
1
)
(
j
<
i
)
f[j]=min(f[j],f[i]+1)(j < i)
f[j]=min(f[j],f[i]+1)(j<i),这显然是没必要的更新,他不会发生更新。
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
int a[maxn], f[maxn];
void work()
{
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> a[i];
memset(f, 0x3f, sizeof(f));
f[1] = 0;
for(int i = 1; i <= n; ++i)
{
if(a[i] > 0)
{
for(int j = i + 1; j <= i + a[i]; ++j)
f[j] = min(f[j], f[i] + 1);
}
}
if(f[n] != 1061109567)cout << f[n];
else cout << -1;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
CF414B Mashmokh and ACM
CF414B Mashmokh and ACM
题意:
如果一个数组的后一个可以被前一个整除那么就是一个好数列
给定一个
n
n
n 和
k
k
k,求数组中最大元素不超过
n
n
n,长度为
k
k
k 的好数列的个数。
1
<
=
n
,
k
<
=
2000
1<=n,k<=2000
1<=n,k<=2000
思路:
计数dp,需要预处理每个数的因子
考虑
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示最大数为
i
i
i,长度为
j
j
j 时的满足条件的数列数量
边界
f
[
1
]
[
0
]
=
1
f[1][0]=1
f[1][0]=1
i
s
is
is
i
m
p
o
r
t
a
n
t
important
important
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
using namespace std;
const int maxn = 2e3 + 9;
const int mod = 1e9 + 7;
ll n, m;
ll f[maxn][maxn];
vector <int> v[maxn];
void work()
{
for(int i = 1; i <= 2000; ++i){
for(int j = 1; j <= i; ++j){
if(i % j == 0){
v[i].push_back(j);
}
}
}
cin >> n >> m;
f[1][0] = 1;// n最小为 1
for(int i = 2; i <= n; ++i)
for(int j = 1; j <= m; ++j)
{
for(int k = 0; k < v[i].size(); ++k)// 由i的因子转移过来
f[i][j] = (f[i][j] + f[v[i][k]][j-1]) % mod;
}
ll ans = 0;
for(int i = 1; i <= n; ++i)
ans = (ans + f[i][m]) % mod;
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P1077 [NOIP2012 普及组] 摆花
P1077 [NOIP2012 普及组] 摆花
题意:
给定一个序列
n
n
n 个数,
m
m
m 个花盆,
a
i
a_i
ai 表示第
i
i
i 种花最多放
a
i
a_i
ai 朵,要求放花的顺序按照编号来,同一种花只能连着放,求一共有多少种不同的摆花方案。
思路:
计数
d
p
dp
dp,考虑
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示前
i
i
i 种花放了
j
j
j 朵
那么转移方程
f
[
i
]
[
j
]
+
=
f
[
i
−
1
]
[
j
−
k
]
(
k
∈
[
0
,
m
i
n
(
j
,
a
[
i
]
)
]
f[i][j]+=f[i-1][j-k](kin[0,min(j,a[i])]
f[i][j]+=f[i−1][j−k](k∈[0,min(j,a[i])]
这题要注意枚举
j
j
j 和
k
k
k 都要从
0
0
0 开始
多思考边界枚举边界问题
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e2 + 9;
const int mod = 1e6 + 7;
ll n, m;
int a[maxn];
int f[maxn][maxn];
void work()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i) cin >> a[i];
f[0][0] = 1;
for(int i = 1; i <= n; ++i)
for(int j = 0; j <= m; ++j)
for(int k = 0; k <= min(j, a[i]); ++k)
f[i][j] = (f[i][j] + f[i-1][j-k]) % mod;
cout << f[n][m];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
LIS—P1091 [NOIP2004 提高组] 合唱队形
LIS—P1091 [NOIP2004 提高组] 合唱队形
题意:
给定
n
n
n 个高度
a
i
a_i
ai,从中选出
m
m
m 个,满足
t
1
<
t
2
<
.
.
.
<
t
i
>
t
i
+
1
>
.
.
.
>
t
m
t_1<t_2<...<t_i>t_{i+1}>...>t_m
t1<t2<...<ti>ti+1>...>tm,最大的
m
m
m 是多少,输出
n
−
m
n-m
n−m 即可
思路:
考虑从前往后和从后往前求得两个最长上升子序列,枚举最高点维护最大值即可。
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e2 + 9;
const ll mod = 1000000000;
ll n, m;
int a[maxn];
int fh[maxn], fl[maxn];
void work()
{
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i];
for(int i = 1; i <= n; ++i){
fh[i] = 1;
for(int j = 1; j < i; ++j) if(a[j] < a[i]){
fh[i] = max(fh[i], fh[j] + 1);
}
}
for(int i = n; i >= 1; --i){
fl[i] = 1;
for(int j = n; j > i; --j) if(a[j] < a[i]){
fl[i] = max(fl[i], fl[j] + 1);
}
}
int ans = 0;
for(int i = 1; i <= n; ++i)
ans = max(ans, fl[i] + fh[i] - 1);
cout << n - ans << endl;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P2513 [HAOI2009]逆序对数列(需优化
P2513 [HAOI2009]逆序对数列
题意:
给定
n
,
k
n,k
n,k,求
1
−
n
1-n
1−n 的全排列中逆序对为
m
m
m 的数量
思路:
考虑
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示
1
−
i
1-i
1−i 的全排列中逆序对为
j
j
j 的数量
初始化
f
[
1
]
[
0
]
=
1
f[1][0]=1
f[1][0]=1
考虑在
i
−
1
i-1
i−1 的排列中加入
i
i
i,增加的逆序对最少是
0
0
0,最多会增加
i
−
1
i-1
i−1
枚举增加的逆序对
k
k
k
f
[
i
]
[
j
]
=
∑
k
=
0
m
i
n
(
i
−
1
,
j
)
f
[
i
−
1
]
[
j
−
k
]
f[i][j]=sum_{k=0}^{min(i-1,j)}f[i-1][j-k]
f[i][j]=k=0∑min(i−1,j)f[i−1][j−k]
这么做会
T
L
E
TLE
TLE
考虑优化第三层循环,这是一个用于累加的
f
o
r
for
for,显然可以用前缀和优化
但是根据上述式子不好推导优化,因为
j
−
k
j-k
j−k 的累加无法用前缀和描述,因此改变一下上边式子的形式
∵
k
∈
[
0
,
m
i
n
(
i
−
1
,
j
)
]
∴
−
k
∈
[
−
m
i
n
(
i
−
1
,
j
)
,
0
]
∴
j
−
k
∈
[
j
−
m
i
n
(
i
−
1
,
j
)
,
j
]
⟹
j
−
k
∈
[
m
a
x
(
0
,
j
−
i
+
1
)
,
j
]
⟹
f
[
i
]
[
j
]
=
∑
k
=
m
a
x
(
0
,
j
−
i
+
1
)
j
f
[
i
−
1
]
[
k
]
because kin[0,min(i-1,j)]\ therefore -kin[-min(i-1,j),0]\ therefore j-kin[j-min(i-1,j),j] \ Longrightarrow j-kin[max(0,j-i+1),j]\ Longrightarrow f[i][j]=sum_{k=max(0,j-i+1)}^{j}f[i-1][k]
∵k∈[0,min(i−1,j)]∴−k∈[−min(i−1,j),0]∴j−k∈[j−min(i−1,j),j]⟹j−k∈[max(0,j−i+1),j]⟹f[i][j]=k=max(0,j−i+1)∑jf[i−1][k]
做完这个变形就可以用一个
s
u
m
sum
sum 来记录前缀和
注意,当
j
>
i
−
1
j>i-1
j>i−1 时,需要减掉起点
j
−
(
i
−
1
)
j-(i-1)
j−(i−1) 之前的
T
L
E
TLE
TLEcode:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e3 + 9;
const int mod = 10000;
ll n, m;
ll f[maxn][maxn];
void work()
{
cin >> n >> m;
f[1][0] = 1;
for(int i = 2; i <= n; ++i)
for(int j = 0; j <= m; ++j)
for(int k = 0, t = min(i-1, j); k <= t; ++k)//枚举增加的逆序对 $k$
{
f[i][j] = (f[i][j] + f[i-1][j - k]) % mod;
}
cout << f[n][m];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
A C AC ACcode:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e3 + 9;
const int mod = 10000;
ll n, m;
int f[maxn][maxn];
void work()
{
cin >> n >> m;
f[1][0] = 1;
for(int i = 2; i <= n; ++i)
{
int sum = 0;
for(int j = 0; j <= m; ++j)
{
sum = (sum + f[i-1][j]) % mod;
if(j > i - 1)
sum = ((sum - f[i-1][j - (i-1) - 1]) % mod + mod) % mod;
f[i][j] = sum;
}
}
cout << f[n][m];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P2066 机器分配
P2066 机器分配
题意:
给定
n
n
n 个公司,
m
m
m 台机器,矩阵
A
i
,
j
A_{i,j}
Ai,j 表示第
i
i
i 个公司分配到
j
j
j 台机器所带来的利润,求分配完
m
m
m 台机器的最大利润
思路:
这道题的状态是公司和机器数量,考虑
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示前
i
i
i 个公司分配
j
j
j 台机器的最大利润,那么
f
[
i
]
[
j
]
f[i][j]
f[i][j] 可以由
f
[
i
−
1
]
[
j
−
k
]
+
m
p
[
i
]
[
k
]
(
k
∈
[
0
,
j
]
)
f[i-1][j-k]+mp[i][k](kin[0,j])
f[i−1][j−k]+mp[i][k](k∈[0,j]) 转移过来。
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
using namespace std;
const int maxn = 1e2 + 9;
const int mod = 1e9 + 7;
ll n, m;
ll mp[maxn][maxn];
ll f[maxn][maxn];// 前i个公司总共分配j台机器的最大利润
void print(int i, int j)
{
if(!i) return;
for(int k = j; k >= 0; --k) if(f[i][j] == f[i-1][j-k] + mp[i][k])
{// 找字典序最小,那就尽量让后边的数大,这样前边就尽量分配的小
print(i-1, j-k);
cout << i << " " << k << endl;
return;
}
}
void work()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
cin >> mp[i][j];
for(int i = 1; i <= n; ++i)
for(int j = 0; j <= m; ++j)
{
for(int k = 0; k <= j; ++k)
f[i][j] = max(f[i][j], f[i-1][j-k] + mp[i][k]);
}
cout << f[n][m] << endl;
print(n, m);
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P1107 [BJWC2008]雷涛的小猫(需优化,普及+/提高
P1107 [BJWC2008]雷涛的小猫
题意:
给定
n
n
n 棵树,每个树的高度都是
h
h
h,输入第
i
i
i 行第一个数为这棵树上的果子个数
n
u
m
num
num,然后输入
n
u
m
num
num 个果子所在的高度,一棵树上果子高度可能重复。小猫可以任选一棵高度
h
h
h 的树顶,然后在一棵树上下来时是连续的,但是如果从一棵树跳到另外一棵树上就会下降
d
d
d 高度,求小猫最多能吃多少果子。
思路:
考虑
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示小猫在第
j
j
j 棵树上,高度为
i
i
i 时最多能吃的果子数量
a
[
i
]
[
j
]
a[i][j]
a[i][j] 表示第
j
j
j 棵树上高度为
i
i
i 的果子数量
状态转移方程很好想,要么顺着这棵树下来,要么从某个树上跳过来
f
[
i
]
[
j
]
=
m
a
x
(
f
[
i
+
1
]
[
j
]
,
f
[
i
+
d
]
[
k
]
)
(
k
!
=
j
)
+
a
[
i
]
[
j
]
f[i][j] = max(f[i+1][j],f[i+d][k]) (k !=j ) + a[i][j]
f[i][j]=max(f[i+1][j],f[i+d][k]) (k !=j)+a[i][j]
看数据这显然会
T
T
T
T
L
E
TLE
TLE code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e3 + 9;
const int mod = 10000;
ll n, m, d;
ll f[maxn][maxn];
int a[maxn][maxn];
void work()
{
cin >> n >> m >> d;
for(int i = 1; i <= n; ++i)
{
int num;cin >> num;
for(int j = 1; j <= num; ++j)
{
int x;cin >> x;
a[x][i]++;
}
}
ll ans = 0;
for(int i = m; i >= 1; --i)
{
for(int j = 1; j <= n; ++j)
{
f[i][j] = f[i+1][j] + a[i][j];// 不跳,顺着第j颗向下移动
if(i + d <= m)// 跳跃转移
for(int k = 1; k <= n; ++k) if(j != k)
f[i][j] = max(f[i][j], f[i+d][k] + a[i][j]);
}
}
for(int i = 1; i <= n; ++i)
ans = max(ans, f[1][i]);
cout << ans;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
仔细观察状态转移方程,第三层
f
o
r
for
for 无非就是去获得
i
+
d
i+d
i+d 高度上最多的一个状态,我们只需要转移过来的时候维护一个高度为
j
(
j
+
d
<
=
h
)
j(j+d<=h)
j(j+d<=h) 时的最多果子的一个状态即可。
A
C
AC
AC code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e3 + 9;
const int mod = 10000;
ll n, m, d;
ll f[maxn][maxn];
int a[maxn][maxn];
ll h[maxn];
void work()
{
cin >> n >> m >> d;
for(int i = 1; i <= n; ++i)
{
int num;cin >> num;
for(int j = 1; j <= num; ++j)
{
int x;cin >> x;
a[x][i]++;
}
}
ll ans = 0;
for(int i = m; i >= 1; --i)
{
for(int j = 1; j <= n; ++j)
{
f[i][j] = f[i+1][j] + a[i][j];// 不跳,顺着第j颗向下移动
if(i + d <= m)// 跳跃转移
f[i][j] = max(f[i][j], h[i+d] + a[i][j]);
h[i] = max(h[i], f[i][j]);
}
}
for(int i = 1; i <= n; ++i)
ans = max(ans, f[1][i]);
cout << ans;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P2782 友好城市
P2782 友好城市
题意:
一条横着的河两岸边有各有
n
n
n 个城市,
n
n
n 行每行给定上边城市和下边城市的坐标表示两个城市一条边,由于可能出现边的交叉,但我们不想交叉,因此需要求出在边不交叉的情况下最多能画出多少条边。
思路:
按一边排序,然后对另一边求最长上升子序列
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 1e6 + 9;
const int mod = 1e9 + 7;
ll n, m;
struct node{
int x, y;
bool operator<(const node &B)const{
return x < B.x;
}
}a[maxn];
int f[maxn];
void work()
{
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i].x >> a[i].y;
sort(a + 1, a + 1 + n);
f[1] = a[1].y;
int cnt = 1;
for(int i = 2; i <= n; ++i)
{
if(a[i].y > f[cnt]){
f[++cnt] = a[i].y;
}
else
{
int pos = lower_bound(f + 1, f + 1 + cnt, a[i].y) - f;
f[pos] = a[i].y;
}
}
cout << cnt;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P1439 【模板】最长公共子序列
P1439 【模板】最长公共子序列
题意:
给定两个
n
n
n 的排列,求最长公共子序列(
n
<
=
1
0
5
)
n<=10^5)
n<=105)
思路:
这个题可以把排列
a
a
a 映射成
1
,
2
,
3...
n
1,2,3...n
1,2,3...n 的排列,按照这个映射修改
b
b
b
这个过程其实就是一个文本替换,显然
L
C
S
LCS
LCS 是不变的
但是我们得到了一个递增的排列
a
a
a,思考可以发现我们只需要找排列
b
b
b 中的
L
I
S
LIS
LIS 即可,因为这个最长上升子序列一定可以在
a
a
a 中找到
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
int a[maxn], b[maxn];
int c[maxn];
int f[maxn];
void work()
{
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i], c[a[i]] = i;
for(int i = 1; i <= n; ++i) cin >> b[i];
for(int i = 1; i <= n; ++i) b[i] = c[b[i]];
//for(int i = 1; i <= n; ++i) cout << b[i] << " ";cout << endl;
f[1] = b[1];
int c = 1;
for(int i = 2; i <= n; ++i)
{
if(b[i] > f[c]){
f[++c] = b[i];
}
else
{
int pos = lower_bound(f + 1, f + 1 + c, b[i]) - f;
f[pos] = b[i];
}
}
cout << c;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
矩阵dp
P1387 最大正方形
P1387 最大正方形
题意:
给定一个
01
01
01 矩阵,求边长最大的全
1
1
1 正方形。
思路:
考虑
f
[
i
]
[
j
]
f[i][j]
f[i][j] 为以
(
i
,
j
)
(i,j)
(i,j) 为右下角时的最大正方形边长
这个状态转移方程比较巧妙:
f
[
i
]
[
j
]
=
m
i
n
(
f
[
i
−
1
]
[
j
]
,
f
[
i
]
[
j
−
1
]
,
f
[
i
−
1
]
[
j
−
1
]
)
+
1
(
a
[
i
]
[
j
]
=
1
)
f[i][j]=min(f[i-1][j],f[i][j-1],f[i-1][j-1])+1 (a[i][j]=1)
f[i][j]=min(f[i−1][j],f[i][j−1],f[i−1][j−1])+1 (a[i][j]=1)
详解博客
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e2 + 9;
const int mod = 1e9 + 7;
ll n, m;
int a[maxn][maxn], f[maxn][maxn];
void work()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
cin >> a[i][j];
int ans = 0;
for(int i = 1; i <= n; ++i)
{
for(int j = 1; j <= m; ++j){
if(a[i][j])
f[i][j] = min({f[i-1][j], f[i][j-1], f[i-1][j-1]}) + 1;
ans = max(ans, f[i][j]);
}
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P1681 最大正方形II
P1681 最大正方形II
题意:
给定一个
n
×
m
ntimes m
n×m 的
01
01
01 矩阵,求
01
01
01 交错的最大正方形边长
思路:
f
[
i
]
[
j
]
[
0
/
1
]
f[i][j][0/1]
f[i][j][0/1] 表示以
(
i
,
j
)
(i,j)
(i,j) 为右下角时的最大正方形边长
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e3 + 9;
const int mod = 1e9 + 7;
ll n, m;
int a[maxn][maxn], f[maxn][maxn][2];
void work()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
cin >> a[i][j];
int ans = 0;
for(int i = 1; i <= n; ++i)
{
for(int j = 1; j <= m; ++j){
if(a[i][j] == 0)
f[i][j][0] = min({f[i-1][j][1], f[i][j-1][1], f[i-1][j-1][0]}) + 1;
else
f[i][j][1] = min({f[i-1][j][0], f[i][j-1][0], f[i-1][j-1][1]}) + 1;
ans = max(ans, f[i][j][0]);
ans = max(ans, f[i][j][1]);
}
}
cout << ans;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P1434 [SHOI2002]滑雪
P1434 [SHOI2002]滑雪
题意:
给定一个矩阵,每个位置一个高度,一个人可以从某个点沿着严格递减的高度移动,求最长移动的距离
思路:
经典题目,记忆化搜索即可
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
int dx[4] = {0, 0, 1, -1};
int dy[4] = {1, -1, 0, 0};
int mp[109][109];
int f[109][109];
bool check(int x, int y){
if(x < 1 || y < 1 || x > n || y > m) return false;
return true;
}
int dfs(int i, int j){
if(f[i][j]) return f[i][j];
f[i][j] = 1;
for(int k = 0; k < 4; ++k){
int x = i + dx[k], y = j + dy[k];
if(check(x, y) && mp[x][y] < mp[i][j]){
f[i][j] = max(f[i][j], dfs(x, y) + 1);
}
}
return f[i][j];
}
void work()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
cin >> mp[i][j];
}
}
int ans = 0;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= m; ++j){
ans = max(ans, dfs(i, j));
}
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P1854 花店橱窗布置
P1854 花店橱窗布置
题意:
n
n
n 个花,
m
(
m
>
=
n
)
m(m>=n)
m(m>=n) 个花瓶,花和花瓶从左到右被标上
i
i
i,如果
i
<
j
i<j
i<j,那么花束
i
i
i 必须放在花束
j
j
j 的左边,放完后的花束从左到右必须满足原来的顺序,每个花瓶只能放一朵花,所有花都要放完。接下来一个矩阵
A
i
,
j
A_{i,j}
Ai,j 表示第
i
i
i 朵花放在第
j
j
j 个花瓶中的美观度,求最大美观度。
思路:
把题目抽象出来,就是给定一个矩阵,从
(
1
,
1
)
(1,1)
(1,1) 开始往右下移动,每次先向右移动
k
k
k 格,然后向下移动一格,求移动到最后一行时的最大值(最后一行也要选定一个
k
k
k 向右移动,不用向下了
考虑
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示前
i
i
i 朵花插到前
j
j
j 个花瓶中的最大美观度,那么
f
[
i
]
[
j
]
f[i][j]
f[i][j] 可以由
m
a
x
(
f
[
i
−
1
]
[
i
−
1
]
,
f
[
i
−
1
]
[
i
]
.
.
.
f
[
i
−
1
]
[
j
−
1
]
)
+
a
[
i
]
[
j
]
max(f[i-1][i-1],f[i-1][i]...f[i-1][j-1]) + a[i][j]
max(f[i−1][i−1],f[i−1][i]...f[i−1][j−1])+a[i][j] 转移过来,那么答案就在
f
[
n
]
[
n
]
f[n][n]
f[n][n] 到
f
[
n
]
[
m
]
f[n][m]
f[n][m] 之间
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
using namespace std;
const int maxn = 1e2 + 9;
const int mod = 1e9 + 7;
ll n, m;
ll mp[maxn][maxn];
ll f[maxn][maxn];
void print(int i, int j)
{
if(!i) return;
for(int k = i - 1; k <= j - 1; ++k) if(f[i][j] == f[i - 1][k] + mp[i][j])
{
print(i - 1, k);
cout << j << " ";
break;
}
}
void work()
{
cin >> n >> m;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
cin >> mp[i][j];
memset(f, -0x3f, sizeof(f));
for(int i = 0; i <= m; ++i) f[0][i] = 0;
for(int i = 0; i <= n; ++i) f[i][0] = 0;
for(int i = 1; i <= n; ++i)
for(int j = i; j <= m - (n - i); ++j)
{// 所有花都要放完对 j和k的范围有约束
for(int k = i - 1; k <= j - 1; ++k)
f[i][j] = max(f[i][j], f[i-1][k] + mp[i][j]);
}
ll ans = -2e9, pos;
for(int i = n; i <= m; ++i) if(ans < f[n][i])
ans = f[n][i], pos = i;
cout << ans << endl;
print(n, pos);
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P1006 [NOIP2008 提高组传纸条(优化,普及+/提高
P1006 [NOIP2008 提高组] 传纸条
题意:
给定一个
m
×
n
mtimes n
m×n 的矩阵,每个位置有一个好感度值,求两个路径上好感度加和的最大值,从
(
1
,
1
)
(1,1)
(1,1) 到
(
m
,
n
)
(m,n)
(m,n) 和从
(
m
,
n
)
(m,n)
(m,n) 到
(
1
,
1
)
(1,1)
(1,1) 两条路径,要求路径不能有交点。
思路:
基础的四位数组解法:
这个两个路径可以等价为从
1
,
1
1,1
1,1 开始 到
m
,
n
m,n
m,n 结束的两个不相交路径
于是可以想象两个人
A
,
B
A,B
A,B 从
1
,
1
1,1
1,1 出发
d
p
[
i
]
[
j
]
[
k
]
[
l
]
dp[i][j][k][l]
dp[i][j][k][l] 定为
A
A
A 走到
i
,
j
i,j
i,j,
B
B
B 走到
k
,
l
k,l
k,l 的最大好感度和
这道题目的一个关键就是要保证两人同步移动,然后使得二人横纵坐标之和相同(即在同一对角线
然后
O
(
n
4
)
O(n^4)
O(n4) 进行转移就好了
code:
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 55;
int a[maxn][maxn];
int dp[maxn][maxn][maxn][maxn];
int main()
{
int n, m;
cin >> n >> m;
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
cin >> a[i][j];
for(int i = 1; i <= n; ++i)
for(int j = 1; j <= m; ++j)
for(int k = 1; k < i; ++k)
{
int l = i + j - k;
if(l > m) continue;
dp[i][j][k][l] = max({dp[i-1][j][k-1][l], dp[i-1][j][k][l-1], dp[i][j-1][k-1][l], dp[i][j-1][k][l-1]})+ a[i][j] + a[k][l];
}
cout << dp[n][m-1][n-1][m] << 'n';
return 0;
}
三维解法:
题解
code:
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<cstring>
using namespace std;
const int maxn=60;
int a[maxn][maxn];
int F[2*maxn][maxn][maxn];// F[k][i][j] 代表步数为 k 时, 一个人到第 i 列,另一个人到第 j 列时的最大价值
int main()
{
int m, n;
scanf("%d %d",&m, &n);
for(int i = 1;i <= m; i++)
for(int j = 1;j <= n; j++)
scanf("%d", &a[i][j]);
//F[sum][i][j]=max{F[sum-1][i][j]...
for(int k = 3; k < m + n; k++)
for(int i = 1;i < n; i++)
for(int j = i + 1; j <= n; j++)
{
if(k - i < 1 || k - j < 1) continue;
int s = max({F[k][i][j], F[k-1][i][j], F[k-1][i-1][j], F[k-1][i][j-1], F[k-1][i-1][j-1]});
F[k][i][j] = s + a[k-i][i] + a[k-j][j];//该点的值为最大的前一个值与当前F[k][i][j]表示两点的值的和。
}
printf("%d",F[m+n-1][n-1][n]);//因为i永远小于j,所以右下角的点不会求到,
//但是到右下角只有一种情况,就是在右下角的上面和右下角的左边,直接输出就好了。
return 0;
}
背包dp
P1064-NOIP2006 提高组-金明的预算方案
P1064-NOIP2006 提高组-金明的预算方案
思路:
感觉写起来好麻烦的一道题,主要就是处理好物品
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
ll f[maxn];
struct node{
int v[5], w[5], cnt;
}a[maxn];
void work()
{
cin >> n >> m;
for(int i = 1; i <= m; ++i)
{
int v, p, q;cin >> v >> p >> q;
if(!q){
a[i].v[1] = v;
a[i].w[1] = v * p;
a[i].cnt++;
}
else
{
if(a[q].cnt == 1){
a[q].cnt++;
a[q].v[2] = a[q].v[1] + v;
a[q].w[2] = a[q].w[1] + v * p;
}
else
{
a[q].cnt = 4;
a[q].v[3] = a[q].v[1] + v;
a[q].w[3] = a[q].w[1] + v * p;
a[q].v[4] = a[q].v[2] + v;
a[q].w[4] = a[q].w[2] + v * p;
}
}
}
for(int i = 1; i <= m; ++i)
{
for(int j = n; j >= 0; --j)
{
for(int k = 1; k <= a[i].cnt; ++k)
{
if(j >= a[i].v[k])
f[j] = max(f[j], f[j - a[i].v[k]] + a[i].w[k]);
}
}
}
cout << f[n];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P1586 四方定理
P1586 四方定理
题意:
给定一个数
n
n
n,把
n
n
n 分解成不超过
4
4
4 个正整数的平方和,方案有多少种
思路:
把
n
n
n 看成背包容量,那么每个数的平方和就是物品的体积和价值,同种物品可以重复选,就是完全背包求方案数,类似于硬币凑钱方案数,但是这道题目限制了使用物品的数量,不超过
4
4
4 个,因此需要加一维维护。
其次完全背包的循环顺序是毫无疑问的,问题在于枚举物品个数的
f
o
r
for
for 位置,这个
f
o
r
for
for 可以放在枚举背包容量之前,也可以放在枚举背包容量之前。
就是我们要枚举某个数的使用个数,而不是去枚举要使用几个数,而枚举某个数的使用个数显然要放到枚举使用哪个数里边
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e5 + 9;
const int mod = 1e6 + 7;
ll n, m;
ll f[5][maxn];// f[i][j]表示用i个数的平方和表示出j
void init(int n)
{
f[0][0] = 1;
// 错误做法
/*for(int i = 1; i <= 4; ++i)
for(int k = 1; k * k <= n; ++k)
for(int j = k * k; j <= n; ++j)
f[i][j] += f[i-1][j - k * k];*/
// 正确做法
for(int k = 1; k * k <= n; ++k)
for(int i = 1; i <= 4; ++i)// // 枚举物品 k*k 的使用个数
for(int j = k * k; j <= n; ++j)
f[i][j] += f[i-1][j - k * k];
// or
/*for(int k = 1; k * k <= n; ++k)
for(int j = k * k; j <= n; ++j)
for(int i = 1; i <= 4; ++i)// 枚举物品 k*k 的使用个数
f[i][j] += f[i - 1][j - k * k];*/
}
void work()
{
cin >> n;
ll ans = 0;
for(int i = 1; i <= 4; ++i) ans += f[i][n];
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(0);
init(32768);
int TT;cin>>TT;while(TT--)
work();
return 0;
}
P5020 [NOIP2018 提高组] 货币系统
P5020 [NOIP2018 提高组] 货币系统
题意:
给定
n
n
n 种面值的货币,
a
i
a_i
ai 表示货币面值,定义货币系统为
(
n
,
a
)
(n,a)
(n,a),两个货币系统
(
n
,
a
)
,
(
m
,
b
)
(n,a),(m,b)
(n,a),(m,b) 是等价,如果满足两个货币系统能表示出的数完全相同,即能表示出来的两个都能表示出来,不能表示的两个都不能表示,求一个与所给货币系统等价的货币系统,满足
m
m
m 最小,输出最小
m
m
m。
思路:
可以猜一手结论,货币系统
b
b
b 的所有面值都在
a
a
a 中出现过,我们只需要去找,用
a
a
a 中的某些面值表示另一些
a
a
a 中面值,然后这些能被表示出的面值就没必要存在了,这样把最多能删除的面值删去,剩下的就是
m
m
m。
把数组
a
a
a 从小到大排序,然后背包确定能被表示出的面值
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
int a[maxn];
bool f[maxn];
void work()
{
memset(f, 0, sizeof(f));
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i];
sort(a + 1, a + 1 + n);
int ans = n;
f[0] = 1;
for(int i = 1; i <= n; ++i)
{
if(f[a[i]]) --ans;
else
{
for(int j = a[i]; j <= a[n]; ++j)// 完全背包
f[j] = f[j] | f[j - a[i]];
}
}
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(0);
int TT;cin>>TT;while(TT--)
work();
return 0;
}
CF543A Writing Code
CF543A Writing Code
题意:
n
n
n 个程序员写
m
m
m 行代码,每个人可以写任意行代码,每个程序员一行代码会出现
a
i
a_i
ai 的
b
u
g
s
bugs
bugs,要求
b
u
g
s
bugs
bugs 数小于
k
k
k 的安排方案数。
思路:
n
3
n^3
n3 转移
类似于完全背包的问题
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 5e2 + 9;
int mod;
ll n, m, k;
int a[maxn];
int f[2][maxn][maxn];// f[i][j] 前 i个
写 j 行
d 个 bugs
void work()
{
cin >> n >> m >> k >> mod;
for(int i = 1; i <= n; ++i) cin >> a[i];
f[0][0][0] = 1;
int op = 1;
for(int i = 1; i <= n; ++i, op ^= 1)
for(int j = 0; j <= m; ++j)
for(int d = 0; d <= k; ++d)
{
f[op][j][d] = f[op ^ 1][j][d];
if(j > 0 && d >= a[i])
f[op][j][d] = (f[op][j][d] + f[op][j-1][d - a[i]]) % mod;
}
int ans = 0;
for(int i = 0; i <= k; ++i)
ans = (ans + f[op ^ 1][m][i]) % mod;
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
区间dp
P1880 [NOI1995] 石子合并
P1880 [NOI1995] 石子合并
题意:
n
n
n 个石子堆围成圆,每次只能选择相邻的两个石子堆合并,并将合并后的数计为这次的得分,求最后只剩下一个石子堆时的最大得分
思路:
经典区间dp
主要最后要枚举一下区间,求答案
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
using namespace std;
const int maxn = 1e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
int a[maxn], sum[maxn];
ll f[200][200];
ll ff[200][200];
void work()
{
memset(ff, 0x3f, sizeof(ff));
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i], a[n + i] = a[i];
for(int i = 1; i <= n * 2; ++i)
sum[i] = sum[i-1] + a[i], ff[i][i] = 0;
for(int len = 2; len <= n; ++len)
for(int l = 1; l + len - 1 <= 2 * n; ++l)
{
int r = l + len - 1;
for(int k = l; k < r; ++k)
f[l][r] = max(f[l][r], f[l][k] + f[k + 1][r] + sum[r] - sum[l-1]),
ff[l][r] = min(ff[l][r], ff[l][k] + ff[k + 1][r] + sum[r] - sum[l-1]);
}
ll Max = 0, Min = 2e9;
for(int i = 1; i <= n; ++i)
Max = max(Max, f[i][i+n-1]),
Min = min(Min, ff[i][i+n-1]);
cout << Min << endl << Max;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
混入一道牛客题—跳跳跳
混入一道牛客题—跳跳跳
题意:
n
n
n 个格子呈环形分布,顺时针方向标号
1
−
n
1-n
1−n,其中
1
1
1 和
n
n
n 相邻,每个格子上有一个正整数
a
i
a_i
ai,第
i
i
i 次跳跃,可以选择左边或者右边最近且尚未被跳跃过的位置进行一次跳跃,并获得
i
×
a
[
p
]
itimes a[p]
i×a[p] 的得分,其中
p
p
p 为第
i
i
i 次跳跃的位置
思路:
一开始考虑的是
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示以
i
i
i 为起点跳
j
j
j 步获得最大的价值,然后发现不能转移,又开了两个两维的
f
[
i
]
[
j
]
[
2
]
[
2
]
f[i][j][2][2]
f[i][j][2][2] 来表示往左右跳的次数和获得的价值,然后感觉好麻烦。
这题正解是裸的区间dp,先变环成链,然后考虑
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示左端点为
i
i
i,右端点为
j
j
j 时获得的最大价值。
仔细想想这个过程就和石子合并几乎一样
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
using namespace std;
const int maxn = 2e3 + 9;
const int mod = 1e9 + 7;
ll n, m;
ll a[maxn << 1];
ll f[maxn << 1][maxn << 1];
void work()
{
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i], a[n + i] = a[i];
for(int len = 1; len <= n; ++len)
for(int l = 1; l + len - 1 <= 2 * n; ++l)
{
int r = l + len - 1;
f[l][r] = max(f[l + 1][r] + len * a[l], f[l][r - 1] + len * a[r]);
}
ll ans = 0;
for(int i = 1; i <= n + 1; ++i)
ans = max(ans, f[i][i + n - 1]);
cout << ans << endl;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P2426 删数
P2426 删数
题意:
给定
n
n
n 个不同的正整数,每次可以在序列最左边或最右边选一段序列删除,删除获得的价值为
a
b
s
(
a
i
−
a
j
)
∗
(
j
−
i
+
1
)
abs(a_i-a_j)*(j-i+1)
abs(ai−aj)∗(j−i+1),其中
i
,
j
i,j
i,j 分别为序列的起点和终点,如果只删除一个数,价值即为删除的这个数
a
i
a_i
ai,求删光序列后获得的最大价值。
思路:
考虑
f
[
i
]
[
j
]
f[i][j]
f[i][j] 表示把
[
i
,
j
]
[i,j]
[i,j] 子段删除时获得的最大价值。
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e2 + 9;
const int mod = 1e9 + 7;
ll n, m;
int a[maxn], f[maxn][maxn];
int g(int i, int j){
if(i == j) return a[i];
return (j - i + 1) * abs(a[i] - a[j]);
}
void work()
{
cin >> n;
for(int i = 1; i <= n; ++i) cin >> a[i];
for(int len = 1; len <= n; ++len)
for(int i = 1; i + len - 1 <= n; ++i)
{
int j = i + len - 1;
f[i][j] = g(i, j);
for(int k = i; k <= j; ++k)
f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j]);
}
cout << f[1][n];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
优化成一维的解法:
题意唯一有干扰的是从后面删数的这个选择,但是稍微一想就知道每次从前面删数一直删到最后,最优解是不变的,最后留下的也是我们要从后面删去的区间。
考虑
f
[
i
]
f[i]
f[i] 表示删除前
i
i
i 个数时产生的最大价值
状态转移方程
f
[
i
]
=
m
a
x
(
f
[
i
]
,
f
[
j
]
+
g
(
j
+
1
,
i
)
(
1
<
=
j
<
i
)
f[i]=max(f[i],f[j]+g(j+1,i)(1<=j<i)
f[i]=max(f[i],f[j]+g(j+1,i)(1<=j<i)
其中,
g
g
g 是一个估价函数(默认左端点小于等于右端点),它代表删除一个区间的价值。首先,前
i
i
i 个数可以一次删完,也可以分多次删。如果一次删,那么直接就是
v
a
l
(
1
,
i
)
val(1,i)
val(1,i)。如果要多次删,我们就要找到对于第
i
i
i 个数,它和哪些数一起删除是最优的。这时枚举
j
j
j,用来找第
i
i
i 个数和哪些数一起删除。
d
p
[
j
]
dp[j]
dp[j] 代表和
i
i
i 无关的部分删除最大价值(这里体现了
d
p
dp
dp 的转移性),而
v
a
l
(
j
+
1
,
i
)
val(j+1,i)
val(j+1,i) 则是和
i
i
i 一起删除的价值。
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e2 + 9;
const int mod = 1e9 + 7;
ll n, m;
int a[maxn], f[maxn];
int g(int i, int j){
if(i == j) return a[i];
return (j - i + 1) * abs(a[i] - a[j]);
}
void work()
{
cin >> n;
for(int i = 1; i <= n; ++i)
{
cin >> a[i];
f[i] = g(1, i);
for(int j = 1; j < i; ++j)
f[i] = max(f[i], f[j] + g(j + 1, i));
}
cout << f[n];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P4290 [HAOI2008]玩具取名(普及+/提高
P4290 [HAOI2008]玩具取名
题意:
某人有一套玩具,并想法给玩具命名。首先他选择
W
I
N
G
WING
WING 四个字母中的任意一个字母作为玩具的基本名字。然后他会根据自己的喜好,将名字中任意一个字母用
W
I
N
G
WING
WING 中任意两个字母代替,使得自己的名字能够扩充得很长。给定一个字符串
s
s
s,输出最初选定的字符可能是哪些,按照
W
I
N
G
WING
WING 的顺序输出,不存在则输出 “The name is wrong!”。
一行四个整数代表每个字母可由两个字母替换的方式的个数,然后
w
w
w 行,每行两个字母,代表
W
W
W 可由哪两个字母替换…
思路:
用数字代替字母,
W
I
N
G
WING
WING 依次映射成
1
,
2
,
3
,
4
1,2,3,4
1,2,3,4
考虑
f
[
l
]
[
r
]
[
k
]
f[l][r][k]
f[l][r][k] 表示区间
[
l
,
r
]
[l,r]
[l,r] 能由
k
∈
(
1
,
2
,
3
,
4
)
k in(1,2,3,4)
k∈(1,2,3,4) 变形过来
枚举
k
k
k 和 它所能替换的组合,转移即可
注意需要初始化区间长度为
1
1
1 的
f
f
f 数组,区间
d
p
dp
dp 的题目最好是先初始化长度为
1
1
1 的再转移其他的
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e2 + 9;
const int mod = 1e9 + 7;
ll n, m;
int num[5];
map <char,int> ma;
int a[5][20][3];
bool f[maxn][maxn][5];
char s[maxn];
char ans[10] = {"0WING"};
void work()
{
for(int i = 1; i <= 4; ++i) ma[ans[i]] = i;
for(int i = 1; i <= 4; ++i) cin >> num[i];
for(int i = 1; i <= 4; ++i)
for(int j = 1; j <= num[i]; ++j)
{
char x, y;cin >> x >> y;
a[i][j][1] = ma[x];
a[i][j][2] = ma[y];
}
cin >> (s + 1);
n = strlen(s + 1);
for(int i = 1; i <= n; ++i) f[i][i][ma[s[i]]] = 1;
for(int len = 2; len <= n; ++len)//枚举区间长度
for(int l = 1, r = l + len - 1; l + len - 1 <= n; ++l, ++r)//枚举区间的左端点和区间的右端点
for(int k = l; k < r; ++k)//总的区间拼出来的数是由哪两个分区间组合出来的,也就是两个区间的交界处
for(int i = 1; i <= 4; ++i)//枚举WING
for(int j = 1; j <= num[i]; ++j)//WING能够被组合出来的方案
if(f[l][k][a[i][j][1]] && f[k+1][r][a[i][j][2]])
f[l][r][i] = 1;
vector <char> v;
for(int i = 1; i <= 4; ++i) if(f[1][n][i])
v.push_back(ans[i]);
if(v.empty()) cout << "The name is wrong!";
else
for(int i = 0; i < v.size(); ++i)
cout << v[i];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
树形dp
P1352 没有上司的舞会
P1352 没有上司的舞会
题意:
给定一棵树
n
n
n 个节点,每个节点有一个权值
a
i
a_i
ai,如果一个选定节点
i
i
i ,那么一定没有选择它的父节点,且不能选择它的子节点,求选出一些节点使得最大权值和。
思路:
树形dp即可
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e4 + 9;
const int mod = 1e9 + 7;
ll n, m;
int a[maxn];
bool vis[maxn];
ll f[maxn][2];
vector <int> e[maxn];
void dfs(int x){
f[x][0] = 0;
f[x][1] = a[x];
for(int i = 0; i < e[x].size(); ++i){
int d = e[x][i];
if(vis[d]) continue;
vis[d] = 1;
dfs(d);
f[x][0] += max(f[d][0], f[d][1]);
f[x][1] += f[d][0];
}
}
void work()
{
cin >> n;
for(int i = 1; i <= n; ++i)cin >> a[i];
for(int i = 1; i < n; ++i){
int x, y;cin >> x >> y;
e[x].push_back(y);
e[y].push_back(x);
}
vis[1] = 1;
dfs(1);
cout << max(f[1][0], f[1][1]);
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
P1040 [NOIP2003 提高组] 加分二叉树
P1040 [NOIP2003 提高组] 加分二叉树
题意:
设一个
n
n
n 个节点的二叉树
tree
text{tree}
tree 的中序遍历为
(
1
,
2
,
3
,
…
,
n
)
(1,2,3,…,n)
(1,2,3,…,n),其中数字
1
,
2
,
3
,
…
,
n
1,2,3,…,n
1,2,3,…,n 为节点编号。每个节点都有一个分数(均为正整数),记第
i
i
i 个节点的分数为
a
i
a_i
ai,
tree
text{tree}
tree 及它的每个子树都有一个加分,任一棵子树
subtree
text{subtree}
subtree(也包含
tree
text{tree}
tree 本身)的加分计算方法如下:
s
u
b
t
r
e
e
subtree
subtree 的左子树的加分
×
s
u
b
t
r
e
e
times subtree
×subtree 的右子树的加分
+
s
u
b
t
r
e
e
+ subtree
+subtree 的根的分数。
若某个子树为空,规定其加分为
1
1
1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为
(
1
,
2
,
3
,
…
,
n
)
(1,2,3,…,n)
(1,2,3,…,n) 且加分最高的二叉树
t
r
e
e
tree
tree。要求输出
t
r
e
e
tree
tree 的最高加分。
t
r
e
e
tree
tree 的前序遍历。
思路:
code:
记忆化搜索
P3183 [HAOI2016]食物链
P3183 [HAOI2016]食物链
题意:
给定一张有向图,
n
n
n 个点,
m
m
m 条边,求有多少条食物链
思路:
建图记忆化搜索即可
code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
vector <int> v[maxn];
ll f[maxn];
bool in[maxn];
int dfs(int x, int k){
if(v[x].empty())
{
if(k == 1) return 0;
else return 1;
}
if(f[x]) return f[x];
ll ans = 0;
for(int i = 0; i < v[x].size(); ++i)
{
ans += dfs(v[x][i], k + 1);
}
f[x] = ans;
return ans;
}
void work()
{
cin >> n >> m;
for(int i = 1; i <= m; ++i){
int x, y;cin >> x >> y;
v[x].push_back(y);
in[y] = 1;
}
ll ans = 0;
for(int i = 1; i <= n; ++i) if(!in[i])// 起点,生产者
ans += dfs(i, 1);
cout << ans;
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
期望dp
P4316 绿豆蛙的归宿
P4316 绿豆蛙的归宿
题意:
n
n
n 个点
m
m
m 条边的无向图,起点未
1
1
1,终点为
n
n
n,每条边都有一个长度。若一个节点有
k
k
k 个节点,那么走每一个边的概率都为
1
k
frac{1}{k}
k1。求
1
1
1 到
n
n
n 的总路长期望是多少。
思路:
参考大佬博客
期望dp经典模型
一般将问题直接作为
d
p
dp
dp 的状态是最好的。
例如本题设状态
f
[
x
]
f[x]
f[x] 表示点
x
x
x 到终点
n
n
n 的期望路径总长
有时期望
d
p
dp
dp 需以最终状态为初始状态转移,即逆推。
一般来说,初始状态确定时可用顺推,终止状态确定时可用逆推。
本题中显然
f
[
n
]
=
0
f[n]=0
f[n]=0,适合逆推的套路
假设一条边
x
⟶
y
xlongrightarrow y
x⟶y
那么有
f
[
x
]
=
1
d
e
g
[
x
]
∗
∑
i
=
1
d
e
g
[
x
]
(
f
[
y
]
+
w
[
x
⟶
y
]
)
f[x]=frac{1}{deg[x]}*sum_{i=1}^{deg[x]} (f[y]+w[xlongrightarrow y])
f[x]=deg[x]1∗∑i=1deg[x](f[y]+w[x⟶y]),
d
e
g
[
x
]
deg[x]
deg[x] 就是表示
x
x
x 的出度,
1
d
e
g
[
x
]
frac{1}{deg[x]}
deg[x]1 就是概率
反向建图拓扑排序一遍递推
f
[
x
]
f[x]
f[x] 即可
正推思路:
f
[
i
]
f[i]
f[i] 表示从
1
1
1 到
i
i
i 的期望,
g
[
i
]
g[i]
g[i] 表示从
1
1
1 到
i
i
i 的概率,
d
e
g
[
x
]
deg[x]
deg[x] 是
x
x
x 的出度,方程的转移:对于一条从
x
x
x 到
y
y
y 的边
设
f
[
y
]
=
1
d
e
g
[
x
]
×
∑
i
=
1
i
n
[
y
]
(
f
[
x
]
+
e
[
i
]
×
g
[
x
]
)
f[y]= frac{1}{deg[x]} times sum_{i=1}^{in[y]}(f[x]+e[i]times g[x])
f[y]=deg[x]1×∑i=1in[y](f[x]+e[i]×g[x])
到达
x
x
x 点路长的期望加上这一步的贡献,乘以可以转移过来的概率,累加所有可以转移到
y
y
y 点的边即可
这一步的贡献是边长乘以有机会从
x
x
x 出发选这条边的概率
逆推code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
queue <int> q;
struct Edge{
int next, to, w;
}e[maxn];
int head[maxn], cnt, in[maxn], deg[maxn];
void add(int x, int y, int z){
e[++cnt].w = z;
e[cnt].to = y;
e[cnt].next = head[x];
head[x] = cnt;
}
double f[maxn];
void topsort()
{
q.push(n);
while(!q.empty())
{
int x = q.front();q.pop();
for(int i = head[x]; i; i = e[i].next)
{
int y = e[i].to;
in[y]--;
if(!in[y])q.push(y);
f[y] += (1.0 * f[x] + e[i].w) / deg[y];
}
}
}
void work()
{
cin >> n >> m;
for(int i = 1; i <= m; ++i){
int x, y, z;cin >> x >> y >> z;
add(y, x, z);
in[x]++,deg[x]++;
}
topsort();
cout << fixed << setprecision(2) << f[1];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
正推code:
#include<bits/stdc++.h>
#define endl 'n'
#define ll long long
#define ull unsigned long long
#define ld long double
#define all(x) x.begin(), x.end()
#define eps 1e-6
using namespace std;
const int maxn = 2e5 + 9;
const int mod = 1e9 + 7;
ll n, m;
queue <int> q;
struct Edge{
int next, to, w;
}e[maxn];
int head[maxn], cnt, in[maxn], deg[maxn];
void add(int x, int y, int z){
e[++cnt].w = z;
e[cnt].to = y;
e[cnt].next = head[x];
head[x] = cnt;
}
double f[maxn], g[maxn];
void topsort()
{
for(int i = 1; i <= n; ++i) if(!in[i]) q.push(i);
g[1] = 1;
while(!q.empty())
{
int x = q.front();q.pop();
for(int i = head[x]; i; i = e[i].next)
{
int y = e[i].to;
in[y]--;
if(!in[y])q.push(y);
f[y] += (1.0 * f[x] + e[i].w * g[x]) / deg[x];
g[y] += g[x] / (1.0 * deg[x]);
}
}
}
void work()
{
cin >> n >> m;
for(int i = 1; i <= m; ++i){
int x, y, z;cin >> x >> y >> z;
add(x, y, z);
in[y]++,deg[x]++;
}
topsort();
cout << fixed << setprecision(2) << f[n];
}
int main()
{
ios::sync_with_stdio(0);
// int TT;cin>>TT;while(TT--)
work();
return 0;
}
提高
待补…
P5569 [SDOI2008] 石子合并
石子合并的加强版(Hard
题意:
一行
n
n
n 堆石子,每次只能选相邻两堆进行合并,合并后的石子数计为这次的得分,求最后只由一堆石子时的最大得分。
树形dp
P2279 [HNOI2003]消防局的设立
最后
以上就是包容冷风为你收集整理的初级dp刷题记录普及~普及/提高线性dp矩阵dp背包dp区间dp树形dp记忆化搜索期望dp提高的全部内容,希望文章能够帮你解决初级dp刷题记录普及~普及/提高线性dp矩阵dp背包dp区间dp树形dp记忆化搜索期望dp提高所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复