搜索+状态搜索+二分
宽搜记录搜索的层数 :dist[]数组 直接在上一个上面+1 (使用数组模拟队列 数组的长度一定要不满所有n)
dfs记录搜索到的多少个点 : cnt+1 if(cnt==n*m) res++;
dfs()
第一步进来的时候才做处理
ans=max(ans ,);
回溯在递归条件外面做
used[]++;
一般进去进去才处理
for(里面递归){
dfs()
//不做回溯
}
used[]--;
如果是字符串求最小||最大长度 即找一个符合的值 从0到n 输出一个数 且对于满足i的数满足条件 比i大的数都能满足条件(单调性) 想到二分
大于等于某个数用:lower_bound() 满足情况的最小位置
小于等于某个数用:–upper_bound() uperbound返回第一个大于某个数 满足情况的最右边
大于某个数用:upper_bound()
小于某个数用:–lower_bound()
(如果是浮点数二分 记得不能>> 操作)
(double二分 l永远小于r,只是精度问题 return r)
二分模板 首先是升序 怎么记: 看是往左边找还是往右边找
如果往左边找 那么极限mid趋近最右边 要往左边找 需要r=mid 有边界等于mid
往右边找 极限mid在最左边 往右边找需要左边界等于mid
找到值要从最大可能的的值 数据范围 -10000<=n<=10000 那么l=-10000 r=10000开始搜;
如果没有重复的值 直接check函数就行了!不用考虑是≤还是<的问题
int find(int v)//输入要找的值 区间分成[l,mid]和[mid+1,r],找到最左边的等于v的值的下标 满足情况的最小元素
{
int l = 0, r = xs.size() - 1;
while (l < r)
{
int mid = l + r >> 1;
if (xs[mid] >= v) r = mid;//要往左边找 右边要确定下来所以是r 这里和下面每次循环其中一个,if(最好固定>=) 找到后直接将右边界设置在mid看看左边还有没有
else l = mid + 1;
}
return r;
}
只改变三个步骤
int find(int v)//输入要找的值 区间分成[l,mid]和[mid+1,r],找到最右边的等于v的值的下标 满足情况的最大元素 变1
{
int l = 0, r = xs.size() - 1;
while (l < r)
{
int mid = l + r + 1 >> 1;//变4 上取整 找最右边 当l=mid 就要+1
//*****
l=mid确定是往右边找 下面if小于等于 还是小于 :取决于 你是找到 小于某个数的最右边也就是小于某个数的最后一最后位置(< )这个数的下一个位置等于某个数) 还是等于v的最左边也就是等于值的第一个位置(<=)
//
if (xs[mid] <= v) l = mid;//变2,这里和下面每次循环其中一个,if(最好固定>=) 找到后直接将右边界设置在mid看看左边还有没有
else r = mid - 1;//变3
}
return r;
}
dfs ( k+1下一层树高度,sum+w[K] 本层的操作 ,index 可能有 用来表示宽度)
数据范围小 考虑搜索
简单全排列 直接用 next_permutation(a.begin(),a.end());
求集合
乱序
#include <iostream>
using namespace std;
typedef long long ll;
int n;
int a[20];
bool vis[20];
// 一共tar个坑,当前枚举到第pos个坑
void dfs(int pos, int tar) {
if (pos == tar + 1) {
for (int i = 1; i <= tar; i ++ ) cout << a[i] << " ";
cout << endl;
return ;
}
// 选数填坑,选择的数范围是1~n
for (int i = 1; i <= n; i ++) {
if (!vis[i]) {
vis[i] = true; a[pos] = i;
dfs (pos + 1, tar);
vis[i] = false;
}
}
}
int main() {
cin >> n;
for (int i = 0; i <= n; i ++ )
dfs(1, i);
return 0;
}
(升序)
#include <iostream>
using namespace std;
int n;
int a[20];
bool vis[20];
// 当前枚举到第pos个坑, 上一个坑填的是start-1,这次只能从start开始找数填, 一共要填tar个坑
void dfs(int pos, int start, int tar) {
if (pos == tar + 1) {
for (int i = 1; i <= tar; i ++ ) cout << a[i] << " ";
cout << endl;
return ;
}
// 选数填坑,选择的数范围是start~n
for (int i = start; i <= n; i ++) {
a[pos] = i;
dfs (pos + 1, i + 1, tar);//从选择了的第i个数开始 不需要记录一直取得的是最小的数
}
}
int main() {
cin >> n;
for (int i = 0; i <= n; i ++ )//集合几个数字
dfs(1, 1, i);
return 0;
}
求全排列 弱化版的求集合(只能提交n个数的情况,上面1-n都要)
#include <iostream>
using namespace std;
int n;
int a[20];
bool vis[20];
// 当前枚举到第pos个坑
void dfs(int pos ) {
if (pos == n + 1) {
for (int i = 1; i <= n; i ++ ) cout << a[i] << " ";
cout << endl;
return ;
}
//
for (int i = 1; i <= n; i ++) {
if(!vis[i]){
vis[i]=true;
a[pos] = i;//这里记录第几个坑
dfs (pos + 1);//不需要记录一直取得的是最小的数
vis[i]=false;
}
}
}
int main() {
cin >> n;
dfs(1);
return 0;
}
简单寻路:从左上方走到右下方每次只能走右边或下边走一步
class Solution {m为行,n为列
private:
int dfs(int i, int j, int m, int n) {
if (i > m || j > n) return 0; // 越界了
if (i == m && j == n) return 1; // 找到一种方法,相当于找到了叶子节点
return dfs(i + 1, j, m, n) + dfs(i, j + 1, m, n);
}
public:
int uniquePaths(int m, int n) {
return dfs(1, 1, m, n);
}
};//使用这个会遍历所有叶子节点 o(2^(n*m))超时
愤怒的奶牛
共有 N 个干草捆位于数轴中的不同整数位置,其坐标依次为 x1,x2,…,xN。
如果将奶牛射向位于位置 x 的干草捆,则该干草捆发生爆炸,爆炸半径为 1,爆炸将吞噬 1 单位距离内的所有干草捆。
然后这些干草捆(全部同时)发生爆炸,每个干草捆的爆炸半径为 2。
二次爆炸中吞噬的所有尚未爆炸的干草捆也(全部同时)发生爆炸,爆炸半径为 3。
也就是说,在 t 时刻爆炸的干草捆的爆炸半径为 t,其爆炸波及的干草捆在 t+1 时刻也会爆炸,爆炸半径为 t+1,以此类推。
请确定,通过合理选择奶牛射向的干草捆,能够引爆的干草捆最大数量。
N 范围 100,就是搜索无疑了
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 105,inf=2e9;
int p[N];
int main()
{
int n;cin>>n;
for (int i = 1; i <= n; i ++ ){
cin>>p[i];
}
sort(p+1,p+1+n);
int res=0;
p[0]=-inf,p[n+1]=inf;
for(int i=1;i<=n;i++){
int a=i,b=i,l=1,r=1;//分成左边和右边各自处理,a和b作为数组下标 ,l和r作为爆炸的波数
while(p[a]-p[a-1]<=l){//当左边下标 和 往左边的一个 的距离相差 小于爆炸的波束的时候进入处理
int k=a-1;//将这个下标保存下来
while(p[a]-p[k-1]<=l) k--;//找到在左边的的最后一个坐标的边界
l++;
a=k;//以这个边界为起点 开始爆炸
}
while(p[b+1]-p[b]<=r){
int k=b+1;
while(p[k+1]-p[b]<=r) k++;
r++;
b=k;
}
res=max(res,b-a+1);
}
cout << res;
return 0;
}
二分
暴力引爆每一个点
取L,R两个左右边界
每次向两边延申
以L为例子,每次找一个大于等于 *L + t 的点,如果这点是新点,则更新L,否则就是最远点
取两端边界距离为答案
#include<bits/stdc++.h>
using namespace std;
int main(){
int n, ans = 0; cin >> n;
vector<int> a(n);
for(int i = 0; i < n; i++) cin >> a[i];
sort(a.begin(), a.end());
for(auto i = a.begin(); i != a.end(); i++){//遍历所有起爆点
auto l = i, r = i;//初始值为起爆点
int tl = 1, tr = 1;//左右边界爆炸时间
while(lower_bound(a.begin(), l, *l - tl) != l)
l = lower_bound(a.begin(), l, *l - tl++);//左边界向左走
while(--upper_bound(r, a.end(), *r + tr) != r)//找到新的点
r = --upper_bound(r, a.end(), *r + tr++);//右边界向右走
ans = max(ans, (int)(r - l + 1));
}
cout << ans;
return 0;
}
奶牛选美https://www.acwing.com/problem/content/2062/
给定一个图求给定的两个连通块之间的最短距离 最短路
6 16
................
..XXXX....XXX...
...XXXX....XX...
.XXXX......XXX..
........XXXXX...
.........XXX....
输出
3
#include <iostream>
#include <cstring>
#include <algorithm>
#include <vector>
#define x first
#define y second
using namespace std;
typedef pair<int, int> PII;
const int N = 55;
int n, m;
char g[N][N];
vector<PII> points[2];//因为,使用vector存放两个连通图的点
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
void dfs(int x, int y, vector<PII>& ps)//只有一个ps
{
g[x][y] = '.';//防止重复搜
ps.push_back({x, y});//存放
for (int i = 0; i < 4; i ++ )
{
int a = x + dx[i], b = y + dy[i];
if (a >= 0 && a < n && b >= 0 && b < m && g[a][b] == 'X')
dfs(a, b, ps);//搜索下个连通图
}
}
int main()
{
cin >> n >> m;
for (int i = 0; i < n; i ++ ) cin >> g[i];
for (int i = 0, k = 0; i < n; i ++ )
for (int j = 0; j < m; j ++ )
if (g[i][j] == 'X')
dfs(i, j, points[k ++ ]);//细节k++
int res = 1e8;
for (auto& a: points[0])
for (auto& b: points[1])
res = min(res, abs(a.x - b.x) + abs(a.y - b.y) - 1);
//因为找最优解,如果不是最优解不能这么做
//计算曼哈段的距离 但需要输出的是点 不是距离
cout << res << endl;
return 0;
}
马蹄铁https://www.acwing.com/problem/content/description/2007/
(())
()((
(()(
))))
贝茜的移动步骤如下:
1())
2)((
345(
876)
( 可以到(或者 ) ,但是)只能到),于是状态
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 10;
int n;
char g[N][N];
bool vis[N][N];
int res;
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
void dfs(int i,int j,int l,int r){//l为左括号 r为右括号
vis[i][j]=true;
if(l==r){//括号相等的时候才可以
res=max(res,l+r);
vis[i][j]=false;//还原回去
return ;
}
for (int k = 0; k < 4; k ++ ){
int x=i+dx[k],y=j+dy[k];
if(x>=0&&x<n&&y>=0&&y<n&&vis[x][y]==false )
{
if(g[i][j]==')'&&g[x][y]=='(') continue;//剪枝 )和(
if(g[x][y]=='(') dfs(x, y,l+1, r);//
else dfs(x,y,l,r+1);
}
}
vis[i][j]=false;//还原
}
int main()
{
cin>>n;
for (int i = 0; i < n; i ++ ){
cin>>g[i];
}
if(g[0][0]=='('){//只有左上角是‘(’的时候才搜
dfs( 0,0 ,1,0);
}
cout << res;
return 0;
}
打乱字母 https://www.acwing.com/problem/content/1998/
将字母按照升序排 放到其他字母按照逆序排的数组里
将字母按照降序排 放到其他的单词按照升序排的数组里
排序 +二分搜索
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 5e4+10;
string all[N],up[N],down[N];
int main()
{
int n;cin>>n;
for (int i = 1; i <= n; i ++ ){
cin>>all[i];
up[i]=down[i]=all[i];
sort(up[i].begin() ,up[i].end());//顺序排 得到的字典序一般在前面
sort(down[i].begin(),down[i].end(),greater<char>());//字典逆序排,字典序一般都在后面
}
sort(up+1,up+1+n);
sort(down+1,down+1+n);
for (int i = 1; i <= n; i ++ ){
string s=all[i];
sort(s.begin(),s.end());//求顺序位置 得到字典序最小的位置
int a=lower_bound( down+1,down+1+n,s)-down;//小的在一群大的里面找
//返回第一个大于等于 最小的位置就是第一个位置
reverse(s.begin(),s.end() );
//大的在一群小的找
int b=upper_bound( up+1,up+1+n,s)-up-1;
//返回第一个大于x的数,所以最后能放的位置就是这个位置前一个位置
cout<<a<<" "<<b<<endl;
}
return 0;
}
我在哪https://www.acwing.com/problem/content/1462/
在一个字符串 里面的任意长度k 的 字串在字符串里都不会找到相同的字串
#include <iostream>
#include <cstring>
#include <algorithm>
#include <unordered_set>
using namespace std;
int n;
string str;
bool check(int len)
{
unordered_set<string> hash;
for (int i = 0; i + len - 1 < str.size(); i ++ )//每次寻找(i,i+len-1)那么长的串 然后必须小于字符串长度 否则越界
{
auto s = str.substr(i, len);//获得从i位开始 长度n的子串
if (hash.count(s)) return false;//如果发现了在哈希表里卖弄找到相同的字串 说明之前找到过
hash.insert(s);
}
return true;//如果这个长度的任意字串 都不重复 可以返回
}
int main()
{
cin >> n;
cin >> str;
int l = 1, r = n;
while (l < r)// 可以二分 这里找到是1-n满足的长度 而不是在字符串里找一个位置
{
int mid = l + r >> 1;
if (check(mid)) r = mid;//数字可以小点 往小的找
else l = mid + 1;
}
cout << r << endl;
return 0;
}
两种操作
给定一个正整数 n,我们希望你可以通过一系列的操作,将其变为另一个正整数 m。
操作共分两种:
将当前的数乘以 2。
将当前的数减去 1。
要求,在变换过程中,数字始终为正。
请你计算,所需要的最少操作次数。
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 20010;
int n, m;
int q[N], d[N];
int main()
{
cin >> n >> m;
memset(d, 0x3f, sizeof d);//到达距离变成无限大 注意这里不能赋值更大 因为随时可能会越界 这里是给int的每个字节赋值 所以是0x3f3f3f3f
d[n] = 0;//到达n本身是0
int hh = 0, tt = 0;//head tail
q[0] = n;
while (hh <= tt)//当队列不空
{
int t = q[hh ++ ];//每次取出队头的元素
int items[] = {t - 1, t * 2};
for (auto x: items)//枚举两种方案
if (x >= 1 && x < N && d[x] > d[t] + 1)//x在范围内 并且x可以被更新
{
d[x] = d[t] + 1;//更新距离
q[ ++ tt] = x;//将x加到队尾 因为开始t设置的是0 所以需要先自增
}
}
cout << d[m] << endl;//最小操作次数 是到达m这个位置
return 0;
}
买不到的数目(打表的爆搜)https://www.acwing.com/problem/content/1207/
对于两个正整数 输出最小的 不能由n的倍数 和m 的倍数 凑出的棵数
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
bool dfs(int n,int p,int q){
if(n==0) return true;//如果最后可以减到0 说明能够合成
if(n>=p&& dfs(n-p,p,q)) return true;//如果能减p就减p就成功
if(n>=q&& dfs(n-q,p,q)) return true;
return false;
}
int main()
{
int p,q;
cin >> p>>q;
int res=0;
for (int i = 1; i <= 1000; i ++ )//循环一直到n
if(!dfs(i,p,q )) res=max(res,i);
cout << res;
return 0;
}
树的重心
搜索 剩余部分的连通块点数的最大值
需要一个全局最小值 还需要记录一个sum求上面的数量以及点数 表示点数 一个res表示以这个结点为根节点的 各个连通块的点数
#include <iostream>
#include <algorithm>
#include <cstring>
using namespace std;
const int N = 1e5 + 10; //数据范围是10的5次方
const int M = 2 * N; //以有向图的格式存储无向图,所以每个节点至多对应2n-2条边
int h[N]; //邻接表存储树,有n个节点,所以需要n个队列头节点
int e[M]; //存储元素
int ne[M]; //存储列表的next值
int idx; //单链表指针
int n; //题目所给的输入,n个节点
int ans = N; //表示重心的所有的子树中,最大的子树的结点数目
bool st[N]; //记录节点是否被访问过,访问过则标记为true
//a所对应的单链表中插入b a作为根
void add(int a, int b) {
e[idx] = b, ne[idx] = h[a], h[a] = idx++;
}
// dfs 框架
/*
void dfs(int u){
st[u]=true; // 标记一下,记录为已经被搜索过了,下面进行搜索过程
for(int i=h[u];i!=-1;i=ne[i]){
int j=e[i];
if(!st[j]) {
dfs(j);
}
}
}
*/
//返回以u为根的子树中节点的个数,包括u节点
int dfs(int u) {
int res = 0; //存储 删掉某个节点之后,最大的连通子图节点数
st[u] = true; //标记访问过u节点
int sum = 1; //存储 以u为根的树 的节点数, 包括u,如图中的4号节点
//访问u的每个子节点
for (int i = h[u]; i != -1; i = ne[i]) {
int j = e[i];
//因为每个节点的编号都是不一样的,所以 用编号为下标 来标记是否被访问过
if (!st[j]) {
int s = dfs(j); // u节点的单棵子树节点数 如图中的size值
res = max(res, s); // 记录最大联通子图的节点数 可能是s为结点的子树
sum += s; //以j为根的树 的节点数
//这里的sum 记录下面的子树的数量之和 主要是为了求上面的数量 顺便返回
}
}
//n-sum 如图中的n-size值,不包括根节点4;
res = max(res, n - sum); // 选择u节点为重心,最大的 连通子图节点数
ans = min(res, ans); //遍历过的假设重心中,最小的最大联通子图的 节点数
return sum;
}
int main() {
memset(h, -1, sizeof h); //初始化h数组 -1表示尾节点 ,因为这个所以上面~i成立
cin >> n; //表示树的结点数
// 题目接下来会输入,n-1行数据,
// 树中是不存在环的,对于有n个节点的树,必定是n-1条边
for (int i = 0; i < n - 1; i++) {
int a, b;
cin >> a >> b;
add(a, b), add(b, a); //无向图
}
dfs(1); //可以任意选定一个节点开始 u<=n
cout << ans << endl;
return 0;
}
树的层次
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 2e5+10;
int q[N],d[N];
bool st[N];
int n,m;
int h[N],e[N],ne[N],idx;
void add(int a,int b){
e[idx]=b,ne[idx]=h[a],h[a] =idx++; ;
}
void bfs(){
int hh=0,tt=0;//hh tt初始化为0 队列中以及放入第一个元素
memset(d,-1,sizeof d);
q[0]=1;//队列第一个元素是1
d[1]=0;//从1开始 到1的距离自然是0
while(hh<=tt){
int t =q[hh++];
for(int i=h[t];~i;i=ne[i] )
{
int k=e[i];
if( d[k]==-1){
d[k]=d[t]+1;
q[++tt]=k ;
}
}
}
}
int main()
{
cin>>n>>m;
memset(h, -1, sizeof h);
for (int i = 0; i < m; i ++ ){
int a,b;cin>>a>>b;
add(a,b);
}
bfs();
cout << d[n];
return 0;
}
拓扑排序
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
const int N = 1e5+10;
int n,m;
int d[N];
int q[N];
int h[N],e[N],ne[N],idx;
void add(int a,int b){
e[idx]=b,ne[idx]=h[a] ,h[a]=idx++ ;
}
bool top(){
int hh=0,tt=-1;//这里只能是t=-1 因为队列还没放东西
for (int i = 1; i <= n; i ++ ){
if(d[i]==0) q[++tt]=i;
}
while(hh<=tt){
int t=q[hh++];
for (int i = h[t]; ~i; i =ne[i] ){
int t=e[i];//这里一开始写错了
d[t]--;
if(!d[t]) q[++tt]=t;
}
}
return tt==n-1;
}
int main()
{
cin >> n>>m;
memset( h,-1 ,sizeof h);
for (int i = 0; i < m; i ++ ){
int a,b;
cin>>a>>b;
add(a, b);
d[b]++;
}
if(top()){
for (int i = 0; i < n; i ++ ) cout << q[i]<<" ";
}
else cout << "-1"<<endl;
return 0;
}
二分
机器人跳跃问题https://www.acwing.com/problem/content/732/
因为输出一个满足的答案 又这个答案满足二分单调性
因为每次范围需要乘2需要提前退出
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int a[N];
int n;
bool check(long long e){
for (int i = 1; i <= n; i ++ ){
e=2*e-a[i];
if(e>1e5) return true;
if(e<0) return false;
}
return true;
}
int main()
{
cin>>n;
for (int i = 1; i <= n; i ++ )cin>>a[i];
int l=0,r=1e5;
while(l<r){
long long mid=l+r>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
cout << r;
return 0;
}
带分数https://www.acwing.com/problem/content/1211/
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
int f[N];
int a[N];
bool st[N];
int ans;
int n;
int cal(int l,int r){
int res=0;
for (int i = l; i <= r; i ++ ){
res=res*10+a[i];//拆开得到数字
}
return res;
}
void dfs(int u){
if(u==9) {
// for (int i = 0; i < 9; i ++ ) cout << a[i];
// cout << endl;
for (int i = 0; i < u-2; i ++ ){
for (int j = i+1; j <= u-2; j ++ ){//双指针 确定分开区间
int a=cal(0,i);
int b=cal(i+1,j);
int c=cal(j+1,u-1);
if(n*c==a*c+b) ans++;
}
}
return ;
}
for (int i = 1; i <=9 ; i ++ ){
if(!st[i]){
st[i]=true;
a[u]=i;
dfs(u+1);//递归模板求全排列
st[i]=false;
}
}
}
int main()
{
cin>>n;
dfs(0);
cout << ans;
return 0;
}
最佳牛围栏 https://www.acwing.com/activity/content/problem/content/1790/
二分答案法 找到一个满足条件的答案(具有二分性)
题解:https://www.acwing.com/solution/content/1148/
check:
1.需要找到一个大小为f的区间使得平均数大于ans 可以对所有数统计前缀和并且减去平均值 那么剩下存在大于0的宽度为f的区间就说明成立
2.需要找到区间长度可能大于 f 所以需要对j-f区间之前的sum数取得最小值minv minv-sum存在大于0 的区间就说明成立了
3.double mid = (l + r) / 2; // 不是>>1 这里是实数
#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e5+10;
double a[N],sum[N];
int n,m;
bool check(double mid ){//double
for (int i = 1; i <= n; i ++ ) sum[i]=sum[i-1]+a[i]-mid;
double minv=sum[0];
for(int i=0,j=m;j<=n;i++,j++){//这里i从0开始时是因为需要计算的是前缀和
minv=min(minv,sum[i]);
// cout << minv<<endl;
if(sum[j]-minv>=0) {return true;
}
}
return false;
}
int main()
{
cin >> n>>m;
double l=0,r=0;
for (int i = 1; i <= n; i ++ ){
cin >> a[i];r=max(r,a[i]);
}
while(r-l>=1e-5){
double mid=(l+r)/2;
if(check(mid)) l=mid;
else r=mid;
}cout<<(int)(r*1000);
return 0;
}
单词接龙
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 21;
int n;
string word[N];
int g[N][N];
int used[N];//记录使用多少次
int ans;
void dfs(string dragon, int last)
{
ans = max((int)dragon.size(), ans);//记录答案
used[last] ++ ;//进入后才加入这个
for (int i = 0; i < n; i ++ )//每个看看
if (g[last][i] && used[i] < 2)
dfs(dragon + word[i].substr(g[last][i]), i);//返回
used[last] -- ;
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> word[i];
char start;
cin >> start;
for (int i = 0; i < n; i ++ )
for (int j = 0; j < n; j ++ )
{
string a = word[i], b = word[j];
for (int k = 1; k < min(a.size(), b.size()); k ++ )
if (a.substr(a.size() - k, k) == b.substr(0, k))
{
g[i][j] = k;//g用来存第i个串到第j个串的最长重合的字串长度
break;//获得最短的长度 为了让最长
}
}
for (int i = 0; i < n; i ++ )
if (word[i][0] == start)//某个串的第一个字符是题目所给的 就递归
dfs(word[i], i);//第i个串
cout << ans << endl;
return 0;
}
分成互质组https://www.acwing.com/problem/content/1120/
把若干个数字分成组 每个组内的数字互为质数
判断是不是质数(gcd是否大于1)
using namespace std;
const int N = 10;
int n;
int p[N];
int group[N][N];//g存的下标
bool st[N];
int ans = N;
int gcd(int a, int b)
{
return b ? gcd(b, a % b) : a;
}
bool check(int group[], int gc, int i)
{
for (int j = 0; j < gc; j ++ )
if (gcd(p[group[j]], p[i]) > 1)
return false;
return true;
}
void dfs(int g, int gc, int tc, int start)
{//当前第几个组 第几个组的哪个元素 枚举到了原数组的多少个元素 这一层搜到的哪个元素
if (g >= ans) return;
if (tc == n) ans = g;//搜完了原数组n个元素
bool flag = true;
for (int i = start; i < n; i ++ )
if (!st[i] && check(group[g], gc, i))//这个元素没被用过而且和第i个数是互质的
{
st[i] = true;//标记
group[g][gc] = i;//添加
dfs(g, gc + 1, tc + 1, i + 1);
st[i] = false;//回溯
flag = false;//说明不用开新的组
}
if (flag) dfs(g + 1, 0, tc, 0);//开新的组
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++ ) cin >> p[i];
dfs(1, 0, 0, 0);
cout << ans << endl;
return 0;
}