EASY
Making the Grade
给一个长为n(n<2000)的序列A,将其变成一个非增或非减的序列B的绝对值改变量。
key:B中所有的数都是A中的数。因为若
a
l
<
b
<
a
r
a_l<b<a_r
al<b<ar时,总能令b变成其中一个值。则枚举
b
i
b_i
bi为每个A中的值即可。dp[i][j]表示
b
i
≤
u
n
q
j
b_i≤unq_j
bi≤unqj时,目前的最小代价。
// #include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 2e3+4;
int n,m;
int a[N],unq[N];
int dp[N][N];
int solve(){
for(int i=0;i<m;++i){
dp[0][i]=abs(a[0]-unq[i]);
if(i)dp[0][i]=min(dp[0][i-1],dp[0][i]);
}
for(int i=1;i<n;++i){
for(int j=0;j<m;++j){
dp[i][j]=dp[i-1][j]+abs(a[i]-unq[j]);
if(j)dp[i][j]=min(dp[i][j-1],dp[i][j]);
}
}
return dp[n-1][m-1];
}
int main(){
cin>>n;
for(int i=0;i<n;++i){
cin>>a[i];
unq[i]=a[i];
}
sort(unq,unq+n);
m=unique(unq,unq+n)-unq;
int ans=solve();
reverse(a,a+n);
ans=min(ans,solve());
cout<<ans<<endl;
}
Milking Time
题意:给出
m
m
m个时间区间,完成每个区间的事可以有一定的贡献。每做完一件事要缓
r
r
r分钟。问最多有多少贡献。
思路:由于时间范围为1e6。我和大部分题解双重循环区间不同。我遍历一遍时间。dp[i]表示前i分钟做的最大贡献。缓r分钟解决方式是每个区间的结束时间
+
r
+r
+r。天坑:一个区间的ending_time可以开始另一件事了。
// #include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
typedef long long ll;
const int N = 1e6+4,M=1e3+5;
struct Node{
int l,v,nex;
}e[M];
int n,m,r;
ll dp[N+N];
unordered_map<int,int> head;
void add(int a,int b,int c){
static int cnt=0;
e[++cnt] = Node{a,c,head.count(b) ? head[b] : 0};
head[b] = cnt;
}
int main(){
cin>>n>>m>>r;
for(int a,b,c,i=1;i<=m;++i){
cin>>a>>b>>c;
if(b<=n)add(a,b+r,c);
}
for(int i=1;i<=n+r;++i){
dp[i] = dp[i-1];
if(head.count(i))for(int j=head[i];j;j=e[j].nex){
dp[i]=max(dp[e[j].l]+e[j].v,dp[i]);
}
}
cout<<dp[n+r]<<endl;
}
Phalanx
给一个 n × n n×n n×n(n<1000)字符矩阵。求沿对角线丿对称的最大子矩阵。dp[siz][i][j]表示右下角坐标为(i,j),大小为siz+1的矩阵是否是对称矩阵。
// #include<bits/stdc++.h>
#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
#define debug(_x) cout<<#_x<<": "<<_x<<endl;
typedef long long ll;
const int N = 1e6+4,M=1e3+5;
char a[M][M];
bool dp[2][M][M];
bool (*f1)[M],(*f2)[M];
int n;
bool solve(int siz){
bool ans=0;
for(int i=siz;i<n;++i)
for(int j=siz;j<n;++j){
ans |= f1[i][j] = (f2[i-1][j-1] && f2[i][j] && a[i-siz][j]==a[i][j-siz]);
}
return ans;
}
int main(){
while(cin>>n&&n){
for(int i=0;i<n;++i){
cin>>a[i];
reverse(a[i],a[i]+n);
}
for(int i=0;i<n;++i)
for(int j=0;j<n;++j)
dp[0][i][j]=1;
f2=dp[0];
f1=dp[1];
int ans=0;
for(int i=1;i<n;++i,swap(f1,f2))
if(solve(i))ans=i;
cout<<ans+1<<endl;
}
}
CF1114D. Flood Fill
题意:给
n
(
n
≤
5000
)
n(n\le 5000)
n(n≤5000)个数,选择一个起始点,可以让颜色相同的整段变色。问最少的变色次数,让所有颜色相同。
思路:假设选好了起始点
i
i
i,则如何求变色次数?显然答案是
n
−
1
−
f
[
i
−
1
]
[
i
+
1
]
n-1-f[i-1][i+1]
n−1−f[i−1][i+1],其中
f
[
i
−
1
]
[
i
+
1
]
f[i-1][i+1]
f[i−1][i+1]代表
c
1
⋯
c
i
−
1
c_1\cdots c_{i-1}
c1⋯ci−1与
c
n
⋯
c
i
+
1
c_n\cdots c_{i+1}
cn⋯ci+1的最长公共子序列。则直接求原数组和逆序数组的最长公共子序列即可。
O
(
n
2
)
O(n^2)
O(n2)
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
using namespace std;
const int N = 5005;
int f[N][N], c[N];
int main(){
IOS;
int n;
cin >> n;
for(int i = 1; i <= n; ++i){
cin >> c[i];
}
n = unique(c + 1, c + n + 1) - c - 1;
for(int i = 1; i <= n; ++i){
for(int j = n; j > 0; --j){
f[i][j] = max({f[i-1][j], f[i][j+1], f[i-1][j+1] + (c[i] == c[j])});
}
}
int ans = 0;
for(int i = 1; i <= n; ++i)
ans = max(ans, f[i-1][i+1]);
cout << n - 1 - ans << endl;
}
MID
CF1458B. Glass Half Spilled
题意:有
n
(
n
≤
100
)
n(n\le 100)
n(n≤100)个玻璃杯,每个玻璃杯的容量是
a
i
a_i
ai,已有水量是
b
i
b_i
bi,倒水有一半的损失。挑
k
k
k个杯子装水,求最大的装水量。
第一个思路:dp[i][k][j]
表示
1...
i
1...i
1...i个杯子,挑
k
k
k个,剩余容量为j
的最大装水量。但是这个状态转移具有后效性,可能前面后面要的杯子,用前面的杯子里的水。
题解:dp[i][k][j]
表示
1...
i
1...i
1...i个杯子,挑
k
k
k个,总容量为
j
j
j的最大水量(未考虑是否装的下,只考虑有多少水)。状态转移如下。则取
k
k
k杯的答案就是max(j, dp[i][k][j])
。时间复杂度
O
(
n
3
⋅
max
a
)
O(n^3\cdot \max a)
O(n3⋅maxa),达到1e8。
dp[i][k][j]=max(dp[i-1][k][j] + b[i]/2, dp[i-1][k-1][j-a[i]] + b[i]);
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
using namespace std;
using ll = long long;
const int N = 1e2 + 5;
int dp[N][N][10005];
int a[N], b[N], ans[N];
int main(){
IOS;
int n, CAP = 0;
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> a[i] >> b[i];
memset(dp, 0xf0, sizeof(dp));
dp[0][0][0] = 0;
for(int i = 1; i <= n; ++i){
CAP += a[i];
for(int k = 0; k <= n; ++k){
for(int j = 0; j <= CAP; ++j){
int &now = dp[i][k][j];
now = dp[i-1][k][j] + b[i];
if(k && j - a[i] >= 0)now = max(now, dp[i-1][k-1][j-a[i]] + 2 * b[i]);
if(ans[k] < min(now, 2 * j))ans[k] = min(now, 2 * j);
}
}
}
for(int k = 1; k <= n; ++k)
cout << fixed << setprecision(6) << ans[k] / 2.0 << " ";
cout << endl;
}
CF1433F. Zero Remainder Sum
题意:给定
n
×
m
n×m
n×m矩阵,每行选最多
m
/
2
m/2
m/2个。求能被
m
o
d
mod
mod整除的最大和。(所有数小于70)
思路:对于第
i
i
i行来说,
f
[
j
]
[
h
]
[
k
]
f[j][h][k]
f[j][h][k]表示
1...
j
1...j
1...j取
h
h
h个,对
m
o
d
mod
mod取余为
k
k
k的最大和。易得转移公式
f
[
j
]
[
h
]
[
k
]
=
max
(
f
[
j
−
1
]
[
h
]
[
k
]
,
f
[
j
−
1
]
[
h
−
1
]
[
(
m
o
d
+
k
−
a
[
i
]
[
j
]
%
m
o
d
)
%
m
o
d
]
+
a
[
i
]
[
j
]
)
f[j][h][k]=\max(f[j-1][h][k],\\ \ \ \ \ \ \ \ \ f[j-1][h-1][(mod+k-a[i][j]\%mod)\%mod]+a[i][j])
f[j][h][k]=max(f[j−1][h][k], f[j−1][h−1][(mod+k−a[i][j]%mod)%mod]+a[i][j])
最后,
s
u
m
[
i
]
[
k
]
=
max
j
,
h
f
[
j
]
[
h
]
[
k
]
sum[i][k]=\max_{j,h}{f[j][h][k]}
sum[i][k]=maxj,hf[j][h][k],再对行进行dp
d
p
[
i
]
[
k
]
=
max
0
≤
l
a
<
m
o
d
(
,
d
p
[
i
−
1
]
[
l
a
]
+
s
u
m
[
i
]
[
(
m
o
d
+
k
−
l
a
)
dp[i][k] = \max_{0\le la<mod}(, dp[i-1][la] + sum[i][(mod+k-la)%mod])
dp[i][k]=0≤la<modmax(,dp[i−1][la]+sum[i][(mod+k−la)
最终答案就是
d
p
[
n
]
[
0
]
dp[n][0]
dp[n][0],时间复杂度为
O
(
n
⋅
m
2
⋅
m
o
d
)
O(n\cdot m^2 \cdot mod)
O(n⋅m2⋅mod)
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
using namespace std;
using ll = long long;
const int N = 73;
int f[N][N/2][N], sum[N][N], dp[N][N];
int main(){
IOS;
int n, m, mod;
cin >> n >> m >> mod;
memset(dp, 0xf0, sizeof(dp));
memset(sum, 0xf0, sizeof(sum));
dp[0][0] = 0;
for(int now, i = 1; i <= n; ++i){
memset(f[0], 0xf0, sizeof(f[0]));
f[0][0][0] = 0;
sum[i][0] = 0;
for(int j = 1; j <= m; ++j){
cin >> now;
for(int h = 0; h <= m / 2; ++h){
for(int k = 0; k < mod; ++k){
f[j][h][k] = f[j-1][h][k];
if(h && f[j][h][k] < f[j-1][h-1][(mod+k-now%mod) % mod] + now)
f[j][h][k] = f[j-1][h-1][(mod+k-now%mod) % mod] + now;
if(sum[i][k] < f[j][h][k])
sum[i][k] = f[j][h][k];
}
}
}
for(int k = 0; k < mod; ++k){
for(int la = 0; la < mod; ++la){
dp[i][k] = max(dp[i][k], dp[i-1][la] + sum[i][(mod+k-la)%mod]);
}
}
}
cout << dp[n][0] << endl;
}
CF1312E. Array Shrinking
题意:给
n
(
n
≤
500
)
n(n\le 500)
n(n≤500)个数,若相邻两个数相同(
a
i
=
=
a
i
+
1
a_i==a_{i+1}
ai==ai+1)则可以用
a
i
+
1
a_i+1
ai+1代替。问最后合并出最少的项数。
思路:
- 先考虑只有相邻的多个数合并成一个数的所有情况。当可以合并成一个数时,这个数必然是唯一的。即 n u m [ i ] [ j ] num[i][j] num[i][j]表示把 a i ⋯ a j a_i\cdots a_j ai⋯aj合并成一个数,那个数的值,如果不能的话, n u m [ i ] [ j ] = 0 num[i][j]=0 num[i][j]=0.就是一个简单的区间DP
- 再考虑最少用多少个数即
n
u
m
num
num区间来得到整个
1
⋯
n
1\cdots n
1⋯n区间。设
f
[
i
]
f[i]
f[i]表示
1
⋯
i
1\cdots i
1⋯i区间最少有多少个数。
f [ i ] = min 0 ≤ j < i f [ j ] + 1 ( i f n u m [ j + 1 ] [ i ] ≠ 0 ) f[i] = \min_{0\le j<i}{f[j] + 1}\ \ \ \ (if\ num[j+1][i]\not=0) f[i]=0≤j<iminf[j]+1 (if num[j+1][i]=0)
#include<bits/stdc++.h>
#define IOS ios::sync_with_stdio(false);cin.tie(0), cout.tie(0);
using namespace std;
using ll = long long;
const int N = 505;
int num[N][N], f[N];
int main(){
IOS;
int n;
cin >> n;
for(int i = 1; i <= n; ++i)
cin >> num[i][i];
for(int len = 2; len <= n; ++len){
for(int i = 1, j = i + len - 1; j <= n; ++i, ++j){
for(int k = i; k < j; ++k){
if(num[i][k] == num[k+1][j] && num[i][k])
num[i][j] = num[i][k] + 1;
}
}
}
f[0] = 0;
for(int i = 1; i <= n; ++i){
f[i] = i;
for(int j = 0; j < i; ++j){
if(num[j+1][i])f[i] = min(f[i], f[j] + 1);
}
}
cout << f[n] << endl;
}