文章目录
作为一个深搜蒟蒻,为了我的深搜能在这个寒假腾飞,特意练练基础
深搜是基础算法,不过多阐释,直接上题
洛谷P2919 Guarding the Farm
题目
题意翻译
农夫John的农场里有很多小山丘,他想要在那里布置一些保镖去保卫他的那些相当值钱的奶牛们。
他想知道如果在一座小山丘上布置一名保镖的话,他最少总共需要招聘多少名保镖。他现在手头有一个用数字矩阵来表示地形的地图。这个矩阵有N行(1<N≤700)和M列( 1<M≤ 700) 。矩阵中的每个元素都有一个值H_ij(0≤H_ij≤10000)来表示该地区的海拔高度。
小山丘的定义是:若地图中一个元素所邻接的所有元素都比这个元素高度要小(或它邻接的是地图的边界),则该元素和其周围所有按照这样顺序排列的元素的集合称为一个小山丘。这里邻接的意义是:若一个位置的横纵坐标与另一个位置的横纵坐标相差不超过1,则称这两个元素邻接,比如某个非边界点的位置有8个相邻点:上、下、左、右、左上、右上、左下、右下。
请你帮助他统计出地图上最少且尽量高的小山丘数量。
题目描述
The farm has many hills upon which Farmer John would like to place guards to ensure the safety of his valuable milk-cows.
He wonders how many guards he will need if he wishes to put one on top of each hill. He has a map supplied as a matrix of integers; the matrix has N (1 < N <= 700) rows and M (1 < M <= 700) columns. Each member of the matrix is an altitude H_ij (0 <= H_ij <= 10,000). Help him determine the number of hilltops on the map.
A hilltop is one or more adjacent matrix elements of the same value surrounded exclusively by either the edge of the map or elements with a lower (smaller) altitude. Two different elements are adjacent if the magnitude of difference in their X coordinates is no greater than 1 and the magnitude of differences in their Y coordinates is also no greater than 1.
输入输出格式
输入格式:
-
Line 1: Two space-separated integers: N and M
-
Lines 2…N+1: Line i+1 describes row i of the matrix with M
space-separated integers: H_ij
输出格式:
- Line 1: A single integer that specifies the number of hilltops
输入输出样例
输入样例:
8 7
4 3 2 2 1 0 1
3 3 3 2 1 0 1
2 2 2 2 1 0 0
2 1 1 1 1 0 0
1 1 0 0 0 1 0
0 0 0 1 1 1 0
0 1 2 2 1 1 0
0 1 1 1 2 1 0
输出样例:
3
分析
真的,这道题我是败在题意理解上了,当时看到样例就懵了
请看这一段话:
若地图中一个元素所邻接的所有元素都比这个元素高度要小(或它邻接的是地图的边界),则该元素和其周围所有按照这样顺序排列的元素的集合称为一个小山丘。
注意这个“所有”,也就是说小山丘周围所有的递减(也可以等于)的区域都是小山丘范围,且“元素”不一定是一个点,可能是个区域,于是,这道题就变成了一个简单的深搜+染色,当然,广搜也行
代码
/*
User:Mandy.H.Y
language:c++
Problem:luogu2919
dfs//这道题败在了读题上面hhhhhhh
似乎广搜也行?
*/
#include<bits/stdc++.h>
#define Max(x,y) (x)>(y)?(x):(y)
#define Min(x,y) (x)<(y)?(x):(y)
#define mem(A) memset((A),0,sizeof(A))
using namespace std;
const int maxn=703;
const int maxm=703;
int n,m,cnt,sum,ans;
int a[maxn][maxm];
bool vis[maxn][maxm];
int dx[8]={1,1,1,-1,-1,-1,0,0};
int dy[8]={0,1,-1,0,1,-1,-1,1};
struct Hill
{
int x,y,h;
}hill[maxn*maxm];//结构体大法好
template<typename T>inline void read(T &x)
{
x=0;char c=getchar();bool f=0;
while(c<'0'||c>'9') {f|=(c=='-');c=getchar();}
while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
if(f) x=-x;
}
template<typename T>void putch(const T x)
{
if(x>9) putch(x/10);
putchar((x%10)|48);
}
template<typename T>inline void put(const T x)
{
if(x<0) putchar('-'),putch(-x);
else putch(x);
}
void docu()
{
freopen("2919.txt","r",stdin);
}
bool cmp(Hill x,Hill y)
{
return x.h>y.h;
}
void readdata()
{
read(n); read(m);
int num=0;
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
{
read(hill[++num].h);
hill[num].x=i;
hill[num].y=j;
a[i][j]=hill[num].h;
}
sum=n*m;
sort(hill+1,hill+sum+1,cmp);//从大到小排序
}
void dfs(int x,int y)
{
if(vis[x][y]) return;
++cnt;//记录走过的节点数
vis[x][y]=1;//标记
for(int i=0;i<8;++i)
{
int nx=x+dx[i];
int ny=y+dy[i];
if(nx<1||nx>n||ny<1||ny>m) continue;//防越界
if(a[x][y]>=a[nx][ny]) dfs(nx,ny);
}
}
void work()
{
int num=1;
while(cnt<sum)
{
while(vis[hill[num].x][hill[num].y]) num++;//找到下一个峰顶
++ans;//统计山峰个数
dfs(hill[num].x,hill[num].y);
}
put(ans);
}
int main()
{
// docu();
readdata();
work();
return 0;
}
洛谷P2420 让我们异或吧
题目
题目描述
异或是一种神奇的运算,大部分人把它总结成不进位加法.
在生活中…xor运算也很常见。比如,对于一个问题的回答,是为1,否为0.那么:
(A是否是男生 )xor( B是否是男生)=A和B是否能够成为情侣
好了,现在我们来制造和处理一些复杂的情况。比如我们将给出一颗树,它很高兴自己有N个结点。树的每条边上有一个权值。我们要进行M次询问,对于每次询问,我们想知道某两点之间的路径上所有边权的异或值。
输入输出格式
输入格式:
输入文件第一行包含一个整数N,表示这颗开心的树拥有的结点数,以下有N-1行,描述这些边,每行有3个数,u,v,w,表示u和v之间有一条权值为w的边。接下来一行有一个整数M,表示询问数。之后的M行,每行两个数u,v,表示询问这两个点之间的路径上的权值异或值。
输出格式:
输出M行,每行一个整数,表示异或值
输入输出样例
输入样例:
5
1 4 9644
2 5 15004
3 1 14635
5 3 9684
3
2 4
5 4
1 1
输出样例:
975
14675
0
分析
个人认为,这道题应该叫做异或的交换律与结合律。
明白了这一点后,根据交换律与结合律再加上a ^ a=0, 0 ^ a=a就可以想到,随便找一个根节点,直接一个深搜遍历,求出每个子节点到根节点的异或值,存入就可以了dis就可以了
任意两点的权值异或值就是dis[u]^dis[v]
有人用LCA,还有用树剖的……都是大佬
代码
/*
User:Mandy.H.Y
language:c++
Problem:luogu2420
*/
//!!!!异或的交换律与结合律 >_<
#include<bits/stdc++.h>
using namespace std;
const int maxn=100005;
int n,size=0,m;
int first[maxn],dis[maxn];
bool vis[maxn];
struct Edge
{
int w,v,nt;
}edge[maxn<<1];
template<typename T>inline void read(T &x)
{
x=0;char c=getchar();bool f=0;
while(c<'0'||c>'9') {f|=(c=='-');c=getchar();}
while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();};
if(f)f=-f;
}
template<typename T>void putch(const T x)
{
if(x>9) putch(x/10);
putchar((x%10)|48);
}
template<typename T>void put(const T x)
{
if(x<0) putchar('-'),putch(-x);
else putch(x);
}
void eadd(int u,int v,int w)
{
edge[++size].v=v;
edge[size].w=w;
edge[size].nt=first[u];
first[u]=size;
}
void dfs(int u)
{
vis[u]=1;
for(int i=first[u];i;i=edge[i].nt)
{
int v=edge[i].v,w=edge[i].w;
if(vis[v]) continue;
dis[v]=dis[u]^w;
dfs(v);
}
}
int main()
{
read(n);
for(int i=1;i<n;++i)
{
int u,v,w;
read(u); read(v);read(w);
eadd(u,v,w);
eadd(v,u,w);
}
dfs(1);
read(m);
for(int i=1;i<=m;++i)
{
int u,v;
read(u);read(v);
put(dis[u]^dis[v]);
putchar('\n');
}
return 0;
}
洛谷P1330封锁阳光大学
题目
题目描述
曹是一只爱刷街的老曹,暑假期间,他每天都欢快地在阳光大学的校园里刷街。河蟹看到欢快的曹,感到不爽。河蟹决定封锁阳光大学,不让曹刷街。
阳光大学的校园是一张由N个点构成的无向图,N个点之间由M条道路连接。每只河蟹可以对一个点进行封锁,当某个点被封锁后,与这个点相连的道路就被封锁了,曹就无法在与这些道路上刷街了。非常悲剧的一点是,河蟹是一种不和谐的生物,当两只河蟹封锁了相邻的两个点时,他们会发生冲突。
询问:最少需要多少只河蟹,可以封锁所有道路并且不发生冲突。
输入输出格式
输入格式:
第一行:两个整数N,M
接下来M行:每行两个整数A,B,表示点A到点B之间有道路相连。
输出格式:
仅一行:如果河蟹无法封锁所有道路,则输出“Impossible”,否则输出一个整数,表示最少需要多少只河蟹。
输入输出样例
输入样例 #1:
3 3
1 2
1 3
2 3
输出样例 #1:
Impossible
输入样例 #2:
3 2
1 2
2 3
输出样例 #2:
1
分析
考虑到题中要覆盖到所有的边,则深搜+染色不失为一种好方法
欢欢喜喜的写了一个深搜准备A了这道题,结果悲剧发生了——
这图不一定是联通图QAQ。
于是我光荣地炸掉了,不过感谢上苍我还有50分……
其实解决方法很简单,深搜模块可以不变(但要加上联通块的染色),在主模块中套一个循环枚举边,加一个染色数组标记联通块,已染过的联通块便不必再深搜
接下来就是主角——深搜
我的深搜只搜要放河蟹的点,放了河蟹的点标记为 1,而与它相连的不能放河蟹的点标记为 2 ,若出现矛盾,则此方案不行,可以返回一个极大值,再从可行方案中选出河蟹最少的一种
注意到每一个边都要被覆盖,且只能在一个端点放河蟹,那么只要确定了一个点是否放河蟹,那么覆盖整个联通块的方案也就随之确定,所以每一个联通块放河蟹的方法只有两种,即i点放河蟹和i点不放河蟹(其中i点是联通块中的任意一点)
又因为不能在两个端点同时放河蟹,所以 联通块的方案又可以是v放河蟹与u放河蟹(u,v是联通块中任意一边的起点与终点);
因为我的深搜是只搜放河蟹的点,所以在主模块中只用dfs(u),dfs(v),再把两种方案所需的河蟹数相比较 ,选出最小值加入ans中
在深搜模块中,加一个双重循环,第一层枚举与u(也就是放了河蟹的点)相连边的另一端标记为2,若其中有已经被标记为1的,则矛盾,返回极大值
第二层枚举与不能放河蟹的v点相连的点(u除外),这些点必须放河蟹,(因为v不放河蟹),进行下一轮深搜 若已被标记为1 则跳过,若被标记为2,则与它必须放河蟹矛盾,返回极大值
还有细节详见代码注释
有大佬用并查集做,0ms过
代码
/*
User:Mandy.H.Y
language:c++
Problem:1330
深搜,广搜,并查集都行
*/
#include<bits/stdc++.h>
#define Max(x,y) (x)>(y)?(x):(y)
#define Min(x,y) (x)<(y)?(x):(y)
#define mem(A) memset((A),0,sizeof(A))
using namespace std;
const int maxn=10005;
const int maxm=100005;
int n,m,size=0,ans,cnt;
int first[maxn];
int vis[maxn],vis1[maxn];
//vis用于标记联通块
//vis1用于深搜染色
struct Edge
{
int u,v,nt;//存入起点的目的是为了标记联通块和深搜
}edge[maxm<<1];
template<typename T>inline void read(T &x)
{
x=0;char c=getchar();bool f=0;
while(c<'0'||c>'9') {f|=(c=='-');c=getchar();}
while(c>='0'&&c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
if(f) x=-x;
}//读入优化
template<typename T>void putch(const T x)
{
if(x>9) putch(x/10);
putchar((x%10)|48);
}
template<typename T>inline void put(const T x)
{
if(x<0) putchar('-'),putch(-x);
else putch(x);
}//输出优化
void docu()
{
freopen("1330.txt","r",stdin);
}
void eadd(int u,int v)
{
edge[++size].v=v;
edge[size].u=u;
edge[size].nt=first[u];
first[u]=size;
}//链表存边
void readdata()//读入数据
{
read(n);read(m);
for(int i=1;i<=m;++i)
{
int x,y;
read(x);read(y);
eadd(x,y);
eadd(y,x);
}
}
bool dfs(int u)//我只搜要放河蟹的点
{
vis[u]=1;//标记联通块
vis1[u]=1;//标记已放了河蟹
++cnt;//cnt是已放河蟹的个数
for(int i=first[u];i;i=edge[i].nt)//枚举与u相连的不能放河蟹的点
{
int v=edge[i].v;
if(vis1[v]==1) {cnt=1000000;return 0;}//如果终点也放了河蟹,则这种方案是不可行的
vis1[v]=2;//标记终点未放河蟹但不能再放河蟹
for(int j=first[v];j;j=edge[j].nt)//枚举下一层要放河蟹的点
{
int v1=edge[j].v;//因为v不能放河蟹,则v1必须放河蟹
if(vis1[v1]==1) continue;//已经有一个端点有了河蟹
if(vis1[v1]==2) {cnt=1000000;return 0;}//两个都不能再放河蟹,则这条边不能被覆盖,该方案不可行
if(!dfs(v1)) return 0;//若该方案不行
}
}
return 1;//若没有冲突 ,则此方案可行
}
void work()
{
ans=0;
//注意,图不一定联通 ,所以要枚举边来标记联通块,这与我的深搜有关
for(int i=1;i<=(m<<1);i+=2)//枚举边,因为存的是无向边,所以m<<1(相当于m*2),
//又因为同一条边是连续存的两次故i+=2
{
int v=edge[i].v;
int u=edge[i].u;
if(vis[u]||vis[v]) continue;//如果已经遍历过这个联通块
cnt=0; mem(vis1);//记得初始化
dfs(u);//假设起点放河蟹
int x=cnt;
cnt=0;mem(vis1);
dfs(v);//假设终点放河蟹
int y=cnt;
if(x==1000000&&y==1000000)
{//如果两种方法都不能覆盖所有的边,直接输出不可行
printf("Impossible");
return;
}
ans+=Min(x,y);//选择两个方案中最小的
}
put(ans);
}
int main()
{
// docu();
readdata();
work();
return 0;
}
洛谷P1171 售货员的难题
题目
题目描述
某乡有nn个村庄(1<n \le 201<n≤20),有一个售货员,他要到各个村庄去售货,各村庄之间的路程s(0<s<1000)s(0<s<1000)是已知的,且AA村到BB村与BB村到AA村的路大多不同。为了提高效率,他从商店出发到每个村庄一次,然后返回商店所在的村,假设商店所在的村庄为11,他不知道选择什么样的路线才能使所走的路程最短。请你帮他选择一条最短的路。
输入输出格式
输入格式:
村庄数nn和各村之间的路程(均是整数)。
输出格式:
最短的路程。
输入输出样例
输入样例:
3
0 2 1
1 0 2
2 1 0
输出样例:
3
分析
经典深搜题,如果加了预见性剪枝,(即当前已走路程加上接下来的理论最小路程还是比当前最优方案大,直接剪掉),可以得90分。
要想得100,用结构体,把路程排序(可以更早的找到最优解,少走冤枉路),不过如果这样终点号就乱了,所以有结构体存入终点编号与长度,只是在枚举时要注意不要忘了
代码
/*
User:Mandy.H.Y
Language:c++
Problem:luogu1171
*/
#include<bits/stdc++.h>
using namespace std;
int n,ans1=0,ans=30000000,to1[22];
bool vis[22];
struct Edge
{
int w,v;
} e[22][22];
void dfs(int x,int d)
{
if(d==n)
{
if(ans1+to1[x]<ans) ans=ans1+to1[x];
return;
}
if(ans1+n-d+1>=ans) return;//预见性剪枝
vis[x]=1;
for(int i=2;i<=n;++i)
{
if(vis[e[x][i].v]) continue;
if(ans1+e[x][i].w>=ans) return;//剪枝
ans1+=e[x][i].w;
dfs(e[x][i].v,d+1);
vis[e[x][i].v]=0;//注意是e[x][i].v而不是i
ans1-=e[x][i].w;
}
}
bool cmp(Edge a,Edge b)
{
return a.w<b.w;
}
void readdata()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
scanf("%d",&e[i][j].w);
e[i][j].v=j;//记录终点编号
}
to1[i]=e[i][1].w;
sort(e[i]+1,e[i]+n+1,cmp);//排序
}
}
void work()
{
dfs(1,1);
printf("%d",ans);
}
int main()
{
readdata();
work();
return 0;
}