搜索

因为noip寄了,所以非常伤心,准备从2023开始加油!刷题!
今天是洛谷P1267
首先,枚举根节点,下一次选的点的值在1~4nn中,每选一个点,在该子树中的选点范围就会缩小,此时我们考虑用搜索,但它应该过不了,再考虑dp,f[i][j][k]表示以i为子树的根节点,以[j,k]为子树的选数范围,但ijk=(4nn)^3,依然过不了,但是对于一个以i根节点的子树来说,它选数范围的左端点或右端点一定是i的父结点的值,且每一个结点的父节点的可能性只有三个,因此记搜加map可过。
代码:

#include<iostream>
#include<map>
#include<cmath>
#define int long long
using namespace std;
int n;
struct node{
	int to;
	int nxt;
}edge[10010];
int head[4010],tot;
void addedge(int u,int v){
	edge[++tot].to=v;
	edge[tot].nxt=head[u];
	head[u]=tot;
}
int dian[4010];
map<pair<int,pair<int,int> >,int> mp;
int f[5][4][20];
int dfs(int u,int l,int r){
	if(mp[make_pair(u,make_pair(l,r))]!=0)return mp[make_pair(u,make_pair(l,r))];
	int zuo=0;
	int you=0;
	for(int i=head[u];i;i=edge[i].nxt){
		int v=edge[i].to;
		if(dian[v]>=l&&dian[v]<dian[u]){
			zuo=max(zuo,dfs(v,l,dian[u]-1));
		}
		if(dian[v]>dian[u]&&dian[v]<=r){
			you=max(you,dfs(v,dian[u]+1,r));
		}
	}
	return mp[make_pair(u,make_pair(l,r))]=1+zuo+you;
}
signed main(){
	cin>>n;
	for(int i=1;i<=4;i++){
		for(int j=1;j<=n*n;j++){
			cin>>dian[(i-1)*n*n+j];
		}
		for(int j=1;j<=n*n;j++){
			if((int)(sqrt(j))*(int)(sqrt(j))!=j){
				addedge((i-1)*n*n+j,(i-1)*n*n+j+1);
			}
			if((int)(sqrt(j-1))*(int)(sqrt(j-1))!=j-1){
				addedge((i-1)*n*n+j,(i-1)*n*n+j-1);
			}
			int heng=sqrt(j-1)+1;
			if((heng+j)%2==0){
				if(heng<n){
					addedge((i-1)*n*n+j,(i-1)*n*n+j+2*heng);
				}
			}
			else{
				if(heng>1){
					addedge((i-1)*n*n+j,(i-1)*n*n+j-2*heng+2);
				}
			}
		}
		for(int j=0;j<=n;j++){
			f[i][2][j]=(i-1)*n*n+j*j;
		}
		for(int j=1;j<=n;j++){
			f[i][1][j]=f[i][2][j-1]+1;
		}
		for(int j=1,k=f[i][1][n];j<=n;j++,k+=2){
			f[i][3][j]=k;
		}
	}
	for(int i=1;i<=n;i++){
		addedge(f[1][2][i],f[2][1][i]);
		addedge(f[2][1][i],f[1][2][i]);
		addedge(f[1][1][i],f[3][2][i]);
		addedge(f[3][2][i],f[1][1][i]);
		addedge(f[3][1][i],f[2][2][i]);
		addedge(f[2][2][i],f[3][1][i]);
		addedge(f[2][3][i],f[4][2][i]);
		addedge(f[4][2][i],f[2][3][i]);
	}
	for(int i=1,j=n;i<=n;i++,j--){
		addedge(f[1][3][i],f[4][1][j]);
		addedge(f[4][1][j],f[1][3][i]);
		addedge(f[3][3][i],f[4][3][j]);
		addedge(f[4][3][j],f[3][3][i]);
	}
	int ans=0;
	for(int i=1;i<=4*n*n;i++){
		ans=max(ans,dfs(i,1,4*n*n));
	}
	cout<<ans;
	return 0;
}

我对搜索和dp有一些小小的想法,搜索复杂度爆炸是一个状态询问多次,dp复杂度爆炸是询问许多无用状态,个人认为记搜加map可以解决,当然,dp经过一定处理也可以完成。除此以外,dp的状态查询大部分都是有一定顺序的,若是排不出顺序的,可以用搜索解决。

学习折半搜索。
洛谷P4799
1.直接搜,比较简单的搜索,但只能过前两个子任务。

#include<iostream>
#include<algorithm>
using namespace std;
int n;
long long m;
long long a[50];
bool cmp(long long x,long long y){
	return x<y;
}
long long dfs(int x,long long y){
	if(x==n){
		return 1;
	}
	long long ans=1;
	for(int i=x+1;a[i]+y<=m&&i<=n;i++){
		ans=ans+dfs(i,y+a[i]);
	}
	return ans;
}
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	sort(a+1,a+n+1,cmp);
	cout<<dfs(0,0);
	return 0;
} 

2.背包dp,可过1,3两个子任务。

