专题-搜索&模拟

前言:

被某位小学妹搞得写不进去题了,想上吊。。。所以我来水题解啦~~因为都是洛谷原题,我就不粘题面啦。
\(ps:\) 是按照我写题的顺序写的题解,不是按照原本题目的排列顺序写的!

\(T1:\)小木棍

思路:

老熟题啦!看看今天的标题也知道这道题用什么解法。首先,我们可以确定\(ans\)一定大于等于这几段小木棍的最大值,小于等于这些木棍长度之和。然后考虑剪枝:显然最小长度一定得是总长度的因数。然后我们开始进行搜索,具体实现细节见代码注释哦~~

代码:

#include<bits/stdc++.h>
using namespace std;
int n,a[70],sum,ans,minn=1e9,maxn,cnt[51];
inline void dfs(int num,int m,int s){//剩余木棍数量 剩余木棍最大值 目前这一组的长度和 
	if(num==0){//搜丸啦~~ 
		cout<<ans;//输出答案 
		exit(0);//强制结束 
	}
	if(s==ans){
		dfs(num-1,maxn,0);//这一组满啦,该下一组啦。 
		return;
	} 
	if(m==minn-1) return;//最大值小于最小值?违背人类伦理,结束结束。 
	for(int i=m;i>=minn;i--){//倒序遍历木棍长度 
		if(cnt[i]&&i+s<=ans){//如果存在且符合题意 
			--cnt[i];//收缴收缴! 
			dfs(num,i,i+s);//继续搜索 
			++cnt[i];//记得回溯~~ 
			if(s==0||s+i==ans) break;//如果能回溯回来肯定证明之前出bug了,结束结束! 
		}
	}
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum+=a[i];
		++cnt[a[i]];
		minn=min(minn,a[i]);//最小值 
		maxn=max(maxn,a[i]);//最大值 
	}
	for(int i=maxn;i<sum;i++) //遍历可能长度 
		if(sum%i==0){//剪枝 
			ans=i;记录答案 
			dfs(sum/i,maxn,0);//搜索 
		}
	cout<<sum;//都不符合条件,只能是木棍长度之和了。 
	return 0;
}

\(T2:\)买瓜

思路:

当然还是使用折半搜索。将所有瓜分为前一半和后一半,分别进行搜索。对于一个瓜,我们可以分为三种状态:全选,全不选,选一半(要劈开的喔)。大体思路就这样愉快的结束啦~~ 其他的具体细节键代码叭~~

代码:

#include<iostream>
#include<map>
#include<algorithm>
#define int long long
using namespace std;
const int N=1e7+10;
int n,ans=1e18;
double m,a[N];
map<double,bool> flag; //存储当前值是否搜到过
map<double,int> step;//step[i]为变成i最小的次数
inline void dfs(double sum,int id,int cnt){
	if(sum>m||id>n/2) return ;//判边界
	if(step[sum]<cnt&&flag[sum]) return ; //若原来的次数比现在快不用再搜索了
	if(sum==m) { //若组合出了直接更新
		ans=min(ans,cnt);
		return ;
	}
	step[sum]=cnt;flag[sum]=1;//存储
	dfs(sum+a[id+1],id+1,cnt);
	dfs(sum,id+1,cnt);
	dfs(sum+a[id+1]/2,id+1,cnt+1);//三种状态 
}
inline void DFS(double sum,int id,int cnt){
	if(sum>m||id>n) return ; //判边界 
	if(step.count(m-sum)){//若原来能组成出m-sum 
		ans=min(ans,cnt+step[m-sum]); //更新 
		return ;
	}
	DFS(sum+a[id+1],id+1,cnt);
	DFS(sum,id+1,cnt);
	DFS(sum+a[id+1]/2,id+1,cnt+1);//三种状态 
}
signed main(){
	ios::sync_with_stdio(false);
	cin>>n>>m;
	for(int i=1;i<=n;i++) cin>>a[i];
	sort(a+1,a+1+n);
	dfs(0,0,0);DFS(0,n/2,0);//前n/2 后 n-n/2 个
	cout<<(ans>1e18/2?-1:ans)<<'\n'; //判无解
	return 0;
}

