AtCoder abc176_f - Brave CHAIN

u1s1 最近比较鸽,被 whk 压榨干了……

这是一个唯一的 ABC 的红题

洛谷爬虫貌似还没修好,咕咕咕 & AtC 题目页面传送门

有一列 \(3n\) 个数 \(a_i\in[1,n]\)。你需要进行 \(n-1\) 次操作,每次可以将最左边 \(5\) 个数任意排列,然后删除最左边 \(3\) 个数,如果这 \(3\) 个数相等则得 \(1\) 分。显然 \(n-1\) 次操作后还剩 \(3\) 个数,那么若它们相等则额外得 \(1\) 分。求最大得分。

\(n\in[1,2000]\)

由于一开始把「最左边」看成了「最右边」,就按照「最右边」想 & 写了,过不去样例然后发现了就 reverse 了一下(捂脸)。所以题解也按照「最右边」来写了。

贪心什么的就不用想了,考虑 DP。

注意到,第 \(i\) 次涉及到的 \(5\) 个数中,只有右边 \(2\) 个数也在第 \(i-1\) 次操作里被涉及到了,而左边 \(3\) 个数在前 \(i-1\) 次操作做完之后都是固定的(\(a_{3i-4\sim 3i-2}\))。于是就有了一个很显然的,以操作数为阶段的 DP:设 \(dp_{i,j,k}\) 表示考虑到第 \(i\) 次操作,第 \(i\) 次操作的右边 \(2\) 个数分别为 \(j,k\) 的最大得分。那么边界是 \(dp_{1,i,j}=[a_1=i=j]\),目标是 \(dp_{n,a_{3n},a_{3n-1}}\)。转移的话,枚举就可以了,是(伪)\(\mathrm O(1)\) 的:

\[dp_{i,j,k}=\max\limits_{vld(A(i,j,k),o,p)}\{dp_{i-1,o,p}+eq(A(i,j,k)-\{o,p\})\} \]

其中 \(A(i,j,k)=\{a_{3i-4},a_{3i-3},a_{3i-2},j,k\}\)\(vld(S,a,b)\) 表示可重集 \(\{a,b\}\) 是否包含于可重集 \(S\)\(eq(S)\) 表示可重集 \(S\) 内的元素是否全部相等。

这样总复杂度是 \(\mathrm O\!\left(n^3\right)\) 的,常数还比较大,大概 \(3\times5\times5\) 这样,显然过不去,考虑换思路?这辈子不可能的。考虑优化。

注意到,光状态数量就爆炸了,于是想到 CF 809D 的套路,转移的时候直接在 DP 数组上修改来实现相邻阶段 DP 数组的转移,从而转化为一个 DS 向的问题(那题是平衡树维护的)。

考虑分析这题的转移。显然可以分成 \(3\) 类:

  1. \(o,p\) 都是固定的 \(3\) 个中的:那么显然只有常数个,而且对于所有 DP 状态,它们都是合法转移。于是直接全局取 \(\max\) 即可,对于有加 \(1\) 的单点取 \(\max\) 特殊照顾一下;
  2. \(o,p\) 一个是固定的,一个不是固定的:不妨设 \(o\) 是固定的,\(p\) 不是固定的。注意到,由于 \(o\) 的数量是常数,所以 \((o,p)\) 的数量是 \(\mathrm O(n)\)。而对于每个 \((o,p)\),以它作为一个合法转移的状态在二维 DP 数组中显然分布成一行一列的并,于是对于每个 \((o,p)\) 都做一次行取 \(\max\) 和列取 \(\max\),然后特殊情况一样特殊单点取 \(\max\)
  3. \(o,p\) 都是固定的:那么显然对于每个状态,这个 \((o,p)\) 唯一且就是它自身。于是如果固定的 \(3\) 个相等的话,全局加 \(1\) 即可。(这一种体现了那个套路的优势,即它可以将赋值转化为修改)

总结一下,我们只需要维护一个数据结构,支持在二维的矩阵上进行单点取 \(\max\)、行取 \(\max\)、列取 \(\max\)、全局加 \(1\)、单点查询(全局取 \(\max\) 可以转化为 \(n\) 次行 / 列取 \(\max\))。DS 学傻的同学们应该很容易想到平方乘以 1~2 个 \(\log\) 的线段树,不过显然不会放你过去。

事实上这是一个刚学 OI 的蒟蒻都会的。注意到一个点的 \(\max\) 贡献来源只可能是单点、本行、本列。于是对于每个单点、行、列都维护一个取 \(\max\) 的贡献总和,然后单点查询取三者 \(\max\) 即可。还剩一个全局加 \(1\),那么显然在外面维护一个整体增加量即可,稍微动点脑子就能证明。

这题中有些要注意的:如果真是个 DS 题,那么每次修改立即生效,而本题不然,它是以一个阶段为单位进行修改操作生效的。那怎么办?难道将 DP 数组 copy 一遍不成?那就梦回三方了。容易想到,对于每个修改先将要修改的地址和值存下来,等到本阶段结束之后一齐赋值。至于全局加 \(1\),应当在阶段开始时加,如果加成功了那么本阶段取值的时候就要减 \(1\)(太显然了吧)。

代码:

#include<bits/stdc++.h>
using namespace std;
#define pb push_back
#define mp make_pair
#define X first
#define Y second
const int N=2000;
int n;
int a[3*N+1];
int dp[N+1][N+1];
int mx_r[N+1],mx_c[N+1];
bool added;
int real_dp(int x,int y){return max(dp[x][y],max(mx_r[x],mx_c[y]))-added;}
vector<pair<int*,int> > chg;
void chkmx(int &x,int y){chg.pb(mp(&x,y));}
int main(){
	cin>>n;
	for(int i=1;i<=3*n;i++)scanf("%d",a+i);
	reverse(a+1,a+3*n+1);//(捂脸 
//	for(int i=1;i<=3*n;i++)cout<<a[i]<<" ";puts("");
	for(int i=1;i<=n;i++)for(int j=1;j<=n;j++)dp[i][j]=a[1]==i&&i==j;
	int add=0;
	for(int i=2;i<=n;i++){
		if(a[3*i-4]==a[3*i-3]&&a[3*i-3]==a[3*i-2])add++,added=true; 
		else added=false;
		chg.clear();
		for(int j=3*i-4;j<=3*i-2;j++){
			vector<int> v;
			for(int k=3*i-4;k<=3*i-2;k++)if(k!=j)v.pb(a[k]);
			for(int k=1;k<=n;k++)chkmx(mx_r[k],real_dp(v[0],v[1]));
			chkmx(dp[a[j]][a[j]],real_dp(v[0],v[1])+1);
		}
		for(int j=1;j<=n;j++){
			for(int k=3*i-4;k<=3*i-2;k++){
				chkmx(mx_r[j],real_dp(j,a[k])),chkmx(mx_c[j],real_dp(j,a[k]));
				vector<int> v;
				for(int o=3*i-4;o<=3*i-2;o++)if(o!=k)v.pb(a[o]);
				if(v[0]==v[1])chkmx(dp[j][v[0]],real_dp(j,a[k])+1),chkmx(dp[v[0]][j],real_dp(j,a[k])+1);
			}
		}
		for(int j=0;j<chg.size();j++)*chg[j].X=max(*chg[j].X,chg[j].Y);
	}
	added=false;
	cout<<real_dp(a[3*n],a[3*n-1])+add;
	return 0;
}
posted @ 2020-09-08 22:30  ycx060617  阅读(465)  评论(2编辑  收藏  举报