#include<iostream>
using namespace std;
int n;
long long m;
long long a[1000010];
long long f[1000010];
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	f[0]=1;
	for(int i=1;i<=n;i++){
		for(int j=m;j>=a[i];j--){
			f[j]+=f[j-a[i]];
		}
	}
	long long ans=0;
	for(int j=0;j<=m;j++){
		ans+=f[j];
	}
	cout<<ans;
	return 0;
}

3但第二个子任务背包dp空间不可行,考虑day1的对dp的多余状态的优化,unordered_map,可过1,2,3三个子任务。

#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
int n;
long long m;
long long a[50];
long long dui[10000010];
long long b[10000010];
int cnt2=0;
long long c[10000010];
int cnt=0; 
bool cmp(long long x,long long y){
	return x<y;
}
bool cnp(long long x,long long y){
	return x>y;
}
unordered_map<long long ,long long> f;
int main(){
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		cin>>a[i];
	}
	sort(a+1,a+n+1,cnp);
	f[0]=1;
	dui[++cnt]=0;
	for(int i=1;i<=n;i++){
		for(int j=cnt;j>=1;j--){
			c[cnt-j+1]=dui[j];
		}
		int l=0,r=cnt;
		while(l<r){
			int mid=(l+r+1)/2;
			if(a[i]+dui[mid]>m){
				r=mid-1;
			}
			else{
				l=mid;
			}
		}
		cnt2=0;
		for(int j=l;j>=1;j--){
			if(f[dui[j]+a[i]]==0){
				b[++cnt2]=dui[j]+a[i];
			}
			f[dui[j]+a[i]]+=f[dui[j]];
		}
		int cnt1=cnt;
		int j=cnt1,k=cnt2;
		cnt=0;
		while(j>=1||k>=1){
			if(j<1){
				dui[++cnt]=b[k];
				k--;
				continue;
			}
			if(k<1){
				dui[++cnt]=c[j];
				j--;
				continue;
			}
			if(c[j]<b[k]){
				dui[++cnt]=c[j];
				j--;
			}
			else{
				dui[++cnt]=b[k];
				k--;
			}
		}
	}
	long long ans=0;
	for(int i=1;i<=cnt;i++){
		ans+=f[dui[i]];
	}
	cout<<ans;
	return 0;
}

4.考虑我们只需要知道总方案数是多少,并不需要知道各个状态的方案数是多少,第四个子任务n<=40,将它折半,就是两个第二个子任务,每个第二个子任务的状态数最多是2^n个,这样对两个第二个子任务分别按状态大小排序,前缀和加双指针,在第二个子任务中用map记录每个状态的方案数。

#include<iostream>
#include<algorithm>
#include<unordered_map>
using namespace std;
unsigned int n;
unsigned long long m;
unsigned long long a[50];
unordered_map<unsigned long long,unsigned long long>mp1;
unordered_map<unsigned long long,unsigned long long>mp2;
unsigned long long dui1[1100010];
unsigned int cnt1=0;
unsigned long long dui2[1100010];
unsigned int cnt2=0;
inline void dfs1(unsigned int x,unsigned long long y){
	if(x==n){
		return ;
	}
	for(int i=x+1;a[i]+y<=m&&i<=n;i++){
		if(mp1[y+a[i]]==0){
			dui1[++cnt1]=y+a[i];
		}
		mp1[y+a[i]]++;
		if(y+a[i]<m)dfs1(i,y+a[i]);
	}
}
inline void dfs2(unsigned int x,unsigned long long y){
	if(x==n){
		return ;
	}
	for(int i=x+1;a[i]+y<=m&&i<=n;i++){
		if(mp2[y+a[i]]==0){
			dui2[++cnt2]=y+a[i];
		}
		mp2[y+a[i]]++;
		if(y+a[i]<m)dfs2(i,y+a[i]);
	}
}
inline bool cmp(unsigned long long x,unsigned long long y){
	return x<y;
}
unsigned long long qian[1100010];
int main(){
	cin>>n>>m;
	for(unsigned int i=1;i<=n;i++){
		cin>>a[i];
	}
	sort(a+1,a+n+1,cmp);
	dui2[++cnt2]=0;
	mp2[0]=1;
	dfs2(n/2,0);
	n/=2;
	dui1[++cnt1]=0;
	mp1[0]=1;
	dfs1(0,0);
	sort(dui1+1,dui1+cnt1+1,cmp);
	sort(dui2+1,dui2+cnt2+1,cmp);
	for(unsigned int j=1;j<=cnt2;j++){
		qian[j]=qian[j-1]+mp2[dui2[j]];
	}
	int j=cnt2;
	unsigned long long ans=0;
	for(unsigned int i=1;i<=cnt1&&j>0;i++){
		while(j>0&&dui2[j]+dui1[i]>m){
			j--;
		}
		if(j>0){
			ans+=mp1[dui1[i]]*qian[j];
		}
	}
	cout<<ans;
	return 0;
}
posted @ 2022-12-31 23:01  zzzzzz2  阅读(34)  评论(0编辑  收藏  举报