当前位置: 首页 > news >正文

差分 异或

专题技巧:差分数组、异或操作、证明的技巧、树上异或、边权转点权。

目录

  • T1 偷闲
  • T2 数列问题
  • T3 变色小球
  • T4 灯与开关(ATabc155f)
  • T5 树异或(ATapc001f)
  • T6 星空(洛谷P3943)
  • 总结

T1 偷闲

题意
有一个长度为 n n n 的序列,每次操作可以选择以下三种的任意一种操作一次:

  1. 选择任意 i i i 1 ≤ i ≤ n 1\le i \le n 1in),将第 1 1 1 个数到第 i i i 个数分别减 1 1 1
  2. 选择任意 i i i 1 ≤ i ≤ n 1\le i \le n 1in),将第 i i i 个数到第 n n n 个数分别减 1 1 1
  3. 将全部数分别加 1 1 1
    求最少操作数使全部数变为 0 0 0 。(保证答案存在)
    1 ≤ t ≤ 100 , − 1 0 9 ≤ a i ≤ 1 0 9 , ∑ n ≤ 2 × 1 0 5 1\le t \le 100,-10^9\le a_i \le 10^9,\sum_{}n\le 2\times 10^5 1t100,109ai109,n2×105

思路
首先看到对某个区间进行加一减一操作可以想到差分。建立一个差分数组 b b b,用差分数组维护操作,最后只要保证差分数组都为 0 0 0 即可。对于操作一,相当于对 b [ 1 ] − 1 , b [ i ] + 1 b[1]-1,b[i]+1 b[1]1,b[i]+1 ;操作二相当于对 b [ i ] − 1 , b [ n + 1 ] + 1 b[i]-1,b[n+1]+1 b[i]1,b[n+1]+1 ;操作三相当于 b [ 1 ] + 1 , b [ n + 1 ] − 1 b[1]+1,b[n+1]-1 b[1]+1,b[n+1]1 。现在要将 b i b_i bi 变为 0 0 0 ,如果 b i ≥ 0 b_i \ge 0 bi0 ,就做 b i b_i bi 次操作二,否则就做 b i b_i bi 次操作一。发现 b 1 b_1 b1 如果先变为 0 0 0 的话会浪费一些操作数,那么就先将 b 2 … b n b_2 \dots b_n b2bn 变为 0 0 0,最后 b 1 b_1 b1 b n + 1 b_{n+1} bn+1 一定是一对相反数,再操作 ∣ b 1 ∣ |b_1| b1 次就可以将序列全部变为 0 0 0

代码

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5;
ll a[maxn],b[maxn];
int main(){
	int t; cin>>t;
	while(t--){
		int n; cin>>n;
		for(int i=1;i<=n;i++){
			cin>>a[i];
			b[i]=a[i]-a[i-1];
		}
		b[n+1]=-a[n];
		ll ans=0;
		for(int i=2;i<=n;i++){
			if(b[i]>0) ans+=b[i],b[n+1]+=b[i],b[i]=0;
			else ans+=(-b[i]),b[1]+=b[i],b[i]=0;
		}
		if(b[1]>0) ans+=b[1];
		else ans-=b[1];
		cout<<ans<<"\n";
	}
	return 0;
}
/*  -1 0 0 0 1
1:-1 i:+1
i:-1 n+1:+1
1:+1 n+1:-1
*/

T2 数列问题

题意
一个长为 n n n 的整数数列 a a a ,每次可以进行操作:选择任意一段连续的数,全都 + 1 +1 +1 − 1 -1 1。求最少的操作次数使所有数变成 0 0 0 n ≤ 1 0 5 , − 1 0 4 ≤ a i ≤ 1 0 4 n\le 10^5,-10^4\le a_i \le 10^4 n105,104ai104

