C.Red Walking on Grid
https://ac.nowcoder.com/acm/contest/81597/C
数组定义: d p [ i ] [ j ] dp[i][j] dp[i][j] —— 在 i i i 行 j j j 列这个位置的走过的R的个数
状态方程: 1. d p [ i ] [ j ] = d p [ i ] [ j − 1 ] + 1 1.dp[i][j] = dp[i][j - 1] + 1 1.dp[i][j]=dp[i][j−1]+1(如果同行能走)
$ 2. d p [ i ] [ j ] = m a x ( d p [ i ] [ j ] , d p [ i − 1 ] [ j − 1 ] + 1 ) 2.dp[i][j] = max(dp[i][j],dp[i - 1][j-1] + 1) 2.dp[i][j]=max(dp[i][j],dp[i−1][j−1]+1)(如果同列能走)(注意!!在两行进行这个决策时,后面那个 d p [ i − 1 ] [ j − 1 ] dp[i-1][j-1] dp[i−1][j−1] 应该放的是没有经过方程 2 2 2 的原始值)
思路:我们从左往右(外循环),从上往下(内循环)遍历,就能避免走过这个格子又经过的情况。如果同行能走(两个格子上都是"R"),则先进行方程 1 1 1 ,因为我们实行贪心策略能走则走。如果这一列能走,则我们取MAX,看是”从左边走过来的值大“还是”从右边走过来的值大“,我们取原始值进行比较就是因为不能在这一行里往返走。
void solve() {
//code here
int n;cin >> n;
string str[2];
cin >> str[0] >> str[1];
str[0] = 'W' + str[0] + 'W'; //为了方便防止越界
str[1] = 'W' + str[1] + 'W';
n += 2;
vector<vector<int>>dp(2,vector<int>(n));
for(int i = 1;i < n - 1;++i){
if(str[0][i] == 'R')dp[0][i] = dp[0][i - 1] + 1;
if(str[1][i] == 'R')dp[1][i] = dp[1][i - 1] + 1;
if(str[0][i] == str[1][i] && str[0][i] == 'R'){
int t1 = dp[0][i],t2 = dp[1][i];
dp[0][i] = max(dp[0][i],t2 + 1);
dp[1][i] = max(dp[1][i],t1 + 1);
}
}
int ans = 0;
for(int i = 1;i < n - 1;++i){
ans = max({ans,dp[0][i] - 1,dp[1][i] - 1});
}
cout << ans << endl;
}
I.Red Playing Cards
https://ac.nowcoder.com/acm/contest/81597/I
数组定义: d p [ i ] dp[i] dp[i] —— 合并完 i i i 位置这个值所对应的区间 [ p o s [ a r r [ i ] ] . x , p o s [ a r r [ i ] ] . y ] [pos[arr[i]].x,pos[arr[i]].y] [pos[arr[i]].x,pos[arr[i]].y]( 其中 p o s [ a r r [ i ] ] . y 即为 i 其中pos[arr[i]].y即为i 其中pos[arr[i]].y即为i),所产生的最大值。
状态方程:详情看代码。
思路:先合并小区间,再合并大区间。因为大区间里可能会包含小区间,先合并小区间时有助于我们在合并大区间时进行决策——到底是合并掉这个区间直接取合并后的值大( d p [ l e f t ] + t m p [ l e f t − 1 ] dp[left] + tmp[left - 1] dp[left]+tmp[left−1]),还是把这个区间看作纯填坑的数组不合并所拿的值大。对于两个区间交错的问题,我们先分别把这两个区间合并的值搞出来放那(放 d p [ i ] dp[i] dp[i]里),再在合并"包含这两个小区间"的大区间时进行决策取不取。
bool cmp(PII &a,PII &b){
return a.y - a.x < b.y - b.x; //长度比较
}
void solve() {
//code here
int n;cin >> n;
int nn = 2 * n;
vector<int>arr(nn + 2); //存原始数据数组
vector<PII>pos(n + 1); //1-n数字对的左位置和右位置
for(int i = 1;i <= nn;++i){ //初始数据处理
cin >> arr[i];
if(pos[arr[i]].x)pos[arr[i]].y = i;
else pos[arr[i]].x = i;
}
vector<PII>up(n + 1); //用于复制pos数组
pos.push_back({0,nn+1}); //为了方便,最后遍历整个数组时而加入。与for(0~nn+1)没有本质区别
up = pos;
sort(up.begin(),up.end(),cmp);
//用于复制pos数组,并按照区间长度从小到大排序
//目的是为了让小区间先进行合并,大区间后合并,方便大区间包含“整个”小区间时,直接取max决策这个小区间是合并了贡献大还是不合并贡献大
//如不懂可以先放着看下面
vector<int>tmp(nn + 2); //用于每个区间遍历时存值
vector<int>dp(nn + 2); //存每个区间遍历完后的值 dp[y] 为 区间[x,y]整合后的最大值(注意:这个值仅存于dp[y]
//目的是方便下面那行max决策时取值
for(auto [l,r] : up){ //相当于int l = up[i].x ,int r = up[i].y;
tmp[l] = arr[l]; //tmp[i] 遍历这个区间时暂时存合并这个最优值(合并好(如果能合并更小的区间)还是不合并好)
for(int i = l + 1;i <= r;++i){
tmp[i] = tmp[i - 1] + arr[l]; //不考虑其他,只是遍历
int left = pos[arr[i]].x; //假设这个位置存的值是右值 去存当前位置的这个值的左值
if(left != i && left > l){ //如果当前确实是左值 且在这个大区间里 我就取max进行决策(到底是”合并这个区间值大“还是”不合并大“)
tmp[i] = max(tmp[i],tmp[left - 1] + dp[i]); //重点! dp[i]就是合并这个小区间所获得的值(被合并的值都在dp[i]里),因为被取,所以左边剩下的tmp[left-1]
}
}
dp[r] = tmp[r] ; //遍历完后 将合并这个区间的最优解放在dp[r]里
}
cout << dp[nn + 1] << endl; //输出整个区间的最优解
}