首页 > 其他分享 >P9746 「KDOI-06-S」合并序列

P9746 「KDOI-06-S」合并序列

时间:2024-07-30 13:50:42浏览次数:5  
标签:P9746 06 KDOI 异或 枚举 端点 区间 net 我们

mx练习赛搬的,虽然数据不咋样,但是一步步的优化思路确实值得一记。

P9746 合并序列

题目大意:

给你 \(n(1\le n \le 500)\) 个数 \(a_1,a_2,\ldots a_n\)(\(a_i < 512\))。每次可以选一个3元组 \((i,j,k)\),满足 \(i<j<k\),并且 \(a_i\oplus a_j\oplus a_k=0\),则你可以将 $ a_i \dots a_k$ 删掉并替换为 \(a_i\oplus a_{i+1}\oplus \cdots\oplus a_k\)。

请你判断是否能够使得序列 \(a\) 仅剩一个数。若可以,你还需要给出一种操作方案。

(\(\oplus\) 表示按位异或运算)

思路:

1. 区间dp

首先,可以看到 \(n\le 500\),我们可以想到区间 \(dp\)。

我们设 \(f_{i,j}\) 为区间 \([i,j]\) 是否 \((1/0)\) 可以被消掉,\(s_{i,j}\) 为区间 \([i,j]\) 的异或和,那么我们有以下转移:

\[f_{i,r} = f_{i,j} \ \ and \ \ f_{x,y} \ \ and \ \ f_{l,r} \ \ and \ \ (s_{i,j} \oplus s_{x,y} \oplus s_{l,r}==0) \]

\[f_{i,i}=1 \]

这样的话预处理 \(s\) 是 \(O(n^2)\) 的,然后转移是 \(O(n^6)\) 的,还要优化。

虽然数据弱的你剪剪枝不一定不过,但是你肯定不敢写

好歹这玩意是六次方,你肯定不信这玩意是正解

2. 优化

我i们会发现题目的 \(a_i\) 给的很小,和 \(n\) 是一个级别的,这个性质我们还没有用到,那么我们往值域上考虑。

考虑到异或和为零就意味着异或的两个数是相等的,所以如果你确定了两个区间,那么就意味着剩下的一个区间的异或的值就已经确定了。

那么我们设 $ g_{i,j,k} $ 为区间 \([i,j]\) 之间是否存在异或和为 \(k\) 的可行(相应 \(f\) 为 \(1\))的区间。

那么转移就变成了:

\[f_{i,r} = f_{i,j} \ \ and \ \ g_{j,l,s_{i,j} \oplus s_{l,r}} \ \ and \ \ f_{l,r} \]

\[g_{i,j,k} = g_{i+1,j,k} \ or \ g_{i,j-1} \ or \ ( f_{i,j} \ \ and \ \ ( \ s_{i,j}==k \ ) \ ) \]

\[f_{i,i}=g_{i,i,a_i}=1 \]

这样的话,转移就会是 \(O(n^4)\) 的。

但是理论上还是过不去的,毕竟 \(n\) 和值域都是 \(500\),还要乘上 \(T\),但是数据……

2. 优化(二阶段)

如果我们再顺着上面的思路去想,会发现好像实在是没法优化了,考虑转换 \(dp\) 状态和枚举的东西。

首先,如果我们如果想要降低复杂度的话,一定要想用最少的枚举确定多个信息。

那它是什么呢?异或!

异或和为 \(0\) 的充要条件就是两个数相等。那我们可以枚举一个 \(k\),表示某两个区间的异或和,和第三个区间的值,这两个值必须是相同的。

所以我们可以用这个性质将三个区间分成两份,不妨让前两个一起考虑,最后一个单独考虑。

这样我们就把区间分成了两份。

我们可以枚举 \(i,r,k\),代表当前我在判断哪个区间可行,它的两部分的异或和是多少。

但是转移的话如果还是用之前的数组好像难以快速解决问题,我们考虑再优化。

有一个套路,\(dp\) 的时候如果状态只是记录可行性,可以考虑将某一维变成存储的值,记录在可行的条件下最优情况。

首先,\(f_{i,r}\) 显然是省不了的,也没必要省。

但是我们可以发现,\(g\) 数组一看就很傻是吧,只是维护一个区间内是否存在就很浪费,我们可以将他变为 \(g_{x,k}\),意义是对于左端点 \(>x\) 的点,异或值为 \(k\) 的可行区间的右端点的最小值。

这样,在判断当前是否可行的时候,只要这个最小值和右边枚举的区间不交就行。

同时,我们会发现,同样的,枚举的 \([l,r]\) 这个区间……

插一下区间关系:

也很蠢,我们也可以设数组 \(lst_{r,k}\),记录右端点等于\(r\),异或值是 \(k\),最大的 \(l\) 是多少。

但是,如果只是变了这几个数组,还是无法降低复杂度,因为你至少还要枚举一个 \(j\)。

