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

news/2024/9/23 5:28:48/文章来源:https://www.cnblogs.com/Jack-YT/p/18324075

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;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/774039.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Day 23 - 模拟赛

百万富翁的第二次实验 题目描述 马克吐温有一本非常著名的小说《百万英镑》,这本小说中主角最后归还了百万英镑给两位富翁。但结果就是两位富翁依然有无穷的问题需要进行社会实验,于是,他们打算进行第二次社会实验。那就是不同财富值的人在一场舞会上会发生什么事情。为了满…

LeetCode 756. Pyramid Transition Matrix

原题链接在这里:https://leetcode.com/problems/pyramid-transition-matrix/description/ 题目: You are stacking blocks to form a pyramid. Each block has a color, which is represented by a single letter. Each row of blocks contains one less block than the row …

两个燃点,引爆在线教育平台数智化

人人都能消费数据,事事都能数据驱动,正在成为火花思维数智化升级下的日常,这也与火山引擎推出的企业数智化升级新模式不谋而合。关注 字节跳动数据平台公众号,回复“火花思维”,获取高清方案设计及产品资料 2017年,专注于少儿逻辑思维的在线教育品牌火花思维正式成立,依…

一个简单的遍历需求

今天开发一个前端遍历展示的接口,被前端怼了看图需求很简单多行展示 每行多个,可显示更多,每行可左右滑动为了图省事简单组装一下直接返回回去了,方便是真的方便,前端生气也是真的生气,可以简单对这个横向、纵向接口压缩一下横向空间上,可以取分页,前端自己传,无论是懒…

分享一个MySQL数据库表结构导出word文档最方便的方法

原文链接:https://blog.csdn.net/typ1805/article/details/83658708 1、使用的是MySQL-Front工具,这个工具使用非常方便,尤其是导出数据的时候,几百万的数据一两分钟就导完了,推荐使用。 MySQL-Front下载(只有3.93M):https://mysql-front.en.softonic.com/ 注:新版本和…

排球比赛计分程序模拟冲刺(sprint)

模拟冲刺计划(spring) 选择小的用户故事模拟冲刺: 裁判张三、业余排球比赛组织者李四、排球运动员王五 一、任务拆分与开发时间:裁判张三:任务 1:开发操作指南和演示视频- 时间:第 1 天 任务 2:实现提前设置比赛基本信息功能- 时间:第 2 天 任务 3:设计简便计分操作界面…

代码随想录day14 || 226 翻转二叉树,101 对称二叉树, 104 二叉树的最大深度, 111 二叉树的最小深度

226 翻转二叉树 func invertTree(root *TreeNode) *TreeNode {// 思考,广度优先遍历,对于每一层,翻转其左右子节点if root == nil {return nil}queue := list.New()queue.PushBack(root)size := 1 // 存储每一层的节点个数for queue.Len() > 0{var count intfor i:=0; i&…

【数值计算方法】数值积分微分

《现代数值计算方法 第二版》1. 引言 高数中计算积分思路基本是牛顿莱布尼兹法: \[I[f]=\int_{a}^{b}f(x)\mathrm{d}x=F(b)-F(a), \]\[F^{\prime}(x)=f(x). \]实际计算中,原函数一般无法求出.给不出解析解,只能求出数值解. 设在区间 [a,b]( 不妨先设 a,b 为有限数 ) 上 ,\(f(x)…

经典的反转

def reverseArr(arr, start, end):while(start < end):arr[start], arr[end] = arr[end], arr[start]start +=1;end -=1def fun(arr,d, n):reverseArr(arr,0, n-1)reverseArr(arr, n-d, n-1)reverseArr(arr,0, n-d-1)arr = [1,2,3,4,5,6,7] fun(arr,2,len(arr)) print(arr)

【日记】今天又是哪朵小云不开心了呀(1886 字)

正文上午上班没多久,天就特别阴,感觉像是要下暴雨的样子。前台接了一个电话,家里人打来的,她妈妈叮嘱她,要注意一点。他们那边已经开始下了。她转过头对我笑笑说,原来下雨在一个城里也能不同步。当时我笑了笑,对她说,局部降雨还有更局部的,然后打开的视频网站,随便点…

51nod两问-Pinball等

问题1-Pinball为什么这样解释的通,我看不懂什么意思?还有这个 \(e\) 在后面状态中没有体现。具体做法?为什么只有 \([a_i,c_i]\) 需要考虑?他可以往左边掉。那么从 \(n\) 开始掉又如何考虑 Kamp手绘的图:这个图似乎就不满足了。不知道什么意思。这个思路怎么做。