题目列表:
第二题
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]);
}
欢迎评论区交流!