思路
对数列 a a a 进行差分得到数组 b b b 。题目转化为在 b b b 数组上每次任选两个数进行一个 + 1 +1 +1 ,一个 − 1 -1 1 的操作,最后将 b b b 数组全部变为 0 0 0 。那当然是正数 − 1 -1 1 ,负数 + 1 +1 +1 ,最后如果到剩下一些正数(或负数)就单独操作即可。相当于取 b b b 中正数之和,负数之和的最大值。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int a[maxn],b[maxn];
int main(){
	int n; cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		b[i]=a[i]-a[i-1];
	}
	b[n+1]=-a[n];
	int z=0,f=0;
	for(int i=1;i<=n+1;i++){
		if(b[i]>0) z+=b[i];
		else f-=b[i];
	}
	cout<<max(z,f);
	return 0;
}
/*
[i,j]
+1: i:+1  j+1:-1
-1: i:-1  j+1:+1
*/

T3 变色小球

题意
n n n 个白色小球,可以选择 m m m 次操作,每次都会使相连的两个小球同时变色,问最多可以有多少个黑色小球?  1 ≤ n , m ≤ 1 0 6 1\le n,m \le 10^6 1n,m106

思路
结论一:一开始黑点数量是偶数,每次操作有 3 3 3 种可能:选择两白、两黑、一黑一白;可以发现操作之后,黑点数量一定还是偶数。
考虑将可以操作的两个小球之间连一条边,会得到一个图(图中可能会有很多个连通块)。每个连通块可以单独考虑。
由结论一可以到结论二:每个连通块可以得到的最多黑球个数 = = = 联通块中的点数(若点数为奇数则另外 − 1 -1 1 )。
给出证明:(如何构造出一种方案)
对一个连通块进行深搜,得到一棵树。现在希望得到最多黑色小球。从叶子节点往上推,发现到某个子树的根节点时,他的子树内除自己外的结点都能变成黑色。所以说整棵树除根结点外其他点都能被染成黑色,根节点可能可以也可能不可以。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;

int fa[maxn];
int Find_fa(int x){ return (fa[x]==x?x:fa[x]=Find_fa(fa[x])); }

int siz[maxn];
int main(){
	int n,m; cin>>n>>m;
	for(int i=1;i<=n;i++)
		fa[i]=i,siz[i]=1;
	for(int i=1;i<=m;i++){
		int x,y; cin>>x>>y;
		int fx=Find_fa(x),fy=Find_fa(y);
		if(fx!=fy){
			fa[fy]=fx;
			siz[fx]+=siz[fy];
		}
	}
	int ans=0;
	for(int i=1;i<=n;i++)
		if(Find_fa(i)==i) ans+=siz[i]-(siz[i]&1);
	cout<<ans;
	return 0;
}

T4 灯与开关(ATabc155f)

link
题意
在一条数轴上有 n n n 盏灯,第 i i i 盏灯在整点 a i a_i ai处,它的初始状态是 b i b_i bi。有 m m m 个开关,若操作第 i i i 个开关,则数轴上 i ∈ [ x i , y i ] i \in [x_i,y_i] i[xi,yi] 的灯的状态都会改变。如果能通过操作一些开关使得最后所有灯的状态都是 0 0 0 ,从小到大输出操作的开关的编号;否则,输出 − 1 -1 1。  1 ≤ n ≤ 1 0 5 , 1 ≤ m ≤ 2 × 1 0 5 , 1 ≤ x i ≤ y i ≤ 1 0 9 1\le n \le 10^5 , 1 \le m \le 2\times 10^5 , 1\le x_i \le y_i \le 10^9 1n105,1m2×105,1xiyi109

