郑州大学2023-2024第一学期算法设计与分析——实验7(第五、六章)

前几天忙着复习,把这事儿忘了,要交了才想起来

1. 自然数拆分问题

一眼完全背包但数据范围足够小还要给出方案,于是考虑dfs

#include<bits/stdc++.h>
//#define int long long
#define endl "\n"

#define fir for(int i=1;i<=n;i++)
#define rif for(int i=n;i>=1;i--)

#define AC return
#define pleaseqwq 0
using namespace std;

//一些特定数据结构用 
typedef pair<int,int>PII;
typedef pair<string,int>PSI;
typedef pair<string,string>PSS;
//特定哈希用 
unordered_map<string,int>si;
unordered_map<int,int>ii;
//bfs用 
const int dx[4]={-1,0,1,0};
const int dy[4]={0,1,0,-1}; 

//常用 
const int N=1e5+10;
const int mod=2147483648;

//并查集用 
int p[N];

int find(int x){
	if(p[x]!=x) p[x]=find(p[x]);
	return p[x];
}


vector<string>v;
int n;

void dfs(int here,int now,string s){
	if(now>n) return ;
	if(now==n){
		v.push_back(s);
		return ;
	}
	for(int i=here;i<=n-now;i++){
		string str=s;
		if(str=="") str=s+to_string(i);
		else str=s+"+"+to_string(i);
		dfs(i,now+i,str);
	}
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(false);
	
	cin>>n;
	dfs(1,0,"");
	
	for(int i=0;i<v.size();i++) cout<<n<<"="<<v[i]<<endl;
	AC pleaseqwq;
}

看来dfs也好久没写了,交的时候错误百出。。。。

2.自然数拆分的方案数

完全背包,,,不展开了,自行搜索吧

与01背包不同的点在于状态转移时是拿已更新的状态更新将要更新的状态,01背包是拿未更新的状态更新将要更新的状态

这涉及01背包的优化为何要倒序进行

#include<bits/stdc++.h>
#define int long long
#define endl "\n"

#define fir for(int i=1;i<=n;i++)
#define rif for(int i=n;i>=1;i--)

#define AC return
#define pleaseqwq 0
using namespace std;

typedef pair<int,int>PII;
typedef pair<string,int>PSI;
typedef pair<string,string>PSS;

unordered_map<string,int>si;
unordered_map<int,int>ii;


const int N=5e3+10;
const int mod=2147483648;

int a[N];
int f[N];

void solve(){
	int n;
	cin>>n;
	memset(f,0,sizeof f);
	f[0]=1;
	//完全背包,考虑1-n这n个物品,每种物品可以无限使用
	//f[i][j]表示我们考虑前i个物品中,总和为j的方案数
	//由于我们此时状态转移与01背包不一致,是从已更新的状态转移过来的
	//因此此时f[i][j]+=f[i][j-1],第一维一致,故省去第一维
	for(int i=1;i<=n;i++)
		for(int j=i;j<=n;j++)
			f[j]=(f[j]+f[j-i])%mod;
	cout<<f[n]<<endl;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(false);
	
	int T;
	cin>>T;
	while(T--) solve();
	
	AC pleaseqwq;
}

3. 子集和问题

简单写个dfs还是TLE

也就是还得继续剪枝

可行性剪枝剪一下就行

由于题目不让自己再排序,所以优化搜索顺序行不通

#include<bits/stdc++.h>
//#define int long long
#define endl "\n"

#define fir for(int i=1;i<=n;i++)
#define rif for(int i=n;i>=1;i--)

#define AC return
#define pleaseqwq 0
using namespace std;

//一些特定数据结构用 
typedef pair<int,int>PII;
typedef pair<string,int>PSI;
typedef pair<string,string>PSS;
//特定哈希用 
unordered_map<string,int>si;
unordered_map<int,int>ii;
//bfs用 
const int dx[4]={-1,0,1,0};
const int dy[4]={0,1,0,-1}; 

//常用 
const int N=1e5+10;
const int mod=2147483648;

//并查集用 
int p[N];

int find(int x){
	if(p[x]!=x) p[x]=find(p[x]);
	return p[x];
}


int a[N];
bool st[N];
int n,k;

