0413南京理工模拟考(七)

这篇博客详细解析了四道编程题,涉及STL中的map、set的使用,全排列问题的DFS与next_permutation()函数,细胞数量的DFS/BFS求解,迷宫问题的最小生成树判断,以及区间DP求解释放囚犯的最优策略。通过这些题目,探讨了不同的算法和数据结构的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目列表:在这里插入图片描述

第二题

P1808 单词分类
考点:STL的map、set使用
此题最终就是要给每种字符串一个特定标志,下次同样字符串能快速确定之前有没有出现过。因此有三种方法记录这种特征
三种方法:
①set集合去重+hash字符串id
②set集合去重字符串
③map映射

方法一:hash字符串标记字符串,利用set将字符串id去重

#include<bits/stdc++.h> 
using namespace std;
/*-------2--------*/
const int MOD=1000000007;
const int P = 10000019;
set<int> ans;

long long hashFunc(string str){
	long long H =1;
	for(int i=0;i<str.length();i++){
		H=(H*P+str[i]-'A')%MOD;
	}
	return H;
}
int main(){
//	freopen("in.txt","r",stdin); 
	string a;
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>a;
		sort(a.begin(),a.end());
		long long id =hashFunc(a);
		ans.insert(id);
	}
	printf("%d",ans.size()) ;
}

方法二:set直接将字符串去重
参考别人的代码发现其实可以不用转换为hash字符串
直接放入集合中即可

//《算法笔记》P450
#include<bits/stdc++.h> 
using namespace std;
/*-------2--------*/
const int MOD=1000000007;
const int P = 10000019;
//这里也要改一下
set<string> ans;

long long hashFunc(string str){
// H =0;则A,AA,AAA这个测试数据最后会被判定为同一个单词,实际是三个,所以改成H=1
	long long H =1;
	for(int i=0;i<str.length();i++){
		H=(H*P+str[i]-'A')%MOD;
	}
	return H;
}
int main(){
//	freopen("in.txt","r",stdin); 
	string a;
	int n;
	cin>>n;
	for(int i=0;i<n;i++){
		cin>>a;
		sort(a.begin(),a.end());
		ans.insert(a);
	}
	printf("%d",ans.size()) ;
}

方法三:map

#include<algorithm>
#include<iostream>
#include<map>
using namespace std;
map<string,bool>z;//存储字符串是否出现过
string a;
int n,sum;
int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        cin>>a;
        sort(a.begin(),a.end());//排序
        if(!z[a]){//如果没有出现过,组数+1
            sum++;
            z[a]=1;
        }
    }
    cout<<sum;
    return 0;
}

第三题

P1706 全排列问题
考点:DFS,next_permutation()函数
方法一:DFS

#include<bits/stdc++.h> 
using namespace std;
//ans存储排列结果
/*------3--------*/
//b[i]==1表示数字i已访问 
int b[11];
int n,ans[10];
void DFS(int s){
	if(s==n+1){
		for(int i=1;i<=n;i++){
			printf("%5d",ans[i]);
		}
		printf("\n");
	}else{
		for(int i=1;i<=n;i++){
			if(b[i]==0){
				b[i]=1;
				ans[s]=i;
				DFS(s+1);
				b[i]=0;
				ans[s]=0; 
			}
		}
	} 
}
int main(){
	scanf("%d",&n);
	DFS(1);
}

方法二:运用全排列函数
next_permutation()的作用是将排列调换到下一个排列,且当到达最后一个排列,不能再继续调换时,返回false

//next_permutation()函数的运用
#include<bits/stdc++.h> 
using namespace std;
int main(){
	int n,a[10];
	cin>>n;
	for(int i=1;i<=n;i++){
		a[i]=i;
	}
	do{
		for(int i=1;i<=n;i++){
			printf("%5d",a[i]);
		}
		printf("\n");
	}while(next_permutation(a+1,a+n+1));
}

第四题

P1451 求细胞数量
分析: 因为没读懂题意,所以没做,其实这个题就是要求“块”数量,妥妥的BFS或DFS
考点:DFS或BFS

因为读入的数字之间没空格,要想输入到矩阵中对应位置,必须有特殊操作

方法一:
按字符读入

	scanf("%d%d",&m,&n);
	getchar();
	for(int i=0;i<m;i++){
		for(int j=0;j<n;j++){
			ch=getchar();
			G[i][j]=ch-'0';
		}
		getchar();
	}
	int ans=0;

但以上代码只有十分,虽然再本地运行结果是正确的。。。。
原因是远程的judge机是linux系统,在linux系统下\n需要两次getchar(),因此虽然在本地运行是正确的。但是在远程服务器实际上每行结尾的\n都当成字符读进去了
因此若还是要以字符形式读入,有以下两种改正方法:

