单词+算法

单词+算法

考研

背诵了List6这一部分单词。

P1120 小木棍(剪枝)

题意:有一些同样长的小木棍,然后随意砍成几段,直到每段的长都不超过 50。

现在想把小木棍拼接成原来的样子,但是不知道开始时有多少根木棍和它们的长度。

给出每段小木棍的长度,编程帮他找出原始木棍的最小可能长度。

剪枝:设原始木棍长度为m

  • 原始木棍长度m一定是所有长度和的因数
  • m最小也是现在所有木棍中最长的长度,因此可以从$a_i$的最大值开始枚举
  • 对于一个被枚举的$a_i$,若$a_i$不满足要求,则同长度的其他木棍也不满足
  • 枚举新木棍时,优先考虑较长的木棍
  • 若当前原始木棍的剩余长度等于当前考虑木棍长度,但搜索后不满足要求,则无需考虑更短木棍,直接回溯。(因为短棍拼出当前长度与长棍一致,但短棍更灵活,留到后面更优,长棍不符合要求,则短棍拼接也不可能符合要求)
  • 若当前木棍长度就是设定原始木棍长度,搜索后不满足也直接回溯
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e2;
int n,a[N],len[N],sum,pre[N],d;
void dfs(int u,int k,int p){
	//当前这一只长棍还有u没有拼,
	//还有k根长棍没有拼
	//当前长棍的最短短棍长度是p
	if(u==0){//这一根长棍拼完了
		dfs(d,k-1,a[n]);//拼下一根长棍
		return ;
	}
	if(k==0){//所有长棍都拼完了
		cout<<d;//这是找到的第一个,也就是最短长度
		exit(0);
	}
	p=min(p,u);//更新最短长度
	while(p&&len[p]==0) --p;//如果u不存在相等长度的,找到最接近的
	while(p){//pre[1]会=0
		if(len[p]){//如果p长度还存在
			--len[p];//保存现场
			dfs(u-p,k,p);
			++len[p];//恢复现场
			if((u==p)||(u==d))//如果这都没有exit,证明长度不合适,后面更小的也不合适
				return;//剪枝
			p=pre[p];//找到更短的长度
		}
		else//如果p长度不存在
			p=pre[p];
	}
}
int main(){
	cin>>n;
	for(int i=1;i<=n;i++){
		cin>>a[i];
		sum+=a[i];
		++len[a[i]];//长度为a[i]的个数
	}
	sort(a+1,a+n+1);
	for(int i=1;i<=n;i++){//预处理长度为i的上一个长度
		if(a[i]!=a[i-1]) 
			pre[a[i]]=a[i-1];
	}
	for(d=a[n];d*2<=sum;++d){//至少有两根
		if((sum%d)==0)//因为sum会由多根同一长度的组成
			dfs(d,sum/d,a[n]);
	}
	cout<<sum;//最终都没有,全部合起来
	return 0;
}

迭代加深搜索IDDFS:枚举搜索深度来进行搜索

P1763 埃及分数(IDDFS)

题意:给出一个分子a和分母b组成,分解成一堆互不相同的单位分数(分子为1)。单位分数的数量越少越好,如果数量一样,最小的那个单位分数越大越好(即分母越大越好)。

剪枝我也没看懂,P1763 题解

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 11,INF = 1e7;
int dep;int max_a;
int tmp[N],ans[N];
int a,b,c;
bool flag;
int GCD(const int x,const int y){
	return y ? GCD(y, x % y) : x;
}
void dfs(const int a,const int b,const int x){
	if(x > dep)
		return;
	if(a == 1){
		tmp[x] = b;
		if(!flag || tmp[dep] < ans[dep])
			for(int i = 1;i <= dep;i++)
				ans[i] = tmp[i];
		flag = 1;
		return;
	}
	if(x == dep - 1){
		int l = ((b << 2) / (a * a)) + 1,r = min(((max_a << 1)) / a,max_a * max_a / b);
		for(int i = l;i <= r;i++){
			int delta = a * a * i * i - ((b * i) << 2);
			int Sqrt = sqrt(delta);
			if(Sqrt * Sqrt != delta || ((a * i - Sqrt) & 1))
				continue;
			tmp[x] = (a * i - Sqrt) >> 1;tmp[x + 1] = (a * i + Sqrt) >> 1;
			if(tmp[x] <= tmp[x - 1] || tmp[x + 1] <= tmp[x])
				continue;
			if(!flag || tmp[dep] < ans[dep]){
				for(int j = 1;j <= dep;j++)
					ans[j] = tmp[j];
				flag = true;
			}
		}
		return;
	}
	int l = max((b + a - 1) / a,tmp[x - 1] + 1);
	int r = min((((dep - x + 1) * b + a - 1) / a),max_a);
	if(flag && r >= ans[dep])
		r = ans[dep] - 1;
	for(int i = l;i <= r;i++){
		tmp[x] = i;
		const int A = a * i - b,B = b * i;
		const int gcd = GCD(A,B);
		dfs(A / gcd,B / gcd,x + 1);
	}
}
signed main(){
	ios::sync_with_stdio(false);
	cin.tie(0);cout.tie(0);
	cin >> a >> b;
	c = GCD(a,b);
	a /= c;b /= c;
	tmp[0] = 1;
	for(dep = 1;dep <= N - 1;dep++){
		ans[dep] = tmp[dep] = INF + 1;
		for(max_a = 100000;max_a <= INF;max_a *= 10){
			dfs(a,b,1);
			if(flag){
				for(int i = 1;i <= dep;i++)
					cout << ans[i] << ' ';
				return 0;
			}
		}
	}
	return 0;
}