bool success=false;

bool dfs(int i,int now){
	now+=a[i];
	if(now>k) return false;
	if(now==k&&!success){
		for(int i=1;i<=n;i++){
			if(st[i]) cout<<a[i]<<' ';
		}
		success=true;
		return true;
	}
	for(int j=i+1;j<=n;j++){
		if(success) break;
		st[j]=true;
		dfs(j,now);
		st[j]=false;
	}
	return false;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(false);
	
	cin>>n>>k;
	int sum=0,mi=0x3f3f3f3f;
	fir{
		cin>>a[i];
		sum+=a[i];
		mi=min(mi,a[i]);
	}
	//剪枝,如果总和都不够,或最小值都大于,直接剪掉。可行性剪枝 
	if(mi<=k&&sum>=k){
		for(int i=1;i<=n;i++){
			st[i]=true;
			dfs(i,0);
			st[i]=false;
			if(success) break; 
		}
	}
	if(!success) cout<<"No Solution!"<<endl;
	AC pleaseqwq;
}

4. 数独游戏

这道题碰巧以前写过,毕竟该期末复习了,就没再做一遍

简单来说就是dfs,然后由于状态太多了,开太大的数组不现实

因此涉及状态压缩。即由于一个int是32位的,换言之,如果我们只需要表示01两种状态

那么一个int,8字节,就可以表示32位的状态

我们拿每一位上的状态去对应对应的状态即可

此题即这样

#include<bits/stdc++.h>
using namespace std;

const int N=9,M=1<<N;
//行列九宫格 
int row[N],col[N],cell[3][3];
//棋盘,多开一点 
char str[100];
//需要两个数组来帮助我们优化
//快速求出多少个1,打表;快速求2的多少次方,打表求值 
int ones[M],mp[M];

void init(){
	//最开始9个状态都可以填,也就是全是1,即1左移N减1 
	for(int i=0;i<N;i++) row[i]=col[i]=(1<<N)-1;
	for(int i=0;i<3;i++)
		for(int j=0;j<3;j++)
			cell[i][j]=(1<<N)-1;
}

//is_set表示在当前xy这个函数是填的过程还是删的过程 
void draw(int x,int y,int t,bool is_set){
	//填的过程 
	if(is_set) str[x*N+y]='1'+t;
	else str[x*N+y]='.';
	
	int v=1<<t;
	//清空操作便取反,然后减去即可 
	if(!is_set) v=-v;
	row[x]-=v;
	col[y]-=v;
	cell[x/3][y/3]-=v;
}

int lowbit(int x){
	return x&-x;
}

int get(int x,int y){
	//行列取与 
	return row[x]&col[y]&cell[x/3][y/3];
}

bool dfs(int cnt){
	//如果没有空位了,那就返回吧 
	if(!cnt) return true;
	
	int minv=10;
	int x,y;
	for(int i=0;i<N;i++)
		for(int j=0;j<N;j++)
		//是空格才枚举 
			if(str[i*N+j]=='.'){
				//求交集 
				int state=get(i,j);
				//ones打表了 
				if(ones[state]<minv){
					minv=ones[state];
					x=i,y=j;
				}
			}
	//求能填哪些数 
	int state=get(x,y);
	//枚举这个状态中的所有的1 
	for(int i=state;i;i-=lowbit(i)){
		int t=mp[lowbit(i)];
		//lowbit求的是2的k次方,mp映射回k 
		draw(x,y,t,true);
		//dfs下一层的值,成功返回true,本层失败则清空值 
		if(dfs(cnt-1)) return true;
		draw(x,y,t,false);
	}
	//找不到方案返回false 
	return false;
}

int main(){
	cin.tie(0),cout.tie(0);
	for (int i = 0; i < N; i ++ ) mp[1 << i] = i;
	for(int i=0;i<1<<N;i++)
		for(int j=0;j<N;j++)
			ones[i]+=i>>j&1;
	while(cin>>str,str[0]!='e'){
		init();
		//cnt表示有多少空位 
		int cnt=0;
		for(int i=0,k=0;i<N;i++)
			for(int j=0;j<N;j++,k++)
				if(str[k]!='.'){
					int t=str[k]-'1';
					//i,j这个位置上填上t 
					draw(i,j,t,true);
				}
				//否则有空位,cnt++ 
				else cnt++;
		dfs(cnt);
		puts(str);
	}
	
	return 0;
}