\(T3:\) [NOIP2015 提高组] 斗地主 加强版

思路:

这道题的主流思想有两个:四维\(dp\)和超大模拟。显然那个超大模拟码量太大,作为一个懒人,我果断选择了四维\(dp\)。(一会去给你们贺一个大模拟的链)。在这里把出牌方式分为顺子和其他(四带二,三带一,三代二等等)。这里用搜索处理顺子,其他则是用\(dp\)预处理出来,状态为\(dp[a][b][c][d]\)表示有\(a\)个单张牌\(b\)组对\(c\)个三张相同牌\(d\)个炸弹。其他具体细节还是见代码哦~
\(ps:\) 这里规定数码相同的牌为同一种牌。

代码:

#include<iostream>
#include<cstring>
using namespace std;
int T,n,y,a,b,ans,c[5],s[15],dp[30][15][10][8];
inline void dfs(int x){//搜索 出x次牌 
	if(x>=ans) return ;//已经比之前的答案要大 那一定不优。 
	memset(c,0,sizeof(c));//清空 
	for(int i=2;i<=14;i++) c[s[i]]++;//记录一下为几顺子(单顺子,双顺子,三顺子...) 
	ans=min(ans,x+dp[c[1]+s[0]][c[2]][c[3]][c[4]]);//有一只鬼 
	if(s[0]==2) ans=min(ans,x+dp[c[1]][c[2]][c[3]][c[4]]+1);//有双鬼 
	for(int a=1;a<=3;a++){//枚举一下几顺子 
		for(int i=3;i<=14;i++){//枚举开始的位置(A被设为了14,2不能参与顺子,所以从3开始) 
			int cnt=0;//记录能连几个 
			for(int j=i;j<=14;j++){//枚举可能的结束位置 
				if(s[j]>=a) cnt++;//结束的位置有足够的牌 
				else break;
				if(a*cnt>=5){//充分发挥人类智慧,发现a*cnt至少大于等于5是符合顺子的要求 
					for(int k=i;k<=j;k++) s[k]-=a;//打出a顺子 
					dfs(x+1);//搜索 
					for(int k=i;k<=j;k++) s[k]+=a;//回溯 
				}
			}
		}
	}
}
signed main(){
	ios::sync_with_stdio(false);
	//因为至多有23张牌,所以炸弹,对子等都有一定的数量限制 
	for(int d=0;d<=5;d++){//炸弹的数量 
		for(int c=0;c<=8;c++){//三张牌的数量 
			for(int b=0;b<=12;b++){//对子的数量 
				for(int a=0;a<=23;a++){//单张牌的数量 
					if(!a&&!b&&!c&&!d) continue;//没有牌 
					y=0x3f3f3f3f;//初始化 
					if(a) y=min(y,dp[a-1][b][c][d]+1);//出单张牌 
					if(b) y=min(y,dp[a][b-1][c][d]+1);//出一个对 
					if(c) y=min(y,dp[a][b][c-1][d]+1);//出一个三张牌 
					if(d) y=min(y,dp[a][b][c][d-1]+1);//出一个炸弹 
					if(a&&c) y=min(y,dp[a-1][b][c-1][d]+1);//出一个三带一 
					if(b&&c) y=min(y,dp[a][b-1][c-1][d]+1);//出一个三带二 
					if(a>=2&&d) y=min(y,dp[a-2][b][c][d-1]+1);//出一个四带两单 
					if(b>=2&&d) y=min(y,dp[a][b-2][c][d-1]+1);//出一个四带两对 
					if(b) y=min(y,dp[a+2][b-1][c][d]);//把一个对拆违两张单牌 
					if(c) y=min(y,dp[a+1][b+1][c-1][d]);//把一个三张牌拆为一张单牌加一个对子 
					if(d) y=min(y,dp[a+1][b][c+1][d-1]);//把炸弹拆为一个三张牌加一张单牌 
					if(d) y=min(y,dp[a][b+2][c][d-1]);//把炸弹拆为两个对子 
					dp[a][b][c][d]=y;//记录答案 
				}
			}
		}
	}
	cin>>T>>n;
	while(T--){
		memset(s,0,sizeof(s));//多组数据清空 
		for(int i=1;i<=n;i++){
			cin>>a>>b;
			if(a==1) a=14;//为了方便找顺子 
			s[a]++;//记录每种牌的数目 
		}ans=0x3f3f3f3f;
		dfs(0);
		cout<<ans<<'\n';
	}
	return 0;
}

