周总结
- 深搜
- [P1019 NOIP2000 提高组单词接龙](https://www.luogu.com.cn/problem/P1019)
- [P1037 NOIP2002 普及组 产生数](https://www.luogu.com.cn/problem/P1037)
- [P1112 波浪数](https://www.luogu.com.cn/problem/P1112)
- [P1118 USACO06FEBBackward Digit Sums G/S](https://www.luogu.com.cn/problem/P1118)
- [P1123 取数游戏](https://www.luogu.com.cn/problem/P1123)
深搜
P1019 NOIP2000 提高组单词接龙
题意:成语接龙,规定开头字母,字符串接龙找最长,每个单词最多使用两次
解题思路:搜索
需要一个数组记录单词次数,一个单词最多两次,visit数组
check函数,是在枚举接口长度的时候判断接口长度的可行性
add函数,简单的连接函数,注意引用
最重要的是深搜,dfs函数
#include <bits/stdc++.h>
using namespace std;
/*
肯定有不同的方案,但是还需要将重叠的部分去掉不计算
还需要记录次数
*/
const int N=100;
int n;
int ans=0;
string word[N];//存储单词
string inti;//原始
int visit[N];//用来记录dfs时候每个单词被使用了几次数组
//k应该是之前方案的最优解
bool check(string s,string m,int k)//check函数判断接口的可行性
{//把m拼接到s上,k为接口长度
int l=s.length();
for(int i=0;i<k;i++)
{
if(s[l-k+i]!=m[i]) return false;
}
return true;
}
void add(string &s,string m,int k)//因为是要吧m接到s上,所以引用传参
{
int ll=m.length();
for(int i=k;i<ll;i++)
{
s+=m[i];//k是最小拼接长度的位置
}
}
void dfs(string now)
{
int x=now.length();
ans=max(ans,x);//拼接之后更新长度,ans初始为0
for(int i=1;i<=n;i++)
{
if(visit[i]>=2)//为什么这个判断在这?
continue;
int maxk = word[i].length();
for(int j=1;j<=maxk;j++)//枚举接口长度,其实这一个过程也在不断地更新
{
if(check(now,word[i],j))
{
string temp=now;//存一个原版为了拼接
add(temp,word[i],j);
if(temp==now)//如果是原串就没有意义不进行接下来的操作
continue;
visit[i]++;
dfs(temp);
visit[i]--;//回溯???看似平淡无奇?!
}
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
{
cin>>word[i];
}
cin>>inti;
dfs(inti);
cout<<ans<<endl;
return 0;
}
P1037 NOIP2002 普及组 产生数
暴力
#include <bits/stdc++.h>
#define long long ll
using namespace std;
//包括原数 能变化出多少种数
//一个数能变成的次数相乘起来就是一个整数能变成的次数
int n,k;
string s;
int v[10][10];//顶多0~9
int f[10];
int num[101];
void floyd()
{
for(int k=0; k<=9; k++)
for(int i=0; i<=9; i++)
for(int j=0; j<=9; j++)
v[i][j]=v[i][j]||(v[i][k]&&v[k][j]);
}
int main()
{
cin>>s>>k;
while(k--)
{
int a,b;
cin>>a>>b;
v[a][b]=true;//a变成b
}
for(int i=0; i<=9; i++) v[i][i]=true ; //当然自己可以变成自己
floyd();
for(int i=0; i<=9; i++)
for(int j=0; j<=9; j++)
if(v[i][j]) f[i]++;//求出i可以变成的数字
int len=2;
num[1]=1;
for(int i=0; i<s.length(); i++) //高精度
{
for(int j=1; j<=100; j++)
num[j]*=f[s[i]-'0'];
for(int j=1; j<=100; j++) //每一位都要相乘
if(num[j]>=10) //进位
{
num[j+1]+=num[j]/10;
num[j]%=10;
}
while(num[len]!=0) len++;//求出长度
}
for(int i=len-1; i>=1; i--) cout<<num[i]; //输出;
cout<<endl;
return 0;
}
P1112 波浪数
题意理解补充:所谓双重数,是在两种进制下,都是波浪数。
例如,十进制数191919191919是一个十进制下的波浪数,它对应的十一进制数121212121212也是一个波浪数,所以十进制数191919191919是一个双重波浪数。
那么三重,四重波浪数类似。
#include <bits/stdc++.h>
using namespace std;
//在指定范围内找到双重,三重,四重波浪数
//搜索进制构造数论数学
const int N=10000005;
//哈希
int v[N];
int a,b,l,r,c,t,x;
//直接暴力不知道能不能过了
int main()
{
cin>>a>>b>>l>>r>>c;
for(int k=a; k<=b; k++)//k是代表进制
for(int i=1; i<k; i++)
for(int j=0; j<k; j++)
{
if(i!=j)//不能构成相同的波浪数字
{
x=0;
t=0;
while(x<=r)
{
if(t%2==0)//如果是偶数位置就加i
{
x=x*k+i;
t++;
}else{//奇数位置
x=x*k+j;
t++;
}
if(x>=l&&x<=r) v[x]++;
//cout<<v[x]<<"v[x] "<<x<<endl;
}
}
}
for(int i=l;i<=r;i++){
if(v[i]==c) cout<<i<<endl;
}
}
P1118 USACO06FEBBackward Digit Sums G/S
典型深搜思想
//有很多种情况输出字典序最小的那种情况
//1到n的排列,字典序
//暴力的方法当然是按字典序枚举最开始的序列,将每个排列都计算枚举得到最后的
//如果这个值和题目中的sum相等就找到了答案
//如何按照字典序枚举,可以使用排序函数,也可以在dfs枚举排列时,每一层都从小到大枚举这样自然就是按照字典序枚举了
/*
举例子做,各项系数恰好与杨辉三角有关系
c[1][1]=1;
for(int i=2;i<=n;i++)//由于这里数组的记录是从1开始的,所以不用担心越界
for(int j=1;j<=i;j++)
c[i][j]=c[i-1][j]+c[][];//每个数都等于它肩上的两个数的和
*/
#include <bits/stdc++.h>
using namespace std;
//有很多种情况输出字典序最小的那种情况
//1到n的排列,字典序
//暴力的方法当然是按字典序枚举最开始的序列,将每个排列都计算枚举得到最后的
//如果这个值和题目中的sum相等就找到了答案
//如何按照字典序枚举,可以使用排序函数,也可以在dfs枚举排列时,每一层都从小到大枚举这样自然就是按照字典序枚举了
/*
举例子做,各项系数恰好与杨辉三角有关系
c[1][1]=1;
for(int i=2;i<=n;i++)//由于这里数组的记录是从1开始的,所以不用担心越界
for(int j=1;j<=i;j++)
c[i][j]=c[i-1][j]+c[][];//每个数都等于它肩上的两个数的和
*/
int n,p;
int a[13];//输出
int c[13][13];//杨辉三角
bool b[13];//判重必备
void dfs(int dep,int s)
{
if(s>p) //如果累加的数已经超过给定的数就返回
{
return ;
}
if(dep>n)
{
if(s==p) //如果答案跟给定的数相等
{
cout<<a[1];
for(int i=2; i<=n; i++)
{
cout<<" "<<a[i];
}
exit(0);//进程的终止
}
return ;//如果没有输出答案就返回
}
for(int i=1; i<=n; i++)
{
if(b[i]==false)//如果当前这个数没有用过
{
b[i]=true;//标记为用过
a[dep]=i;//保存第dep个取的数
dfs(dep+1,s+i*c[n][dep]);
b[i]=false;//状态回归
}
}
}
int main()
{
cin>>n>>p;
c[1][1]=1;//初始化杨辉三角
for(int i=2; i<=n; i++)
{
for(int j=1; j<=i; j++)
{
c[i][j]=c[i-1][j]+c[i-1][j-1];//生成杨辉三角
}
}
dfs(1,0);//深搜
return 0;
}
P1123 取数游戏
#include <bits/stdc++.h>
//取数字任意两个不相邻使其和相加最大
//因为会影响到下一步的选择所以排除了贪心和动态规划
//这个题其实不会做!!!!!
//感觉这个 别人写的搜索有些奇怪
int n,m;
using namespace std;
const int N=21;
int d[8][2]={1,0,-1,0,0,1,0,-1,1,1,-1,1,1,-1,-1,-1};//方向数组用来控制搜索的方向
int t,s[8][8],mark[8][8],ans,mx;
void dfs(int x,int y)//搜索函数,表示搜索点(x,y)
{
if(y==m+1)//继续搜索下一行
{
dfs(x+1,1);
return ;
}
if(x==n+1){//当边界时,搜索就结束了
mx=max(ans,mx);//更新最大值
return ;
}
dfs(x,y+1);//不去次数的情况
if(mark[x][y]==0)//取次数时,需保证次数周围没有 其他数
{
ans+=s[x][y];
for(int fx=0;fx<8;++fx){//标记周围的数字
++mark[x+d[fx][0]][y+d[fx][1]];
}
dfs(x,y+1);
for(int fx=0;fx<8;fx++)//回溯
{
--mark[x+d[fx][0]][y+d[fx][1]];
}
ans-=s[x][y];
}
}
int main()
{
int t;
cin>>t;
while(t--)
{
cin>>n>>m;
memset(s,0,sizeof(s));
memset(mark,0,sizeof(mark));
for(int i=1; i<=n; i++)
for(int j=1; j<=m; j++)
cin>>s[i][j];
mx=0;
dfs(1,1);//从1 1开始搜索
cout<<mx<<endl;
}
return 0;
}