5. 代码排版

。。。算法题就别放大模拟了吧。。。。。不想写,抄了

主要是大模拟太废精力了,还要注重一堆细节,对算法水平也没一点点提升。。。

6.最短路径

简单bfs。可以说是模板题了,考试可以把这个当做板子背一下。

#include<bits/stdc++.h>
//#define int long long
#define endl "\n"

#define fir for(int i=1;i<=n;i++)
#define rif for(int i=n;i>=1;i--)

#define AC return
#define pleaseqwq 0
using namespace std;

//一些特定数据结构用 
typedef pair<int,int>PII;
typedef pair<string,int>PSI;
typedef pair<string,string>PSS;
//特定哈希用 
unordered_map<string,int>si;
unordered_map<int,int>ii;
//bfs用 
const int dx[4]={-1,0,1,0};
const int dy[4]={0,1,0,-1}; 

//常用 
const int N=1e3+10;
const int mod=2147483648;

//并查集用 
int p[N];



int g[N][N];
bool st[N];
int d[N];
int n,m;

int find(int x){
	if(p[x]!=x) p[x]=find(p[x]);
	return p[x];
}

void bfs(){
	int begin,end;
	cin>>begin>>end;
	queue<int>q;
	q.push(begin);
	st[begin]=true;
	while(q.size()){
		int t=q.front();
		q.pop();
		for(int i=0;i<=n;i++){
			if(g[t][i]&&!st[i]){
				q.push(i);
				st[i]=true;
				d[i]=d[t]+1;
				if(i==end) break;
			}
		}
	}
	if(begin==end) cout<<"The length of the shortest path between "<<begin
		<<" and "<<end<<" is "<<0<<"."<<endl;
	else if(d[end]) cout<<"The length of the shortest path between "<<begin
		<<" and "<<end<<" is "<<d[end]<<"."<<endl;
	else cout<<"There is no path between "<<begin<<" and "<<end<<"."<<endl;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(false);
	
	cin>>n>>m;
	for(int i=0;i<m;i++){
		int x,y;
		cin>>x>>y;
		g[x][y]=1,g[y][x]=1;
	}
	bfs();
	AC pleaseqwq;
}

7. 一笔画

一眼欧拉路,于是写了,然后WA了

思路没问题,既然数据加强了,大概率是不在同一连通块内的两个图有各自的欧拉路

因此再判断一下这些点是否位于同一连通块内即可

这就没必要dfs判断了,直接并查集即可。并查集这个东西,,,不详细讲了,网上搜搜看吧。

欧拉路不会的翻离散数学,代码里也有注释

嗯,最后过了

要是不提示一句数据加强了还不好想到。。。

#include<bits/stdc++.h>
//#define int long long
#define endl "\n"

#define fir for(int i=1;i<=n;i++)
#define rif for(int i=n;i>=1;i--)

#define AC return
#define pleaseqwq 0
using namespace std;

//一些特定数据结构用 
typedef pair<int,int>PII;
typedef pair<string,int>PSI;
typedef pair<string,string>PSS;
//特定哈希用 
unordered_map<string,int>si;
unordered_map<int,int>ii;
//bfs用 
const int dx[4]={-1,0,1,0};
const int dy[4]={0,1,0,-1}; 

//常用 
const int N=1e3+10;
const int mod=2147483648;

//并查集用 
int p[N];


int cnt[N];

int find(int x){
	if(p[x]!=x) p[x]=find(p[x]);
	return p[x];
}


signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(false);
	
	int n,m;
	cin>>n>>m;
	
	for(int i=1;i<=n;i++) p[i]=i;
	
	//一笔画即欧拉路,离散讲过
	//无向图即判断度数为奇数的点是否只能是0个或2个 
	for(int i=0;i<m;i++){
		int x,y;
		cin>>x>>y;
		cnt[x]++,cnt[y]++;
		p[find(x)]=find(y);
	}
	//点度数和所有连通块个数 
	int sum=0,all=0;
	for(int i=1;i<=n;i++){
		//若不在同一连通块,并查集上不在同一顶点 
		if(p[i]==i) all++;
		if(cnt[i]%2==1) sum++;
	}
	if(all==1&&(sum==0||sum==2)) cout<<"Y"<<endl;
	else cout<<"N"<<endl;
	AC pleaseqwq;
}