\(T4:\)Anya and Cubes

思路:

这个搜索的思路还是蛮好想的,对于一个数有三种状态:选,不选,变为阶乘。不过可能有人一看到\(1e9\)的阶乘就慌了(比如我),莫慌莫慌,再偷偷看一眼数据范围:嗯,\(s≤10^{16}\),大概估算一下,能变为阶乘的数最大超不过20,那好办了,直接与处理呗。然后就没有什么难点了,其他的还是见代码哦~~

代码:

#include<iostream>
#include<algorithm>
#include<unordered_map>
#define int long long
using namespace std;
const int N=30;
int n,k,s,ans,a[N],fac[25];
unordered_map<int,int> num[N];//用map会被卡!!!
inline void dfs(int sum,int id,int cnt){//当前和 当前遍历到第id位 共用了cnt个! 
	if(sum>s||cnt>k) return ;//当前和大于总和或当前阶乘数大于总阶乘数 
	if(id>n/2){//遍历了一半 
		num[cnt][sum]++;//记录一下 
		return ;
	}
	dfs(sum+a[id],id+1,cnt);//选这个数 
	dfs(sum,id+1,cnt);//不选这个数 
	if(a[id]<=20) dfs(sum+fac[a[id]],id+1,cnt+1);//把它变成阶乘 
}
inline void DFS(int sum,int id,int cnt){
	if(sum>s||cnt>k) return ;//同10 
	if(id>n){//tong11 
		for(int i=0;i<=k-cnt;i++) ans+=num[i][s-sum]; //如果前半程有能与当前情况匹配成答案的加上 
		return ;
	}
	DFS(sum+a[id],id+1,cnt);//选数 
	DFS(sum,id+1,cnt);//不选数 
	if(a[id]<=20) DFS(sum+fac[a[id]],id+1,cnt+1);//阶乘 
}
signed main(){
	ios::sync_with_stdio(false);
	fac[1]=1;
	for(int i=2;i<=20;i++) fac[i]=fac[i-1]*i;//预处理阶乘 
	cin>>n>>k>>s;
	for(int i=1;i<=n;i++) cin>>a[i];
	dfs(0,1,0);DFS(0,n/2+1,0);//折半搜索 
	cout<<ans<<'\n';
	return 0;
}

\(T5:\)Expression

思路:

乍一看没有思路,再一看还是没有思路。那整体看没法做只能一位一位的看喽。既然放在这了肯定是折半搜索,怎么搜呢?从个位开始往前搜,也分为三种情况:给\(x\)加数,给\(y\)加数,给\(z\)加数。具体实现还是见代码呗~~

\(ps:\)这题搜索传的参比较多,注意不要调错参数,不然你会怒调一小时无果。

代码(先别急着打,这是假的!):