①:改进一

	scanf("%d%d",&m,&n);
	getchar();
	getchar();
	for(int i=0;i<m;i++){
		for(int j=0;j<n;j++){
			scanf(" %c",&ch) ;
			G[i][j]=ch-'0';
		}
		getchar();
		getchar();
	}
	int ans=0;

改进一这种方式会显得比较笨拙;
还有一种方法是 scanf(" %c", &ch)
即在%c前添加一个空格, %c 前面的空格会跳过不可见字符。
因此这样只会读入能读入的字符,不会读入不可见的\n

	scanf("%d%d",&m,&n);
	for(int i=0;i<m;i++){
		for(int j=0;j<n;j++){
			scanf(" %c",&ch) ;
			G[i][j]=ch-'0';
		}
	}
	int ans=0;

方法二:
scanf("%1d",&G[i][j]);

//《算法笔记》P277
#include<bits/stdc++.h> 
using namespace std;
const int maxn=110;
int G[maxn][maxn];
int n,m;
bool inq[maxn][maxn] ={false};
int X[4]={-1,1,0,0};
int Y[4]={0,0,1,-1};
struct node{
	int x,y;
	
}Node;
bool judge(int x,int y){
	if(x>=m||x<0||y>=n||y<0) return false;
	if(G[x][y]==0||inq[x][y]==true) return false;
	return true; 
}
void BFS(int x,int y){
	queue<node> Q;
	Node.x=x,Node.y=y;
	Q.push(Node);
	inq[x][y]=true;
	while(!Q.empty()){
		node top=Q.front();
		Q.pop();
		for(int i=0;i<4;i++){
			int newx=top.x+X[i];
			int newy=top.y+Y[i];
			if(judge(newx,newy)){			
				Node.x=newx,Node.y=newy;
				Q.push(Node);
				inq[newx][newy]=true;
			}
		}
	}
}
int main(){
// 	freopen("in.txt","r",stdin);
	char ch;
	scanf("%d%d",&m,&n);
	for(int i=0;i<m;i++){
		for(int j=0;j<n;j++){
		scanf("%1d",&G[i][j]);
		}
	}
	int ans=0;
	for(int x=0;x<m;x++){
		for(int y=0;y<n;y++){
			if(G[x][y]!=0&&inq[x][y]==false){
				ans++;
				BFS(x,y);
			}
		}
	}
	printf("%d",ans);
}

第五题

P2307 迷宫

方法一:
统计点数和边数
①点数-1 !=边数 --> 不可能是最小生成树–>输出0
②点数-1=边数
若kruskal算法后,所有边都加进来了,则说明这些边可以组成最小生成树
否则,说明有边没加进来,为什么呢?如下面例子:1,3在同一个集合中,(1,3)便不会被统计
图中存在环,输出0
样例:
1 2
2 3
1 3
4 5
0 0
点数-1=边数满足要求,但是实际存在环,图也不是最小生成树。
通过测识,可知,以上说的这种最后有两个或以上的集合的测试点是
#2和#9
具体测试方法就是将这个注释的flag=1取消注释,然后将flag=Kruskal()注释掉,就可以了
在这里插入图片描述

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

 const int maxn=10010;
 //a[i]表示这组数据中i房间出现过 ,cnt表示出现的房间的总数 
 int fa[maxn],flag=0; 
 struct edge{
 	int x,y;
 }E[maxn];
 int a[maxn],cnt=0,k=0; 
 int findFather(int x){
 	if(x==fa[x]) return x;
 	else return fa[x]=findFather(fa[x]);
 }
 int Kruskal(){
 	flag=0;
 	int Num_Edge=0;
 	for(int i=1;i<=10000;i++){
 		fa[i]=i;
 	}
 	for(int i=0;i<cnt;i++){
 		int faU=findFather(E[i].x);
 		int faV=findFather(E[i].y);
 		if(faV!=faU){
 			fa[faV]=faU;
 			Num_Edge++;
 			if(Num_Edge==k-1){
 				flag=1;
 				break;
 			}
 		}
 	}
 	return flag;
 }

int main(){
//  freopen("in.txt","r",stdin); 
	int u,v;
	while(scanf("%d%d",&u,&v)&&!(u==-1&&v==-1)){
		if(!(u==0&&v==0)){
			E[cnt].x=u;
			E[cnt].y=v;
			//num是点数量 
			if(a[u]==0){
				 k++;
				 a[u]=1;
			} 
			if(a[v]==0){
				 k++;
				 a[v]=1;
			}
			//出现的边数 
			cnt++;
		}else{
			if(cnt!=k-1) flag=0;
			else{
				flag=Kruskal(); 
//				flag=1;
			}
			printf("%d\n",flag);
			k=0,cnt=0;
			memset(a,0,sizeof(a));
		}
	}
} 

方法二:
当发现图中有环,获取flag=1标记为违规
最后没有环且只有一个集合即为最小生成树
源自本题题解区