8. 幸福小镇的故事!(简单)

一眼floyd,多源汇最短路径,然后TLE了

看了眼数据范围,哦,1e3,n^3的复杂度会超时

仔细一看,查询次数足够少,那直接每次bfs一下就行

正解

#include<bits/stdc++.h>
//#define int long long
#define endl "\n"

#define fir for(int i=1;i<=n;i++)
#define rif for(int i=n;i>=1;i--)

#define AC return
#define pleaseqwq 0
using namespace std;

//一些特定数据结构用 
typedef pair<int,int>PII;
typedef pair<string,int>PSI;
typedef pair<string,string>PSS;
//特定哈希用 
unordered_map<string,int>si;
unordered_map<int,int>ii;
//bfs用 
const int dx[4]={-1,0,1,0};
const int dy[4]={0,1,0,-1}; 
//常用 
const int N=1e3+10;
const int mod=2147483648;

vector<int>g[N];
int d[N];

int n;

void bfs(){
	memset(d,-1,sizeof d);
	int x,y;
	cin>>x>>y;
	queue<int>q;
	q.push(x);
	d[x]=0;
	while(q.size()){
		int t=q.front();
		q.pop();
		for(int i=0;i<g[t].size();i++){
			if(d[g[t][i]]==-1){
				d[g[t][i]]=d[t]+1;
				q.push(g[t][i]);
			}
		}
	}
	cout<<d[y]<<endl;
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(false);
	cin>>n;
	n--;
	while(n--){
		int x,y;
		cin>>x>>y;
		g[x].push_back(y),g[y].push_back(x);
	}
	int q;
	cin>>q;
	while(q--) bfs();
	
	
	AC pleaseqwq;
}

这是floyd解法,TLE了。一般适用于查询次数足够多,且点的个数在几百个左右的时候。数据结构课应该讲过,不细说了。

#include<bits/stdc++.h>
//#define int long long
#define endl "\n"

#define fir for(int i=1;i<=n;i++)
#define rif for(int i=n;i>=1;i--)

#define AC return
#define pleaseqwq 0
using namespace std;

//一些特定数据结构用 
typedef pair<int,int>PII;
typedef pair<string,int>PSI;
typedef pair<string,string>PSS;
//特定哈希用 
unordered_map<string,int>si;
unordered_map<int,int>ii;
//bfs用 
const int dx[4]={-1,0,1,0};
const int dy[4]={0,1,0,-1}; 
//常用 
const int N=1e3+10;
const int mod=2147483648;

int d[N][N];

queue<PII>q;
int n;

void floyd(){
	for(int k=1;k<=n;k++)
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)
                d[i][j]=min(d[i][j],d[i][k]+d[k][j]);
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(false);
	//每个q跑一遍bfs就行
	//但这里写了floyd,只是因为快忘了
	//数据结构似乎,我记得讲了这个算法?
	
	memset(d,0x3f,sizeof d);
	for(int i=1;i<=1000;i++) d[i][i]=0;
	cin>>n;
	int m=n;
	m--;
	while(m--){
		int x,y;
		cin>>x>>y;
		d[x][y]=1,d[y][x]=1;
	}
	floyd();
	int q;
	cin>>q;
	while(q--){
		int x,y;
		cin>>x>>y;
		//保证有解则无需特判,否则特判是否大于0x3f3f3f3f/2,如果是,无法到达 
		cout<<d[x][y]<<endl;
	}
	
	
	AC pleaseqwq;
}

9.BFS


得慢慢想的一道题,思考的过程放在代码注释了

从主函数,用到什么函数去看对应的函数,就能顺着思路走了

这道题应该是bfs+dfs+剪枝

#include<bits/stdc++.h>
//#define int long long
#define endl "\n"

#define fir for(int i=1;i<=n;i++)
#define rif for(int i=n;i>=1;i--)

