2021.1.3搜索测试
【杂言】:
寡人怎么也没想到 T1写挂了,
【\(T1\)】:
【题目描述】:
给出一个n个未知数的方程,\(x_1,x_2,x_3......x_n\)求\(x_1+x_2+x_3....+x_n==S\)的正整数解的个数,并且要保证,对于任意\(i (1<=i< n)\) \(x_i\)与\(x_{i+1}\)相差不大于\(p\)
【数据范围】:
对于 \(30%\) 数据 \(2<=n<=10 ,p=0,S<=30;\)对于 \(100%\) 数据 \(2<=n<=10,p<=3 , S<=30;\)保证数据有梯度。
【考场代码】:(只有三十分,合着我暴力挂了):
/*
by : Zmonarch
知识点 :搜索
*/
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <queue>
#include <set>
#include <stack>
#include <cstring>
#define int long long
using namespace std ;
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
return x * f ;
}
int n , tot , s , p ;
void search(int now , int last ,int rest)
{
//printf("sum : %d\n" ,rest) ;
if(now == n + 1 ) //现在已经加到了最后一个数
{
if (rest == 0) tot ++ ;//方案数++
return ;
}
for(int i = 1 ; i < s && abs(i - last ) <= p; i++)//前一个数和后一个数不能超过p
{
search(now + 1 , i , rest - last - i) ;
}
}
signed main()
{
// freopen("math.in" , "r" , stdin) ;
// freopen("math.out" , "w" , stdout) ;
n = read() , s = read() , p = read() ;
//for(int i = 1 ; i <= s ; i++)
if(p == 0) //因为是正整数, 所以如果相差为零, 那么也就只有一种情况, 那就是全部都是同一个数的时候,除不尽就不是正整数了
{ //这里的相差是绝对值, 特判一下 ,
if(s % n == 0) printf("1") ;
else printf("0") ;
}
else
{
search( 0 , 0 ,s) ;
printf("%lld", tot) ;
}
return 0 ;
}
挂掉的原因是 : 1. 在进行下一次搜索的时候,忘记了这次枚举的数值, 与上一次的值相差不能超过\(p\);
2. 在初始化的时候 , 对于任意的满足条件的数值,都可以作为相加的开始值
【Std】:
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <queue>
#include <set>
#include <stack>
#include <cstring>
#define int long long
using namespace std ;
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
return x * f ;
}
int n , tot , s , p ;
void search(int now , int last ,int rest)
{
if(now == n) //现在已经加到了最后一个数
{
if (abs(rest - last) <= p) tot ++ ;
return ;
}
for(int i = max((long long)1 , last - p) ; i <= min(last + p , rest - n + i); i++)//前一个数和后一个数不能超过p
{
search(now + 1 , i , rest - i) ;
}
}
signed main()
{
//freopen("math.in" , "r" , stdin) ;
//freopen("math.out" , "w" , stdout) ;
n = read() , s = read() , p = read() ;
//for(int i = 1 ; i <= s ; i++)
if(p == 0) //因为是正整数, 所以如果相差为零, 那么也就只有一种情况, 那就是全部都是同一个数的时候,除不尽就不是正整数了
{ //这里的相差是绝对值, 特判一下 ,
if(s % n == 0) printf("1") ;
else printf("0") ;
}
else
{
for(int i = 1 ; i <= s-(n-1) ; i++)
search( 2 , i ,s - i) ;
printf("%lld", tot) ;
}
return 0 ;
}
【改完代码】:
/*
by : Zmonarch
知识点 :搜索
*/
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <queue>
#include <set>
#include <stack>
#include <cstring>
#define int long long
using namespace std ;
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
return x * f ;
}
int n , tot , s , p ;
void search(int now , int last ,int rest)
{
//printf("sum : %d\n" ,rest) ;
if(now == n) //现在已经加到了最后一个数
{
if (rest == 0) tot ++ ;//方案数++
return ;
}
for(int i = max( (long long)1 , last - p ) ; i <= s && abs(i - last ) <= p; i ++)//前一个数和后一个数不能超过p
{
search(now + 1 , i , rest - i) ;
}
}
signed main()
{
n = read() , s = read() , p = read() ;
//for(int i = 1 ; i <= s ; i++)
if(p == 0) //因为是正整数, 所以如果相差为零, 那么也就只有一种情况, 那就是全部都是同一个数的时候,除不尽就不是正整数了
{ //这里的相差是绝对值, 特判一下 ,
if(s % n == 0) printf("1") ;
else printf("0") ;
}
else
{
for(int i = 1 ; i <= s ; i++) //开始的值的时候也挂了
{
search( 1 , i , s - i) ;
}
printf("%lld", tot) ;
}
return 0 ;
}
【T2】:
【题目描述】:
给出一个\(n \times n\)的网格,有一些格子是障碍,再给出一对起点终点,求从起点到终点需要的最小步数,每次可以从一个格子走到上下左右4相邻的四个格子里.
【输入】:
第一行一个整数\(n\)。
以下\(n\)行\(n\)个整数,描述整个网格,其中\(0\)表示没有障碍,\(1\)表示有障碍。
最后一行四个整数,\(Sx,Sy,Tx,Ty\),描述起点和终点。
【输出】 :
输出最少步数。
如果永远走不到输出-1。
【数据范围】:
对于30%的数据,\(n<=10\)
对于 100%的测试数据,\(n<=1000\)。
【考场代码】:
大水题 ,一眼切 , 直接过。
/*
by : Zmonarch
知识点 : 搜索
*/
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <queue>
#include <set>
#include <stack>
#include <cstring>
#define int long long
using namespace std ;
const int kmaxn = 1000 + 1 ;
const int inf = 2147483647 ;
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
return x * f ;
}
int n , vis[kmaxn][kmaxn] , f[kmaxn][kmaxn] , sx , sy , tx , ty , ans = inf;
int dx[4] = {0 , 0 , 1 , -1} ;
int dy[4] = {1 , -1 , 0 , 0} ;
struct node
{
int x , y , step ;
};
queue<node> q ;
bool check(int x , int y)
{
return ( x <= n && y<=n && x >= 1 && y >= 1) ;
}
void search(node now)
{
q.push(now) ;
while(!q.empty())
{
node u = q.front() ;
q.pop() ;
int x = u.x , y = u.y , step = u.step ;
if(step >= ans) continue ; //小小的优化,截去不符合条件的搜索枝
if(u.x == tx && u.y == ty)
{
ans = min(ans , step) ;
}
for(int i = 0 ; i < 4 ; i++)
{
int kx = x + dx[i] ;
int ky = y + dy[i] ;
if(!check(kx , ky)) continue ; //检查边界
if(vis[kx][ky]) continue ;
vis[kx][ky] = 1 ;
node nxt = {kx , ky , step + 1} ;
q.push(nxt) ;
}
}
}
signed main()
{
//freopen("grid.in" , "r" , stdin) ;
//freopen("grid.out" , "w" , stdout) ;
n = read() ;
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= n ; j++)
{
vis[i][j] = read() ;
}
}
sx = read() , sy = read() , tx = read() , ty = read() ;
if(vis[sx][sy] || vis[tx][ty])
{
cout <<"-1" ;
return 0 ;
}
node ss = {sx , sy , 0} ;
search(ss) ;
if(ans != inf) printf("%lld" , ans) ;
else printf("-1") ;
return 0 ;
}
【T3】
【题目描述】 :
戈兰斜是一种在带数字的网格上玩的日本拼图游戏。目标是在网格的每个单元格中绘制对角线,连接到每个格点的对角线个数等于他对应的数字。另外,禁止对角线形成环。
第 一个 图给 出了游戏的初始状态。 第二个图给出了对应的一个解答。数据保证问题一定存在至少一解。
【输入】:
输 入的 第一 行包含一个的单个整数 \(n\)表示棋盘的尺寸,棋盘是一个正方形。
然后紧接 \(n+1\)行。包含网格的初始状态。每行为一个含\(n+1\)个字符的字符串,字符要么为一个数字,要么为一个(‘.’),
其中数字都是\(0\)到\(4\)之间的任意整数,‘.’表示连接到此格点的对角线数没有限制
【输出】:
输出包含 \(n\)行,每行 \(n\) 个字符,每个字符为斜杠或反斜杠表示如何填充相应的棋盘。
【数据范围】 :
对于 \(100%\)的数据 , \(n\leq 7\)
【考场】:
没什么很好的思路,准确的来说,没思路, 就不放代码了。
【正解思路】:
首先 , 这个问题分为四个部分 , 判断是否有回路 , 是否符合对角线的限制 ,对角线的判断 , 和搜索 ,这四个模块写完, 也就\(OK\)了。
1.有回路 : 我们可以设一个并查集, 只要让他们不会有斜着交叉的对角线出现,那么构成回路的条件也就只有4个顶点的父亲都是一个点了。
2.是否符合对角线的限制 , 我们设数组\(m\)表示原来的限制, 我们再设一个\(cur\)表示现在放了多少 , \(lim\)表示还能放多少 , 然后判断一下即可
3.对角线的判断 : 可以分两块进行求解 , 一块是从左上角到右下角的这一类的斜线 , 另一块是从右上角到左下角的这一类的斜线 ;
4.搜索 : 注意回溯 , 然后类比一下普通的棋盘,那么走就行了, 但是我们更新的时候是从左到右, 从上到下更新的。所以\(dx_i\) 和\(dy_i\)应当注意一下
【STd】:
#include<cstdio>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
using namespace std;
int chgx[4]={0,0,1,1},chgy[4]={0,1,0,1};
int n,ok;
int dad[101],ps[101][101],sum[101][101],a[101][101],cz[101][101];
int getnum()
{
char c=getchar();
while(!((c>='0' && c<='4') || c=='.')) c=getchar();
if(c=='.') return -1;
else return c-'0';
}
int findad(int x)
{
if(!dad[x]) return x;
return findad(dad[x]);
}
int psbl(int x,int y)
{
if(a[x][y]==-1) return true;
if(sum[x][y]<=a[x][y] && sum[x][y]+ps[x][y]>=a[x][y]) return true;
return false;
}
void dfs(int x,int y)
{
if(y==n) y=1,x++;
if(x==n)
{
ok=1;
return;
}
int t1,t2,i,pd=0;
++sum[x][y],++sum[x+1][y+1],--ps[x][y],--ps[x+1][y+1],--ps[x+1][y],--ps[x][y+1];
for(i=0;i<4;i++)
if(!psbl(x+chgx[i],y+chgy[i])) {pd=1;break;}
if(!pd)
{
t1=findad((x-1)*n+y),t2=findad(x*n+y+1);
if(t1!=t2)
{
cz[x][y]=0;
dad[t1]=t2;
dfs(x,y+1);
if(ok) return;
dad[t1]=0;
}
}
--sum[x][y],--sum[x+1][y+1],++sum[x+1][y],++sum[x][y+1],pd=0;
for(i=0;i<4;i++)
if(!psbl(x+chgx[i],y+chgy[i])) {pd=1;break;}
if(!pd)
{
t1=findad((x)*n+y),t2=findad((x-1)*n+y+1);
if(t1!=t2)
{
cz[x][y]=1;
dad[t1]=t2;
dfs(x,y+1);
if(ok) return;
dad[t1]=0;
}
}
--sum[x+1][y],--sum[x][y+1],++ps[x][y],++ps[x+1][y+1],++ps[x+1][y],++ps[x][y+1];
}
int main()
{
int i,j,pd;
freopen("gokigen.in","r",stdin);
freopen("gokigen.out","w",stdout);
scanf("%d",&n),n++;
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
{
ps[i][j]=4,a[i][j]=getnum(),pd=0;
if(i==1 || i==n) ps[i][j]--,pd=1;
if(j==1 || j==n) ps[i][j]--,pd=1;
ps[i][j]-=pd;
}
dfs(1,1);
for(i=1;i<n;i++)
{
for(j=1;j<n;j++)
{
if(cz[i][j]) putchar('/');
else putchar('\\');
}
putchar('\n');
}
}
【Code】:
/*
by : Zmonarch
知识点 : 搜索
*/
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <iostream>
#include <queue>
#include <set>
#include <stack>
#include <cstring>
#define int long long
using namespace std ;
const int kmaxn = 1000 + 1 ;
const int inf = 2147483647 ;
inline int read()
{
int x = 0 , f = 1 ; char ch = getchar() ;
while(!isdigit(ch)) { if(ch == '-') f = - 1 ; ch = getchar() ; }
while( isdigit(ch)) { x = x * 10 + ch - '0' ; ch = getchar() ; }
return x * f ;
}
inline int getnum()
{
char c = getchar() ;
while (!(c == '.' || (c >= '0' && c <= '4'))) c = getchar() ;
if(c == '.') return - 1 ;
else return c - '0' ;
}
const int dx[4] = {0 , 0 , 1 , 1} ;//判定4个方位
const int dy[4] = {0 , 1 , 0 , 1} ;//从左向右 , 从上到下依次更新
int n ;
bool flag , ans[101][101] ;
int point_to[kmaxn] , m[101][101] , cur[101][101] , lim[101][101] ;
// m用来存原始棋盘 , point_to表示的是父亲是谁 ,lim表示最多有几条对角线, cur表示现在有几条对角线
void prepare() // 有多组数据
{
flag = 0 ;
memset(cur , 0 , sizeof(cur)) ;
memset(point_to , 0 , sizeof(point_to)) ;
}
bool board(int x , int y)
{
if(m[x][y] == -1) return 1 ;//对于没有限制的点, 自然可以
if(cur[x][y] <= m[x][y] && cur[x][y] + lim[x][y] >= m[x][y]) return 1;
//如果当前的对角线数已经到了目标数目, 那么停止,否则就继续
return 0 ;
}
int find(int x ) //寻找其父亲
{
if(!point_to[x]) return x ;
return find(point_to[x]) ;
}
void search(int x , int y)
{
if(y == n) y = 1 , x ++ ;//继续下一行
if(x == n) { flag = 1 ; return ; } //下一行就没有了
int t1 , t2 , pd = 0 ;
//先处理左上到右下的斜线
cur[x][y]++ , cur[x + 1][y + 1]++;
lim[x][y]-- , lim[x + 1 ][y]-- , lim[x][y + 1]-- , lim[x + 1][y + 1]--;
for(int i = 0 ; i < 4 ; i++) //继续向下更新
{
int kx = x + dx[i] , ky = y + dy[i] ;
if(!board(kx , ky))
{
pd = 1 ;
break ;
}
}
if(!pd)
{
t1 = find( (x - 1) * n + y ) , t2 = find(x * n + y + 1) ;
// (x-1) * n + y是左上的编号 , x*n + y+1右下的编号
if(t1 != t2) //不在一个并查集中
{
ans[x][y] = 1 ;
point_to[t1] = t2 ;
search(x , y + 1) ;
if(flag) return ;
point_to[t1] = 0 ; // 回溯
}
}
cur[x][y]-- , cur[x + 1][y + 1]-- ;//令其回溯
//右上到坐下的斜线
cur[x + 1][y]++ , cur[x][y + 1]++ ;
pd = 0 ;
for(int i = 0 ; i < 4 ; i++)
{
int kx = x + dx[i] , ky = y + dy[i] ;
if(!board(kx , ky))
{
pd = 1 ; break ;
}
}
if(!pd)
{
t1 = find( x * n + y ) , t2 = find( (x - 1) * n + y + 1 ) ;
if(t1 != t2 )//对于有没有环,看这个点的连边是否为4条
{
ans[x][y] = 0 ;
point_to[t1] = t2 ;
search(x , y + 1) ;
if(flag) return ;
point_to[t1] = 0 ;
}
}
cur[x + 1][y] -- , cur[x][y + 1] --;
lim[x][y]++ , lim[x + 1][y]++, lim[x][y + 1] ++ , lim[x + 1][y + 1]++ ;
}
signed main()
{
int t = read() ;
while(t--)
{
prepare() ;
n = read() , n++;
for(int i = 1 ; i <= n ; i++)
{
for(int j = 1 ; j <= n ;j++)
{
lim[i][j] = 4 ;
m[i][j] = getnum() ;
//处理边边角角
if((i == 1 || i== n) && (j ==1 || j == n)) // 处理角
{
lim[i][j] = 1 ;
continue ;
}
if(i == 1 || i == n || j == 1 || j == n)//处理边
{
lim[i][j] = 2 ;
continue ;
}
}
}
search(1 , 1);//从最左上角开始搜索
for(int i = 1 ; i < n ;i++)
{
for(int j = 1 ; j < n ; j++)
{
if(!ans[i][j]) printf("/");
else printf("\\");
}
printf("\n") ;
}
}
return 0 ;
}