我们会想到,既然已经将前两个区间分为一组了,那我们直接记录 \(net_{i,k}\) 左端点 等于\(i\),包含两个可行区间,最小的右端点是多少。

这样的话,转移的时候就直接枚举 \(i,r,k\),只要 \(net_{l,k}\) 和 \(lst_{r,k}\) 不交,那就说明区间 \(i,r\) 可行。

一定注意一下枚举顺序,由于我们在判断当前区间的时候需要用当前区间左端点之后的信息,所以要倒序枚举 \(i\),然后又因为需要用到当前区间内的小区间的信息,所以正序枚举一个 \(>i\) 的 \(r\)。

这样转移就是 \(O(n^3)\) 的,再看一下 \(g,net,lst\) 数组的维护。

首先 \(g\) 和 \(lst\) 都很好维护。

\(g_{i,k}\) 直接可以从 \(g_{i+1,k}\) 继承,然后每找到一个可行区间就更新。

\(lst\) 由于要卡到右端点,所以更简单,直接不停取 \(min\) 就好了。

但是,\(net\) 呢?

我们会发现由于 \(net\) 记录的是左端点等于 \(i\) 的两个区间,所以当我们找到一个合法区间的时候,都要进行更新,这是动态的,因为我们对于某个确定的 \(i\),我们都有可能要用刚刚找到的可行区间来更新之后的区间。

你可能会想到,区间的判定就已经是 \(O(n^3)\) 的了,每次确定了一个区间之后又要用 \(O(n)\) 的枚举配合 \(g\) 来求解,这样不就是 \(O(n^4)\) 的了吗?

但其实会发现,可行的区间最多只有 \(O(n^2)\) 种,所以我们只会动态更新 \(net\) 数组 \(O(n^2)\) 次,每次是 \(O(n)\) 的,所以复杂度就是 \(O(n^3)\) 的。

这里一定要注意,为了保证 \(O(n^3)\) 的复杂度,一定要在判断出当前区间合法时直接退出循环,保证 \(net\) 数组只会动态更新 \(O(n^2)\) 次。

所以,最终复杂度 \(O(n^3)\),理论上的正解。虽然跑的不一定比暴力快

Code:

#include<bits/stdc++.h>
using namespace std;
inline int read(){
	int rt=0;	char g=getchar();
	while(g<'0'||g>'9')	g=getchar();
	while(g>='0'&&g<='9')	rt=(rt<<3)+(rt<<1)+g-'0',g=getchar();
	return rt;
}
int a[505],sum[505][505];
bool f[505][505];
int net[505][512],lst[505][512];
int g[505][512],gans[505][512];
struct node{int i,j,x,y,l,r;}fans[505][505];
int ans[505][505];
struct nnode{int l,r;}netans[505][512];
int num;
inline void out(int l,int r)
{
	if(l==r)	return;
	register int A,B,C;
	A=fans[l][r].i-num;out(fans[l][r].i,fans[l][r].j);
	B=fans[l][r].x-num;out(fans[l][r].x,fans[l][r].y);
	C=fans[l][r].l-num;out(fans[l][r].l,fans[l][r].r);
	printf("%d %d %d\n",A,B,C);	num+=C-A;
}
int main()
{
	register int T=read(),n;
	register int i,j,k,l,r;
	while(T--)
	{
		n=read();	memset(f,0,sizeof(f));
		for(i=0;i<512;i++)	g[n+1][i]=gans[n+1][i]=n+1;
		
		for(i=1;i<=n;i++)
		{
			a[i]=read(),f[i][i]=1;
			for(j=1;j<=i;j++)	sum[j][i]=sum[j][i-1]^a[i];
			for(j=0;j<512;j++)	net[i][j]=n+1,lst[i][j]=0;
		}
		for(l=n;l;l--)
		{
			memcpy(g[l],g[l+1],sizeof(g[l+1]));
			memcpy(gans[l],gans[l+1],sizeof(gans[l+1]));
			g[l][a[l]]=gans[l][a[l]]=lst[l][a[l]]=l;
			
			for(j=0;j<512;j++)
				if(net[l][j^a[l]]>g[l+1][j])
					net[l][j^a[l]]=g[l+1][j],netans[l][j^a[l]]={l,gans[l+1][j]};
			
			for(r=l+2;r<=n;r++)
				for(j=0;j<512;j++)
					if(net[l][j]<lst[r][j])
					{
						ans[l][r]=ans[l][netans[l][j].l]+ans[netans[l][j].r][net[l][j]]+ans[lst[r][j]][r]+1,
						f[l][r]=1,fans[l][r]={l,netans[l][j].l,netans[l][j].r,net[l][j],lst[r][j],r};
						lst[r][sum[l][r]]=max(lst[r][sum[l][r]],l);
						if(g[l][sum[l][r]]>r)	g[l][sum[l][r]]=r,gans[l][sum[l][r]]=l;
						for(k=0;k<512;k++)
							if(net[l][k^sum[l][r]]>g[r+1][k])
								net[l][k^sum[l][r]]=g[r+1][k],netans[l][k^sum[l][r]]={r,gans[r+1][k]};
						break;
					}
		}
		if(f[1][n]){printf("Huoyu\n%d\n",ans[1][n]);num=0;out(1,n);}
		else puts("Shuiniao");
	}
	return 0;
}