双向深度优先搜索:数据分为两部分,分别进行DFS

CF525E Anya and Cubes(双向DFS)

题意:给出一个长度为 n 的数组 a ,可以选任意个数组成数组 b ,并在 b 中选出不超过 k 个数,将这不超过 k 个数变成自己的阶乘。最后求数组 b 的各个元素之和为 S 的方案数。

题解:前 $n/2$ 个数一组,后 $n-n/2$ 个数一组,分别DFS。前半组搜索结束时,用Hash记录该状态的方案数,然后对后半部分进行DFS,设搜索结束时状态是选了 i 个阶乘,和为 s ,即为 $(i,s)$,在后半部分搜索结束时,将答案加上 $\sum_{t=0}^{k-i} f[(t,S-s)]$ 即可,$f$ 是前半部分对应方案数。

整体DFS $O(3^n)$,若合并两部分答案复杂度为 $k$,双向DFS $O(k · 3^{n/2})$。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e3;
int n,t,m;
ll S,ans,a[N];
int b[N];
map<pair<int,ll>,ll> f,g;//存放前半部分数量
ll fact(ll x){//阶乘
	ll ret;
	for(ret=1;x;ret*=x--)
		if((S/ret)<x) return -1;//未看懂
	return ret;
}
void check1(){
	int cnt=0;//阶乘个数
	ll sum=0;//和
	for(int i=1;i<=m;i++){
		if(b[i]==1) sum+=a[i];//1表示在b中但不会变阶乘
		else if(b[i]==2){//2表示在b中会变成阶乘
			ll s=fact(a[i]);
			if((s==-1)||(++cnt>t)||((s+sum)>S)) return;
			//这是加上s后当前和大于S和选择阶乘数限制的情况
			sum+=s;
		}
	}
	++f[{cnt,sum}];//累加方案数
	++f[{-1,sum}];//标记sum这一和出现过
}
void dfs1(int u){
	if(u>m) check1();//到达搜索终点
	else{
		for(int i=0;i<=2;i++){//枚举每个位置的三进制数
			b[u]=i;
			dfs1(u+1);
		}
	}
}
ll qry(int x,ll s){//统计i从0到x,f[{i,s}]的和
	//记忆化提高效率
	if(g.count({x,s})) return g[{x,s}];
	return g[{x,s}]=f[{x,s}]+(x?qry(x-1,s):0);
}
void check2(){
	int cnt=0;ll sum=0;
	for(int i=m+1;i<=n;i++){//遍历后半部分的数
		if(b[i]==1) sum+=a[i];
		else if(b[i]==2){
			ll s=fact(a[i]);
			if((s==-1)||(++cnt>t)||((s+sum)>S)) return;
			sum+=s;
		}
	}
	if(f.count({-1,S-sum}))//如果S-sum出现过
		ans+=qry(t-cnt,S-sum);//累加答案
}
void dfs2(int u){
	if(u>n) check2();//选完了
	else{
		for(int i=0;i<=2;i++){//枚举状态
			b[u]=i;
			dfs2(u+1);
		}
	}
}
int main(){
	cin>>n>>t>>S;
	for(int i=1;i<=n;i++) cin>>a[i];
	m=n/2;
	dfs1(1);//前半部分
	dfs2(m+1);//后半部分
	cout<<ans;
	return 0;
}

双向广搜:从两个状态一起开始BFS

P1379 八数码难题

题意:在3*3的棋盘上,有8枚编号棋子,中间为0号空格,棋子可移动到空格中,给出初始布局和目标布局,找到最少步骤移动次数。

题解:每个状态可以映射为一个九位数,双向BFS。还可用A*或IDA*。

#include<bits/stdc++.h>
using namespace std;
int bg,ed=123804765;//开始与结束状态
int a[10];
map<int,int> rec[2];//1表示从结束状态开始的搜索,0是开始状态开始的
queue<pair<int,bool> > Q;
int change(int x,int op){
	int p;//0的位置
	for(int i=9;i;i--){//9位数变状态
		a[i]=x%10,x/=10;
		if(a[i]==0) p=i;
	} 
	if(op==1){
		if(p<=3) return -1;//边界
		swap(a[p],a[p-3]);
	}else if(op==2){
		if(p>=7) return -1;//边界
		swap(a[p],a[p+3]);
	}else if(op==3){
		if(p%3==1) return -1;//边界
		swap(a[p],a[p-1]);
	}else if(op==4){
		if(p%3==0) return -1;//边界
		swap(a[p],a[p+1]);
	}
	x=0;//变为9位数
	for(int i=1;i<=9;i++) x=x*10+a[i];
	return x;
}
void check(int x,pair<int,bool> h){
	if(x!=-1)
		if(rec[h.second^1].count(x)){
		//另一个方向的已到达这个状态
			cout<<rec[h.second^1][x]+rec[h.second][h.first]+1;
			exit(0);
		}else{//存入状态
		rec[h.second][x]=rec[h.second][h.first]+1;
		Q.push({x,h.second});
	}
}
int main(){
	cin>>bg;
	if(bg==ed){
		cout<<"0";
		return 0;
	}
	Q.push({bg,0});
	Q.push({ed,1});
	while(!Q.empty()){//双向BFS
		pair<int,bool> h=Q.front();
		Q.pop();
		int x=change(h.first,1);//0向上
		check(x,h);
		x=change(h.first,2);//0向下
		check(x,h);
		x=change(h.first,3);//0向左
		check(x,h);
		x=change(h.first,4);//0向右
		check(x,h);
	}
	return 0;
}