#include<iostream>
#define int long long
using namespace std;
int x,y,z,ansa,ansb,ansc,ans=0x3f3f3f3f,p[20];char ch;
inline void dfs(int x,int y,int z,int carry,int a,int b,int c,int len,int dep){
	//现在的x,现在的y,现在的z,进位,答案的a,答案的b,答案的c,总答案的长度,数字位数 
	if(len>=ans) return ;//如果之前有更优解则这次搜索肯定不优 
	if(!x&&!y&&!z&&!carry){//已经加完了 
		ans=len;//记录长度 
		ansa=a;//记录a的答案 
		ansb=b;//记录b的答案 
		ansc=c;//记录c的答案 
		return ;
	}
	if(!z){//c的最高位已经没有了 
		int res=x+y+carry;//c上还差的数 
		int cnt=0;//记录位数 
		while(res){
			cnt++;
			res/=10;
		}
		dfs(0,0,0,0,a+p[dep]*x,b+p[dep]*y,c+p[dep]*(x+y+carry),len+cnt,dep+1);//都加上去继续搜索 
		return ;
	}
	if((x+y+carry)%10==z%10){//三个数个位符合人类逻辑 
		dfs(x/10,y/10,z/10,(x%10+y%10+carry)/10,a+(x%10)*p[dep],b+(y%10)*p[dep],c+(z%10)*p[dep],len,dep+1);//舍弃个位继续向前搜 
		return ;
	}
	dfs(x*10+(z%10-y%10-carry+10)%10,y,z,carry,a,b,c,len+1,dep);//给x上加数 
	dfs(x,y*10+(z%10-x%10-carry+10)%10,z,carry,a,b,c,len+1,dep);//给y上加数 
	dfs(x,y,z*10+(x%10+y%10+carry)%10,carry,a,b,c,len+1,dep);//给z上加数 
}
signed main(){
	cin>>x>>ch>>y>>ch>>z;
	p[0]=1;
	for(int i=1;i<=18;i++) p[i]=p[i-1]*10;//初始化位数 
	dfs(x,y,z,0,0,0,0,0,0);//搜索 
	cout<<ansa<<"+"<<ansb<<"="<<ansc;
	return 0;
}

\(update:2025/08/23\)

\(ps:\)有个同机房大佬发现了代码漏洞,几乎把整个机房都给\(hack\)了。
\(hack\)数据:\(2+4324=53245\)
应得答案:\(2+543243=543245\)
假代码答案:\(10002+43243=53245\)

问题出在哪了呢?我们只是往前搜非零的数了,但似乎并没有考虑两个非零数之间有否有零,有几个零的问题。不过也不是什么大问题,只要在原本就多的参数上雪上加霜一下就好了。再加两个新的参数\(la,lb\)来分别表示我们给\(x,y\)的前面加上的零的个数。

新代码:

#include<iostream>
#define int long long
using namespace std;
int x,y,z,ansa,ansb,ansc,ans=0x3f3f3f3f,p[20];char ch;
inline void dfs(int x,int y,int z,int carry,int a,int b,int c,int len,int dep,int la,int lb){
	if(len>=ans) return ;
	if(!x&&!y&&!z&&!carry){
		ans=len;
		ansa=a;
		ansb=b;
		ansc=c;
		return ;
	}
	if(!z){
		int res=x+y+carry;
		int cnt=0;
		while(res){
			cnt++;
			res/=10;
		}
		dfs(0,0,0,0,a+p[dep]*x,b+p[dep]*y,c+p[dep]*(x+y+carry),len+cnt,dep+1,la,lb);
		return ;
	}
	if((x+y+carry)%10==z%10){
		if(!x) la++;
		if(!y) lb++;
		dfs(x/10,y/10,z/10,(x%10+y%10+carry)/10,a+(x%10)*p[dep],b+(y%10)*p[dep],c+(z%10)*p[dep],len,dep+1,la,lb);
		return ;
	}
	dfs(x*10+(z%10-y%10-carry+10)%10,y,z,carry,a,b,c,len+1+la,dep,0,lb);
	dfs(x,y*10+(z%10-x%10-carry+10)%10,z,carry,a,b,c,len+1+lb,dep,la,0);
	dfs(x,y,z*10+(x%10+y%10+carry)%10,carry,a,b,c,len+1,dep,la,lb);
}
signed main(){
	cin>>x>>ch>>y>>ch>>z;
	p[0]=1;
	for(int i=1;i<=18;i++) p[i]=p[i-1]*10;
	dfs(x,y,z,0,0,0,0,0,0,0,0);
	cout<<ansa<<"+"<<ansb<<"="<<ansc;
	return 0;
}

\(T6:\)[abc336_f]Rotation Puzzle

思路: 未完...

代码:

#include<bits/stdc++.h>
using namespace std;
int a[9][9],b[9][9],n,m;
unordered_map<unsigned long long,int> mp1,mp2;
inline unsigned long long hhash(int a[9][9])
{
	unsigned long long h = 0;
	for(int i = 1;i <= n;i++)
		for(int j = 1;j <= m;j++)
			h = h*7654321 + a[i][j];
	return h;
}
inline unsigned long long hashh(int a[9][9])
{
	unsigned long long h = 0;
	for(int i = 1;i <= n;i++)
		for(int j = 1;j <= m;j++)
			h = h*1234567 + a[i][j];
	return h;
}
inline void rotate(int x,int y)
{
	memcpy(b,a,sizeof(a));
	for(int i = 0;i <= n-2;i++)
		for(int j = 0;j <= m-2;j++)
			b[i+x][j+y] = a[n-2-i+x][m-2-j+y];
	memcpy(a,b,sizeof(b));
}
int ans = 100;
inline void dfs1(int step)
{
	unsigned long long h1 = hhash(a),h2 = hashh(a);
	if(!mp1[h1]) mp1[h1] = step-1;
	else mp1[h1] = min(mp1[h1],step-1);
	if(!mp2[h2]) mp2[h2] = step-1;
	else mp2[h2] = min(mp2[h2],step-1);
	if(step > 10) return;
	rotate(1,1);
	dfs1(step+1);
	rotate(1,1);
	rotate(1,2);
	dfs1(step+1);
	rotate(1,2);
	rotate(2,1);
	dfs1(step+1);
	rotate(2,1);
	rotate(2,2);
	dfs1(step+1);
	rotate(2,2);
}
inline void dfs2(int step)
{
	unsigned long long h1 = hhash(a),h2 = hashh(a);
	if(mp1[h1] && mp2[h2]) ans = min(ans,mp1[h1]+step-1);
	if(step > 10) return;
	rotate(1,1);
	dfs2(step+1);
	rotate(1,1);
	rotate(1,2);
	dfs2(step+1);
	rotate(1,2);
	rotate(2,1);
	dfs2(step+1);
	rotate(2,1);
	rotate(2,2);
	dfs2(step+1);
	rotate(2,2);
}
int main()
{
	cin >> n >> m;
	bool flag = 1;
	for(int i = 1;i <= n;i++)
		for(int j = 1;j <= m;j++)
		{
			cin >> a[i][j];
			if(a[i][j] != (i-1)*m+j) flag = 0;
		}
	if(flag)
	{
		cout << 0 << endl;
		return 0;
	}
	dfs1(1);
	for(int i = 1;i <= n;i++)
		for(int j = 1;j <= m;j++)
			a[i][j] = (i-1)*m + j;
	dfs2(1);
	if(ans > 20) cout << -1;
	else cout << ans;
	return 0;
}

\(T7:\)Distinct Paths

思路:未完...

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=35,mod=1e9+7;
int n,m,k,c[N],a[N][N],dp[N][N];
int dfs(int x,int y){//dfs使用int类型方便记录答案 
	if(x==n+1) return 1;
	int st=dp[x-1][y]|dp[x][y-1];
	if(n+m-x-y+1>k-__builtin_popcount(st))return 0;//剪枝1:可行性剪枝 
	int tmp=-1,res=0;//tmp记录第一次搜索的答案,res统计总方案数 
	for(int i=1;i<=k;i++){//i枚举该格子填充的颜色 
		if(a[x][y]&&a[x][y]!=i)continue;//该格子已经被钦定颜色 
		if(st&(1<<i-1))continue;//之前已经填充过i这个颜色,不能重复填 
		dp[x][y]=st|(1<<i-1),c[i]++;//可以填i 
		if(c[i]==1){//如果是剩余的颜色(在后面没有出现过(被钦定过))
			if(tmp==-1)tmp=(y==m?dfs(x+1,1):dfs(x,y+1));//第一个这种颜色,搜索并记录答案tmp 
			res+=tmp,res%=mod;//不是第一次的话直接加答案就行了 
		}else res+=(y==m?dfs(x+1,1):dfs(x,y+1)),res%=mod;
		c[i]--;//回溯 
	}
	return res;
}
signed main(){
	cin>>n>>m>>k;
	if(n+m-1>k){cout<<0;return 0;}
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			cin>>a[i][j],c[a[i][j]]++;//c:辅助第二个剪枝 
	cout<<dfs(1,1);
	return 0;
}
posted @ 2025-08-18 21:48  晏清玖安  阅读(34)  评论(5)    收藏  举报