#define AC return
#define pleaseqwq 0
using namespace std;

//一些特定数据结构用 
typedef pair<int,int>PII;
typedef pair<string,int>PSI;
typedef pair<string,string>PSS;
//特定哈希用 
unordered_map<string,int>si;
unordered_map<int,int>ii;
//bfs用 
const int dx[4]={-1,0,1,0};
const int dy[4]={0,1,0,-1}; 
//常用 
const int N=5e3+10;
const int mod=2147483648;

string s[N];
int f[N][N];
bool st[N][N],vis[N][N];

queue<PII>q;

int n,m;
//V所在的位置 
int vx,vy;
//J所在的位置
int jx,jy; 

void bfs(){
	while(!q.empty()){
		PII t = q.front();
		q.pop();
		int tx=t.first,ty=t.second;
		for(int i=0;i<4;i++){
			int u=tx+dx[i],v=ty+dy[i];
			if(u<1||v<1||u>n||v>m) continue;
			if(!st[u][v]){
				//若此点未被更新过
				//则此点入队 
				f[u][v]=f[tx][ty]+1;
				q.push({u,v});
				//此处入队,防止重复入队 
				st[u][v]=true; 
			}
		}
	} 
}

bool dfs(int x,int y){
	//如果能找到路径,则存在这么一条路径
	if(x==jx&&y==jy) return true;
	vis[x][y]=true;
	for(int i=0;i<4;i++){
		int tx=x+dx[i],ty=y+dy[i];
		//边界 
		if(tx<1||ty<1||tx>n||ty>m) continue;
		//不可走 
		if(st[tx][ty]) continue;
		if(vis[tx][ty]) continue;
		if(tx==jx&&ty==jy) return true;
		if(dfs(tx,ty)) return true;
	}
	return false;
}

//返回值为true时,认为解在二分中点右边 
bool solve(int mid){
	memset(st,0,sizeof st);
	 
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++)
		//对于所有小于mid的点,我们认为无法走,即设st为true 
			if(f[i][j]<mid) st[i][j]=1;
	}
	//本身小于mid的点,如果走则不符合最优性,直接剪掉 
	if(st[vx][vy]||st[jx][jy]) return false;
	memset(vis,0,sizeof vis);
	//dfs即可
	return dfs(vx,vy); 
}

signed main(){
	cin.tie(0),cout.tie(0);
	ios::sync_with_stdio(false);
	
	cin>>n>>m;
	//横纵坐标统一变为以1开始
	for(int i=1;i<=n;i++) cin>>s[i];
	for(int i=1;i<=n;i++) s[i]='?'+s[i];
	
	//不能简单跑一遍bfs,否则无法准确计算到达树的点
	//所以实际上应该是找每个点到达任意一个树的点
	//换言之就是对所有树做一次bfs,取其中的最小的距离 
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++){
			if(s[i][j]=='+'){
				q.push({i,j});
				st[i][j]=true;
			}
			if(s[i][j]=='V') vx=i,vy=j;
			if(s[i][j]=='J') jx=i,jy=j;
		}
	bfs();
	//此时得到了所有点距离树的距离的最小值
	//然后我们需要规划回家的路径
	/*
	举个例子吧,假设我们现在距离树的距离是
	0123
	1234
	2345
	3456
	左下起点,右下终点 
	那么实际上就是在这张图中找一条路径
	使得我们最终到达的目标时,经过的所有点的最小值最大
	这样好理解多了吧。
	此时需要遍历出所有路径,但是由于路径数量太多
	需要单独记住每一次的路径是不现实的。因此需要剪枝
	四种剪枝策略,最优性剪枝,可行性剪枝,优化搜索顺序,排除等效冗余 
	可见的一种方案是最优性剪枝,即记录每次的结果
	若搜索过程中有不可能更优的方案,直接停止
	扩展一下思路即二分,用二分确定区间来找答案 
	*/
	int l=0,r=n;
	while(l<r){
		//右移一位除2,注意优先级 
		int mid=l+r+1>>1;
		if(solve(mid)) l=mid;
		else r=mid-1;
	}
	cout<<l<<endl;
	AC pleaseqwq;
}

  • 9
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值