思路
看到变换一个区间的 01 01 01序列,可以想到先对序列进行异或差分,这样就可以 O ( 1 ) O(1) O(1) 完成操作。
题意变成:考虑要选择哪些操作,每次操作将将两个 1 1 1 变为 0 0 0 ,最后序列要全 0 0 0
T 3 T3 T3 ,将操作区间的两个端点连一条边,就有一个图。考虑将图深搜简化成树,这是可行的。现在有了一个森林,森林中的每棵树可以单独考虑。然后对每棵树进行 树形 D P DP DP。从叶子节点往上推,假设现在考虑到某个结点,如果他的儿子结点得到的 D P DP DP 值为 1 1 1 ,说明要选择这个结点到儿子这条边的操作,那么儿子的 D P DP DP 值变为 0 0 0 ,这个结点的值要反转。最后根节点的 D P DP DP 值若为 1 1 1 则说明没有合法的解决方案。(当然若根节点为 n + 1 n+1 n+1 则最后 D P DP DP 值是 1 1 1 0 0 0 都是可行的)

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5,maxm=2e5+5;

int idx,head[maxn];
struct EDGE{ int v,next,k; }e[maxm*2];
void Insert(int u,int v,int k){
	e[++idx]={v,head[u],k};
	head[u]=idx;
}

int ans,ansl[maxm],f[maxn],d[maxn];
bool vis[maxn];
void Dfs(int x,int fat){
	vis[x]=1;
	for(int i=head[x];i;i=e[i].next){
		int v=e[i].v;
		if(v!=fat){
			Dfs(v,x);
			if(f[v]) ansl[++ans]=e[i].k,f[x]^=f[v];
		}
	}
}

int fa[maxn];
int Find_fa(int x){ return (fa[x]==x?x:fa[x]=Find_fa(fa[x])); }

int n;
struct NODE{ int id,x; }a[maxn];
int Get_l(int x){
	int l=0,r=n+1,mid;
	while(l+1<r){
		mid=(l+r)>>1;
		if(a[mid].id<x) l=mid;
		else r=mid;
	}
	return r;
}

int Get_r(int x){
	int l=0,r=n+1,mid;
	while(l+1<r){
		mid=(l+r)>>1;
		if(a[mid].id<=x) l=mid;
		else r=mid;
	}
	return l;
}

int main(){
	int m; cin>>n>>m;
	for(int i=1;i<=n;i++)
		cin>>a[i].id>>a[i].x;
	sort(a+1,a+n+1,[](NODE a,NODE b){ return a.id<b.id; });
	for(int i=1;i<=n+1;i++)
		fa[i]=i;
	for(int i=1;i<=m;i++){
		int x,y; cin>>x>>y;
		x=Get_l(x);
		y=Get_r(y);
		if(x>y) continue;
		int fx=Find_fa(x),fy=Find_fa(y+1);
		if(fx!=fy) fa[fy]=fx,Insert(x,y+1,i),Insert(y+1,x,i);
	}
	for(int i=1;i<=n;i++)
		f[i]=a[i].x^a[i-1].x;
	Dfs(n+1,0);
	for(int i=1;i<=n;i++)
		if(!vis[i]){
			Dfs(i,0);
			if(f[i]){
				cout<<-1;
				return 0;
			}
		}
	sort(ansl+1,ansl+ans+1);
	cout<<ans<<"\n";
	for(int i=1;i<=ans;i++)
		cout<<ansl[i]<<" ";
	return 0;
}

T5 树异或(ATapc001f)

link
题意
给一棵有 n n n 个节点的树,节点编号从 0 0 0 n − 1 n-1 n1,树边编号从 1 1 1 n − 1 n-1 n1。第 i i i 条边连接节点 x i x_i xi y i y_i yi,其权值为 a i a_i ai。你可以对树执行任意次操作,每次操作选取一条链和一个非负整数 x x x,将链上的边的权值与 x x x 异或成为该边的新权值。问最少需要多少次操作,使得所有边的权值都为 0 0 0。  2 ≤ N ≤ 1 0 5 , 0 ≤ x i , y i ≤ N − 1 , 0 ≤ a i ≤ 15 2\leq N \leq 10^5 , 0\leq x_i,y_i \leq N-1 , 0\leq a_i \leq 15 2N105,0xi,yiN1,0ai15

