EASY
Making the Grade
给一个长为n(n<2000)的序列A,将其变成一个非增或非减的序列B的绝对值改变量。![]()
key:B中所有的数都是A中的数。因为若a l < b < a r a_l<b<a_ral<b<ar时,总能令b变成其中一个值。则枚举b i b_ibi为每个A中的值即可。dp[i][j]表示b i ≤ u n q j b_i≤unq_jbi≤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 mm个时间区间,完成每个区间的事可以有一定的贡献。每做完一件事要缓r rr分钟。问最多有多少贡献。
思路:由于时间范围为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×nn×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 ii,则如何求变色次数?显然答案是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_iai,已有水量是b i b_ibi,倒水有一半的损失。挑k kk个杯子装水,求最大的装水量。
第一个思路:dp[i][k][j]表示1... i 1...i1...i个杯子,挑k kk个,剩余容量为j的最大装水量。但是这个状态转移具有后效性,可能前面后面要的杯子,用前面的杯子里的水。
题解:dp[i][k][j]表示1... i 1...i1...i个杯子,挑k kk个,总容量为j jj的最大水量(未考虑是否装的下,只考虑有多少水)。状态转移如下。则取k kk杯的答案就是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×mn×m矩阵,每行选最多m / 2 m/2m/2个。求能被m o d modmod整除的最大和。(所有数小于70)
思路:对于第i ii行来说,f [ j ] [ h ] [ k ] f[j][h][k]f[j][h][k]表示1... j 1...j1...j取h hh个,对m o d modmod取余为k kk的最大和。易得转移公式
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+1ai+1代替。问最后合并出最少的项数。
思路:
- 先考虑只有相邻的多个数合并成一个数的所有情况。当可以合并成一个数时,这个数必然是唯一的。即n u m [ i ] [ j ] num[i][j]num[i][j]表示把a i ⋯ a j a_i\cdots a_jai⋯aj合并成一个数,那个数的值,如果不能的话,n u m [ i ] [ j ] = 0 num[i][j]=0num[i][j]=0.就是一个简单的区间DP
- 再考虑最少用多少个数即n u m numnum区间来得到整个1 ⋯ n 1\cdots n1⋯n区间。设f [ i ] f[i]f[i]表示1 ⋯ i 1\cdots i1⋯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;
}