5.3训练总结

5.3训练记录

知识简述

  1. 双指针:跟题目类似,就是使用两个指针不断地运动递归。不过要注意两个指针不能乱动
  2. 双向搜索(折半搜索):主要目的是降低时间复杂度,因为一般的搜索时间复杂度是呈指数级的,而双向搜索可以让复杂度整体开根号,指数除以 2
  3. 尺取法:如下图所示,不做过多赘述。

4.floyd判环:

主要解决如·下图形状的判环。定义两个快慢指针,快指针一步走两个,慢指针一步走一个,第一次他们相遇时让快指针回到起点,下一次相遇的地点就是环的入口。

题目简介

Meet in the Middle

题目入口

给定 n 和一个长度为 n 的数组,有多少种方法选择一个该数组的子集使得和为 x

就是双向搜索的模版题目

#include<bits/stdc++.h>
using namespace std;
#define int long long
map<int, int> vis;
#define N 50
int n,x,a[N],mid,ans;
void dfs1(int i,int sum)//第一个搜索
{
	if(i>mid)
	{
		vis[sum]++;
		return;
	}
	dfs1(i+1,sum);
	dfs1(i+1,sum+a[i]);
}
void dfs2(int i,int sum)//后一半搜索
{
	if(vis[x-sum]&&i>n)
	{
		ans+=vis[x-sum];
		return;
	}
	if(i>n)
		return;
	dfs2(i+1,sum);
	dfs2(i+1,sum+a[i]);
}
signed main()
{
	cin>>n>>x;
	mid=n>>1;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
	}
	if(n==1&&x==1&&a[1]==1)//特判
	{
		cout<<1<<endl;
		return 0;
	}
	dfs1(2,0);
	dfs1(2,a[1]);
	dfs2(mid+2,a[mid+1]);//从中间断开搜索
	dfs2(mid+2,0);
	cout<<ans<<endl;
}
异或路径

题目入口

给你一个 n \ast m 的矩阵 a ,问有多少种从 (1,1)(n,m) 只能往下走或者往右走的路径,使得路径上的权值的异或和为一个给定的数 k

这题的难点就是折半搜索折在哪里的问题。敲黑板,因为矩阵要让他两边的搜索尽可能地短,我们就要选对角线 ,也就是 n+m-2 懂了这个这道题也就不难了。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int maxm=25;
int a[maxm][maxm];
int dir1[2][2]={0,1,1,0};
int dir2[2][2]={0,-1,-1,0};
map<int,int>mark[maxm][maxm];
int n,m,k;
int ans;
void dfs1(int x,int y,int step,int val){//搜索
    if(!step){
        mark[x][y][val]++;
        return ;
    }
    for(int k=0;k<2;k++){
        int xx=x+dir1[k][0];
        int yy=y+dir1[k][1];
        if(xx<1||xx>n||yy<1||yy>m)continue;
        dfs1(xx,yy,step-1,val^a[xx][yy]);
    }
}
void dfs2(int x,int y,int step,int val){
    if(!step){
        if(mark[x][y].count(val^k)){
            ans+=mark[x][y][val^k];
        }
        return ;
    }
    val^=a[x][y];
    for(int k=0;k<2;k++){
        int xx=x+dir2[k][0];
        int yy=y+dir2[k][1];
        if(xx<1||xx>n||yy<1||yy>m)continue;
        dfs2(xx,yy,step-1,val);
    }
}
signed main(){
    cin>>n>>m>>k;
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            cin>>a[i][j];
        }
    }
    int step=n-1+m-1;
    dfs1(1,1,step/2,a[1][1]);//对角线折半
    dfs2(n,m,step-step/2,0);
    cout<<ans<<endl;
    return 0;
}

mex和med

原题入口

假设当前的 mex 值是 i 那么区间一定包含 0∼i−1 ,同时区间长度小于等于 i \ast 2 。我们对于每个 mex 值为 i 都仅考虑包含它的长度为 i \ast 2 和长度为 i \ast 2-1 的区间的个数,因为长度为 i \ast 2-2 的区间一定在 mex 值为 i-1 时统计过。

#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,t;
int a[200005],to[200005];
signed main()
{
		cin>>n;
		int ans=0;
		for(int i=1;i<=n;i++)
		{
			cin>>a[i];
			to[a[i]]=i;
		}
		int l=INT_MAX,r=0;
		for(int i=0;i<n;i++)
		{
			l=min(l,to[i]),r=max(r,to[i]);
			int len=i*2+2;
			int l1,r1;
			if(r-l+1>len) 
                continue;
			l1=max(1ll,r-len+1),r1=min(l,n-len+1);
			ans+=max(0ll,(r1-l1+1));
			len--;
			if(r-l+1>len) 
                continue;
			l1=max(1ll,r-len+1),r1=min(l,n-len+1);
			ans+=max(0ll,(r1-l1+1));
		}
		cout<<ans<<endl;
}
囊中羞涩

原题入口

这题和 Meet in the Middle 几乎一样,不过多赘述

#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,a[100005];
int ans=0;
int b[1000005],c[1000005];
void dfs(int now,int nn,int res,int *b)
{
	if(res>m) 
		return;
	if(now>nn)
	{
		b[++b[0]]=res;
		return;
	}
	dfs(now+1,nn,res+a[now],b);
	dfs(now+1,nn,res,b);
}
signed main()
{
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=n;i++)
		scanf("%lld",&a[i]);
	sort(a+1,a+n+1);
	dfs(1,n/2,0,b);
	dfs(n/2+1,n,0,c);
	sort(b+1,b+b[0]+1);
	for(int i=1;i<=c[0];i++)
	{
		int l=1,r=b[0],res;
		while(l<=r)
		{
			int mid=(l+r)/2;
			if(b[mid]+c[i]<=m)
				res=mid,l=mid+1;
			else 
				r=mid-1;
		}
		ans+=res;
	}
	printf("%lld",ans);
	return 0;
}
特殊价值

题目入口

这道题的话主要是思路难想,思路就是前缀和记录,再尺取法更新答案。

#include<bits/stdc++.h>
using namespace std;
#define N 1000005
long long n,a[N],x[N];
long long s[N];
int main()
{
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a[i]);
		s[i]=s[i-1]+a[i];//前缀和记录异或的值和和
		x[i]=x[i-1]^a[i];
	}
	long long ans=s[n]-x[n];//最大值
	int ansl=1,ansr=1e9,l,r=1;//分别是答案的区间两端,和目前的区间两端
	for(l=1;l<=n;l++)
	{
		while(s[r]-s[l-1]-(x[r]^x[l-1])<ans&&r<=n)//尺取法,直到不满足
		{
			r++;
		}
		if(r>n)//越界了
			break;
		if(r-l<ansr-ansl)//更改答案
		{
			ansl=l;
			ansr=r;
		}
	}
	printf("%d %d\n",ansl,ansr);//输出
	return 0;
}
6 个赞

牛 膜拜大佬

排版好

精心整理

4 个赞