思路
看到链操作可以想到树上差分。对于链 u − > v u->v u>v 操作就相当于在差分数组上 d u = d u ⊕ x , d y = d y ⊕ x d_u = d_u \oplus x , d_y = d_y \oplus x du=dux,dy=dyx ,就可以 O ( 1 ) O(1) O(1) 完成操作(也可以归为“边权化点权”)。现在的问题就是:每次在差分数组中选 2 2 2 个数异或上同一个数,最后要全部数变为 0 0 0 。由于所有点权 d i d_i di 的值域为 [ 0 , 15 ] [0,15] [0,15] ,点的个数很多,所以会有很多相同的值。容易想到先选择两个相同的数操作是最优的,因为一次就能将两个非 0 0 0 数清零。这样之后,每个值只会剩下至多一个。
所以这样对多只有 16 16 16 个数,想到状压 D P DP DP
又可以想到,对于一个集合 S S S 中所有数全部异或起来为 0 0 0 时,只需 ∣ S ∣ − 1 |S|-1 S1 次操作就能将 S S S 清零。
所以设 f s f_s fs 表示将 s s s 这个集合中的所有数清零所需的最小操作数,有转移:
f s = m i n { ∣ s ∣ ⊕ x ∈ s x ≠ 0 ∣ s ∣ − 1 ⊕ x ∈ s x = 0 min ⁡ t ∈ s f t + f s − t f_s = min \begin{cases} |s| & \oplus_{x \in s} x \not= 0 \\ |s|-1 & \oplus_{x \in s} x = 0 \\ \min\limits_{t \in s} f_t + f_{s-t} \end{cases} fs=min ss1tsminft+fstxsx=0xsx=0

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5,maxa=20,maxs=(1<<16)+5;
int d[maxn],gs[maxa],f[maxs];
int main(){
	int n; cin>>n;
	for(int i=1;i<n;i++){
		int x,y,z; cin>>x>>y>>z;
		d[x]^=z,d[y]^=z;
	}
	for(int i=0;i<n;i++)
		gs[d[i]]++;
	int ans=0,S=0,k=0;
	for(int i=1;i<=15;i++){
		ans+=gs[i]/2;
		if(gs[i]&1) S+=(1<<i),k++;
	}
//一个数字的集合有解(也就是说可以用取两个数字异或上同一个数字将这个集合里的所有数字变成0)当且当这个数字集合的异或和为0
	for(int i=1;i<=S;i++)
		f[i]=k-1;
	for(int i=1;i<=S;i++){
		int tmp=0,cnt=0;;
		for(int j=0;j<=15;j++)
			if(i&(1<<j)) tmp^=j,cnt++;
		if(!tmp) f[i]=min(f[i],cnt-1);
		for(int j=i;j;j=(j-1)&i)
			f[i]=min(f[i],f[j]+f[i^j]);
	}
	cout<<f[S]+ans;
	return 0;
}

T6 星空(洛谷P3943)

link
题意
n n n 个灯泡中有 k k k 个被熄灭。有 m m m 种长度的灯泡段可以被选择翻转,每种可以被选择多次,求将所有灯泡点亮的最少操作数。   n , b i ≤ 4 × 1 0 4 , m ≤ 64 , k ≤ 8 n,b_i \le 4 \times 10^4 , m \le 64 , k \le 8 n,bi4×104,m64,k8

思路
翻转一段灯泡,可以想到异或差分。每次可以消去两个 1 1 1 ,求消去所有 1 1 1 的最小方案数。发现差分后的序列最多只有 2 k 2k 2k 1 1 1 ,考虑状压。可以先 b f s bfs bfs 求出任意两个 1 1 1 同时消去所需的操作数,不能用(或不方便)递推( D P DP DP)是因为这个翻转操作会有后效性,其实只是本人这样弄不出来。复杂度能过,好像会有一点细节。感觉和前几题差不多。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5,maxk=20;

int n,m,hv,b[maxn],lgt[maxk*2],dis[maxk*2][maxk*2],cot[maxn];
queue<int>q;
void Bfs(int st){
	while(!q.empty()) q.pop();
	for(int i=1;i<=n+1;i++)
		cot[i]=1e9;
	q.push(lgt[st]);
	cot[lgt[st]]=0;
	while(!q.empty()){
		int x=q.front();
		q.pop();
		for(int i=1;i<=m;i++){
			if(x+b[i]<=n+1&&cot[x+b[i]]==1e9){
				cot[x+b[i]]=cot[x]+1;
				q.push(x+b[i]);
			}
			if(x-b[i]>=1&&cot[x-b[i]]==1e9){
				cot[x-b[i]]=cot[x]+1;
				q.push(x-b[i]);
			}
		}
	}
	for(int i=1;i<=hv;i++)
		dis[st][i]=dis[i][st]=min(dis[st][i],cot[lgt[i]]);
}

int d[maxn],f[(1<<maxk)];
int main(){
	int k; cin>>n>>k>>m;
	for(int i=1,x;i<=k;i++){
		cin>>x;
		d[x]^=1,d[x+1]^=1;
	}
	for(int i=1;i<=m;i++)
		cin>>b[i];
	for(int i=1;i<=n+1;i++)
		if(d[i]==1) lgt[++hv]=i;
	for(int i=1;i<=hv;i++) 
		for(int j=1;j<=hv;j++) 
		dis[i][j]=1e9;
	for(int i=1;i<=hv;i++) 
		dis[i][i]=0;
	for(int i=1;i<=hv;i++)
		Bfs(i);
	for(int i=0;i<(1<<hv);i++)
		f[i]=1e18;
	f[(1<<hv)-1]=0;
	for(int s=(1<<hv)-1;s>=0;s--)
		for(int i=1;i<=hv;i++)
			if((1<<(i-1))&s){
				for(int j=i+1;j<=hv;j++)
					if((1<<(j-1))&s){
						int s2=(s^(1<<(i-1))^(1<<(j-1)));
						f[s2]=min(f[s2],f[s]+dis[i][j]);
					}
			}
	cout<<f[0];
	return 0;
}

总结

感觉主要是要看到对一段区间进行加减异或等要想到差分。

相关文章:

  • 网络编程之客户端聊天(服务器加客户端共三种方式)
  • 智能家居安全革命:代理IP如何守护物联网世界
  • Elasticsearch:使用 ColPali 进行复杂文档搜索 - 第 1 部分 - 8.18
  • 穿越禁区:前端跨域通信的艺术与实践
  • Deployment声明式更新与应用式更新对比
  • Weblogic未授权远程命令执行漏洞复现
  • string(1):
  • 基于pycatia的CATIA装配体STP批量导出技术解析与优化指南
  • 分治-快速排序系列一>快速排序
  • VMWare:解决Linux虚拟机找不到共享文件夹
  • Java单元测试、Junit、断言、单元测试常见注解、单元测试Maven依赖范围、Maven常见问题解决方法
  • ubuntu高并发内核参数调优 - (压测客户端调优)
  • 【面试场景题-Redis中String类型和map类型的区别】
  • 蓝桥杯练习day2:执行操作后的变化量
  • 如何判断 MSF 的 Payload 是 Staged 还是 Stageless(含 Meterpreter 与普通 Shell 对比)
  • MySQL:数据库基础
  • 解决虚拟机网络问题
  • 【论文笔记】VGGT-从2D感知3D:pose估计+稠密重建+点跟踪
  • 爬虫基础之爬取猫眼Top100 可视化
  • 程序化广告行业(29/89):人群策略在广告投放中的应用
  • 德雷克海峡发生7.4级地震,震源深度10千米
  • 党旗下的青春|赵天益:少年确定志向,把最好的时光奉献给戏剧事业
  • “非思”的思想——探索失语者的思想史
  • 金砖国家外长会晤落幕,外交部:发出了反对单边霸凌行径的“金砖声音”
  • 国新办发布《关于新冠疫情防控与病毒溯源的中方行动和立场》白皮书
  • 民营经济促进法出台,自今年5月20日起施行