标签:P9746,06,KDOI,异或,枚举,端点,区间,net,我们
From: https://www.cnblogs.com/Jack-YT/p/18324075

相关文章

  • 069基于SSM+Jsp的智能停车场管理系统
     开发语言:Java框架:ssm技术:JSPJDK版本:JDK1.8服务器:tomcat7数据库:mysql5.7(一定要5.7版本)数据库工具:Navicat11开发软件:eclipse/myeclipse/ideaMaven包:Maven3.3.9系统展示系统功能界面用户注册用户信息车位信息管理员登录界面管理员功能界面用户管理车位信息管理......
  • 06_Calendar类_SimpleDateFormat类_System类
    一、Calendar类Calendar的构造方法是protectedCalendar(),由于修饰符是protected,所以无法直接创建该对象,需要使用Calendar.getInstance();创建。其他方法:代码示例:importjava.util.Calendar;publicclassdemo01{publicstaticvoidmain(String[]args){......
  • 06-UnitTest框架
    DAY-09课堂笔记UnitTest基本使用UnitTest框架介绍框架什么是框架?1.框架英文单词framework2.为解决一类事情的功能集合UnitTest框架是Python自带的一个单元测试框架-⾃带的,可以直接使⽤,不需要单外安装-测试⼈员⽤来做⾃动化测试,作为⾃动化测试的执⾏框......
  • C语言day06(数组、字符数组)
    C语言day06【1】数组1》概念:具有一定顺序的若干变量的集合2》定义格式:存储类型数据类型数组名[元素的个数]例:intarr[5];//定义了一个数组arr,在内存空间中开辟了5个空间来储值在数组中保存的每一条数据都叫(元素)变量数组名:代表数组的首地址(地址常量);数组......
  • 问题 E: 深入浅出学算法060-友好城市
    题目描述有一条横贯东西的大河,河有笔直的南北两岸,岸上各有位置各不相同的N个城市。北岸的每个城市有且仅有一个友好城市在南岸,而且不同城市的友好城市不相同。每对友好城市都向政府申请在河上开辟一条直线航道连接两个城市,但是由于河上雾太大,政府决定避免任意两条航道交叉,以避......
  • CF1906C Cursed Game 题解
    题目大意交互库有一个\(3\times3\)的01矩阵\(a\),每次询问一个\(n\timesn\)的01矩阵\(b\),交互库会返回一个\((n-2)\times(n-2)\)的01矩阵\(c\),满足:\[c_{x,y}=\bigoplus\limits_{1\lei,j\le3}\left(a_{i,j}\operatorname{AND}b_{x+i-1,y+j-1}\right......
  • 【Python学习手册(第四版)】学习笔记06-Python动态类型-赋值模型详解
    个人总结难免疏漏,请多包涵。更多内容请查看原文。本文以及学习笔记系列仅用于个人学习、研究交流。主要介绍Python的动态类型(也就是Python自动为跟踪对象的类型,不需要在脚本中编写声明语句),Python中变量和对象是如何通过引用关联,垃圾收集的概念,对象共享引用是如何影响多个变量......
  • CF1060F Shrinking Tree
    考虑分别以每个点为根计算概率,可以计算所有边固定了收缩顺序的概率之和后除以\((n-1)!\)即为答案设\(f_{x,i}\)表示在\(x\)的子树内,删除最后\(i\)条边前后根的编号不发生变化的概率和,所求即为\(f_{rt,n-1}\)记当前点为\(v\),父节点为\(u\),因为收缩\((u,v)\)时,之前的......
  • 洛谷P1067 [NOIP2009 普及组] 多项式输出
    题目链接:-P1067[NOIP2009普及组]多项式输出题目叙述:[NOIP2009普及组]多项式输出题目描述一元n次多项式可用如下的表达式表示:多项式中自变量为x,从左到右按照次数递减顺序给出多项式。多项式中只包含系数不为0的项。如果多项式n次项系数为正,则多项式开头......
  • 链路追踪和分析-Sleuth+Zipkin-微服务核心组件【分布式微服务笔记06】
    链路追踪和分析-Sleuth+Zipkin-微服务核心组件【分布式微服务笔记06】链路追踪和分析-Sleuth+Zipkin在微服务框架中,一个由客户端发起的请求在后端系统中会经过多个不同的的服务节点调用,来协同产生最后的请求结果,每一个请求都会形成一条复杂的分布式服务调用链路链路中的任何......