#include <cstdio>
#include <cstring>
int f[100001]/*祖先*/,a,b,sum/*集合数量*/;
bool book[100001]/*是否出现*/,flag/*是否违规*/;
void cls(){//初始化
    for(register int i=1;i<=100000;i++) f[i]=i;
    memset(book,0,sizeof(book));sum=0;flag=false;
}
int find(int x){//路径压缩般找祖先
    if(f[x]==x) return x;
    return f[x]=find(f[x]);
}
int main(){
//	freopen("in.txt","r",stdin); 
    cls();
    while(scanf("%d%d",&a,&b)==2)//scanf返回成功输入的数据的个数 
    {
        if(a==-1&&b==-1) break;
        if(a==0&&b==0) {printf("%d\n",((!flag&&sum==1)?1:0));cls();continue;}//这组数据结束
        if(!book[a]) sum++;//集合数量+1 
        if(!book[b]) sum++;//集合数量+1 
        book[a]=true;book[b]=true;//标记
        int x1=find(a),x2=find(b);//祖先
        if(x1==x2) flag=true;//标记为违规 
        else {sum--;f[x1]=x2;}//修改
    }
    return 0;
}

第六题

P1622 释放囚犯
考点:区间dp
代码来源
分析与代码解释:
注意()表示不含端点,[ ]表示含端点
①a[0]=0;a[m+1]=n+1;
因为f[i][j]指的是(a[i-1],a[j+1])的区间内释放
i号~j号囚犯 **[i,j]**所需耗费的代价(肉)。所以要求f[1][m]得知道a[0]和a[m+1]而牢房端点是1和n,所以要让1-n包含在(a[0],a[m+1])之间,故1左移一个点得到 a[0]=0
n号牢房右移一个点得 a[m+1]=n+1;

②区间len最小为1,最大为m
如题目样例,区间为1,得到f[1][1]含义是(0,6)即[1,5]号房的区间长度上来看,释放1号囚犯需要的代价,值6-0-1(代表不用给肉的6号房,因为不在区间内)-1(代表3号房,因为释放3
号房的1号囚犯当天不用给肉),同理可得
f[2][2]=9
f[3][3]=13
当len=2时
f[1][2] 代表释放1,2两位囚犯在(a[0],a[3]) (即样例中[1,13])号房之间需要的代价。先释放1号囚犯代价为a[3]-a[0]-1(代表a[3])-1(代表释放的1号囚犯的a[1]号牢房不需要给肉)=14-2+f[2][2]=14-2+9=21。先释放2号囚犯的f[1][2]=14-2+f[1][1]=16故f[1][2]=16;
同理,在先释放2号囚犯(即分割点k=2)时f[2][3]=min(INF,21-3-2+f[2][1]+f[3][3])=min(INF,21-3-2+0+13)=29
先释放3号,f[2][3]=min(29,21-3-2+f[2][2]+f[4][3])=min(29,21-3-2+13+0)=25
当len=3时只有f[1][3]
先释放1号囚犯(k=1为分割点),代价为a[4]-[0]-2=19
f[1][3]=min(INF,19+f[1][0]+f[2][3])=19+25=44
同理:
先释放2号囚犯f[1][3]=min(44,19+f[1][1]+f[3][3])=36
先释放3号囚犯f[1][3]=min(36,19+f[1][2]+f[4][3])=19+16=35;
样例:
20 3
3 6 14

③状态转移方程: f[i][j]=min(f[i][j],a[j+1]-a[i-1]-1-1+f[i][k-1]+f[k+1][j]);
以k为分割点
a[j+1]-a[i-1]-1-1:释放k号囚犯需要的肉
f[i][k-1]:释放k号囚犯后,释放前面的在(a[i-1],a[k])之间的牢房里的可释放囚犯需要的代价
f[k+1][j]:释放k号囚犯后,释放后面的在(a[k],a[j+1])之间的牢房里的可释放囚犯需要的代价

#include<bits/stdc++.h>
using namespace std;
int n,m;
int a[1000];
int f[1000][1000]; 
int main()
{
// 	freopen("in.txt","r",stdin); 	
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++)
        scanf("%d",&a[i]);
    sort(a+1,a+m+1); //排序,有序的才能转移状态
    a[0]=0;a[m+1]=n+1; //假设0和n+1号牢房有人,方便后面的状态转移
    for(int len=1;len<=m;len++)  //枚举区间长度
        for(int i=1;i+len-1<=m;i++) //区间左端点位置
        {
            int j=i+len-1;  //区间右端点位置
            f[i][j]=999999999;  
            for(int k=i;k<=j;k++)  //枚举分界点
                f[i][j]=min(f[i][j],a[j+1]-a[i-1]-1-1+f[i][k-1]+f[k+1][j]); //状态转移
        }
    printf("%d",f[1][m]);  
}

欢迎评论区交流!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值