ACM题集:https://blog.csdn.net/weixin_39778570/article/details/83187443
P1219 八皇后
题目:https://www.luogu.org/problemnew/show/P1219
题意:N皇后问题求解个数,部分输出
解法:回溯搜索每一行放置在哪一列就行
#include
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n;
int a[20],ans=0;
bool use[20];
bool ok(int row, int col){
fo(i,1,row-1){
if(a[row-i]-col==i || col-a[row-i]==i) return 0;
}
return 1;
}
void PF(){
fo(i,1,n){
printf("%d%c",a[i],i==n?'\n':' ');
}
}
void dfs(int row){
if(row>n){
ans++;
if(ans<=3)PF();
return;
}
fo(i,1,n){
if(!use[i] && ok(row,i)){
a[row]=i;
use[i] = 1;
dfs(row+1);
use[i] = 0;
}
}
}
int main(){
scanf("%d",&n);
dfs(1);
cout<<ans<<endl;
return 0;
}
P1019 单词接龙
题目:https://www.luogu.org/problemnew/show/P1019
题意:给定n个字符串,若两个字符串有重复部分则可以把两个字符串拼起来,所有字符串能拼接出来的最长字符串的长度
解法:
题目说两个拼接之后,另外相邻的两部分不能存在包含关系
就是说如果存在包含关系,就不能标记为使用过,就是at和ation这种,题目没说的很清 楚,就是指两个单词合并并且包含不能标记为使用过
上面那句话其实是提供了一个剪枝的条件,当重复字符串长度达到左字符串或者右字符串长度是不选
(ps:不剪也可以过啦…因为枚举的时候一样枚举选和不选两种情况啦)
这道题可以不使用string拼接
先看一下这组数据
3
actti s1
iox s2
ttioxasssss s3
a
13
s1,s2,s3个字符串和s1,s3字符串拼起来的长度是一样的,都是13
我们可以"敏锐地"发现,当中间字符串s2被右串包含s3的时候(该串不能单独和右串拼 接,iox和ttioxasssss不能拼接)
我们是可以"忽略"中间这个字符串的,因为它对长度并没有贡献
当s1包含s2的情况也是,s2没有贡献
所以我们不用考虑真的把两个字符串拼起来
比如字符串 s1, s2, s3
拼接 s3 的时候
s1和s2能拼接的情况我们只需要考虑
s2 是否能与 s3拼接
s1 是否能与 s3拼接这两种情况
具体做法我们在每次dfs的时候,每次让新加入的串作为"原串"去比较,每次只需要递归长度就行了
#include
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
string str[25];
int n,max_len;
int vis[25];
int check(string s1, string s2){
int len = min(s1.length(), s2.length());
int k = s1.length();
fo(i,1,len){ // 从小到大枚举长度i
bool f = 1;
for(int j=0; j<i; j++){ // 检查长度为i是否能匹配成功
if(s1[k-i+j] != s2[j]) f=0; // 出现不同
}
if(f)return i; // 贪心选择最小重叠
}
return 0;
}
void dfs(string now, int len){
max_len = max(max_len, len);
fo(i,1,n){
if(vis[i]==2)continue;
int k = check(now,str[i]);
if(k==len||k==str[i].length())continue; // 包含关系,可以剪枝,删了这一行也不会超时
if(k>0){ // 如果有重叠的话,可以选也可以不选
vis[i]++;
dfs(str[i], len+str[i].length()-k);
vis[i]--;
}
}
}
int main(){
cin>>n;
fo(i,1,n)cin>>str[i];
char c;
cin>>c;
fo(i,1,n){
if(str[i][0]==c){
vis[i]++;
dfs(str[i],str[i].length());
vis[i]--;
}
}
cout<<max_len<<endl<<endl;
return 0;
}
P1101 单词方阵
题目:https://www.luogu.org/problemnew/show/P1101
题意:给一n×n的字母方阵,内可能蕴含多个“yizhong”单词。单词在方阵中是沿着同一方向连续摆放的。摆放可沿着 8 个方向的任一方向,同一单词摆放时不再改变方向,单词与单词之间可以交叉,因此有可能共用字母。输出时,将不是单词的字母用*代替,以突出显示单词。
解法:搜索方阵中是否存在目标单词从八个方向进行搜索,要注意一个点,每个字符是可以被使用多次的!!!
可以使用一个数组记录路径
#include
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
char mp[105][105];
char goal[] = "yizhong";
bool vis[105][105];
int dx[] = {-1,-1,-1,0,0,1,1,1}; // 行
int dy[] = {-1,0,1,-1,1,-1,0,1}; // 列
int n;
// 其实完全可以一个for
vector<pair<int,int> > ver; // 记录路径
void dfs(int x,int y,int fx,int len){ // x,y,方向,长度
if(len==7){ // 搜索到终点了
for(pair<int,int> p : ver){
vis[p.first][p.second]=1;
}
return;
}
// 一次只走一种方向
int nowx = x + dx[fx];
int nowy = y + dy[fx];
if(goal[len]==mp[nowx][nowy]){
ver.push_back(make_pair(nowx,nowy));
dfs(nowx,nowy,fx,len+1);
}
}
void PF(){
fo(i,1,n)fo(j,1,n){
if(vis[i][j])putchar(mp[i][j]);
else putchar('*');
if(j==n)putchar(10);
}
}
int main(){
scanf("%d",&n);
fo(i,1,n){
scanf("%s",mp[i]+1);
}
int x,y;
fo(i,1,n)fo(j,1,n){
if(mp[i][j]=='y'){
fo(k,0,7){
x = i+dx[k];
y = j+dy[k];
if(mp[x][y]=='i'){ // 可以往下遍历
ver.clear(); // 每次dfs前记得清空数组
ver.push_back(make_pair(i,j));
ver.push_back(make_pair(x,y));
dfs(x,y,k,2);
}
}
}
}
PF();
return 0;
}
P1605 迷宫
题目:https://www.luogu.org/problemnew/show/P1605
题意:走迷宫求到达终点的方案数,求行走方案总数,每个格子只能走一遍
解法:每个格子只能走一遍,回溯一下就好
#include
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int block[10][10];
bool vis[10][10];
int dx[] = {-1,0,0,1};
int dy[] = {0,-1,1,0};
int ans,n,m,t,edx,edy;
void dfs(int x,int y){
if(x==edx&&y==edy){
ans++;
return;
}
fo(i,0,3){
int nowx = x+dx[i];
int nowy = y+dy[i];
if(nowx>=1&&nowx<=n && nowy>=1&&nowy<=m && block[nowx][nowy]==0&&!vis[nowx][nowy]){
vis[nowx][nowy]=1;
dfs(nowx,nowy);
vis[nowx][nowy]=0; // 回溯,还原现场
}
}
}
int main(){
scanf("%d%d%d",&n,&m,&t);
int x1,y1;
scanf("%d%d%d%d",&x1,&y1,&edx,&edy);
int xx,yy;
fo(i,1,t){
scanf("%d%d",&xx,&yy);
block[xx][yy]=1;
}
vis[x1][y1]=1;
dfs(x1,y1);
cout<<ans<<endl;
return 0;
}
P1040 加分二叉树
题目:https://www.luogu.org/problemnew/show/P1040
题意:设一个nn个节点的二叉树tree的中序遍历为(1,2,3,…,n=),其中数字1,2,3,…,n为节点编号。每个节点都有一个分数(均为正整数),记第ii个节点的分数为di,treedi,tree及它的每个子树都有一个加分,任一棵子树subtree(也包含treetree本身)的加分计算方法如下:
subtree的左子树的加分× subtree的右子树的加分+subtree的根的分数。
若某个子树为空,规定其加分为1,叶子的加分就是叶节点本身的分数。不考虑它的空子树。
试求一棵符合中序遍历为(1,2,3,…,n)且加分最高的二叉树tree。要求输出;
(1)treetree的最高加分
(2)treetree的前序遍历
解法:首先要知道各种序遍历是什么样的,中序遍历有个特点,就是跟节点可以是遍历输出顺序的任意一个,后续遍历则是一点在最后,前序遍历则一定在最前面
中序遍历的树根一定在区间中[L,R]区间的树根在L,R中
可以通过枚举区间的根来确定满足中序遍历的情况下,求区间表达式的值最大,并且同步更新区间的根
可以使用dfs或dp来解决
#include
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n,a[50];
ll dp[50][50],root[50][50];
ll dfs(int L, int R){
ll &ans = dp[L][R];
if(L>R)return 1; // 空树,定义空树的贡献值为1
if(dp[L][R]) return dp[L][R];
if(L==R)return dp[L][R] = a[L]; //叶子节点,不考虑它的空子树。
fo(k,L,R){
ll t = dfs(L,k-1)*dfs(k+1,R)+a[k]; // 树形dp,枚举中点
if(t>ans){
ans = t;
root[L][R] = k; // 跟新树根
}
}
return ans;
}
bool first = true;
void PF(int L, int R){
if(L>R)return;
if(first){
first = false;
}else printf(" ");
printf("%lld",root[L][R]); // 前序遍历输出
PF(L,root[L][R]-1);
PF(root[L][R]+1,R);
}
// 区间DP
void DP(){
fo(k,1,n){ // 区间长度
for(int i=1; i+k-1<=n; ++i){
int j = i+k-1;
dp[i][i-1] = 1; // 左树空
dp[j][j-1] = 1; // 右树空
fo(t,i,j){
ll ans = dp[i][t-1]*dp[t+1][j] + a[t];
// if(t==j&&dp[t+1][j])cout<
if(ans>dp[i][j]){
dp[i][j] = ans;
root[i][j] = t;
}
}
}
}
}
int main(){
scanf("%d",&n);
fo(i,1,n)scanf("%d",&a[i]),root[i][i]=i; // 一个节点的树根是自己!!!一定标记
// dfs(1,n);
DP();
printf("%lld\n",dp[1][n]);
PF(1,n);
return 0;
}
P1092 虫食算
题目:https://www.luogu.org/problemnew/show/P1092
题意:给你一个加法表达式,你知道哪些字符相同哪些不同,求出字符代表的值
解法一:直接枚举每个字符所代表的值,判断表达式的每一列是否会冲突,
式子某一列(加数1+加数2)%n(n进制)==得数
或者 (加数1+加数2+1)%n(n进制)==得数
这样做大概有70分左右
// 70 分
#include
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
int n;
char a[30],b[30],c[30];
bool vis[30];
int num[30];
int mp[30]; // 比map快多了,但还是没用
bool ok(){
int t1,t2,t3;
for(int i=1;i<=n; ++i){
t1 = num[mp[a[i]-'A'+1]];
t2 = num[mp[b[i]-'A'+1]];
t3 = num[mp[c[i]-'A'+1]];
if(t1==-1||t2==-1||t3==-1)continue;
if((t1+t2)%n!=t3 && (t1+t2+1)%n!=t3)return false;
}
return true;
}
void PF(){
int jinwei = 0;
int sum,t3;
for(int i=n; i>=1; i--){
sum = num[mp[a[i]-'A'+1]]+num[mp[b[i]-'A'+1]]+jinwei;
t3 = num[mp[c[i]-'A'+1]];
if(sum%n != t3)return;
jinwei = sum/n;
}
if(jinwei>0)return; // 最后一位不能进位
fo(i,1,n){
printf("%d%c",num[mp[i]],i==n?'\n':' ');
}
exit(0);
}
void dfs(int step){ // 枚举字符 A,B,C,D... 所对应的数字
if(step>n){
PF();
return;
}
for(int i=n-1;i>=0;i--){
if(!vis[i]){
num[step] = i;
if(ok()){
vis[i] = 1;
dfs(step+1);
vis[i] = 0;
}
}
}
num[step] = -1;
}
void solve(){
memset(num,-1,sizeof(num));
int k = 1;
fo(i,1,n){
if(!mp[a[i]-'A'+1])mp[a[i]-'A'+1] = k++; // 字母a[i]映射为下标 mp[a[i]-'A'+1]
if(!mp[b[i]-'A'+1])mp[b[i]-'A'+1] = k++;
if(!mp[c[i]-'A'+1])mp[c[i]-'A'+1] = k++;
}
dfs(1);
}
int main(){
scanf("%d",&n);
scanf("%s",a+1);
scanf("%s",b+1);
scanf("%s",c+1);
solve();
return 0;
}
一个一个字符搜索 n! 部分剪枝
但是算法的局限性在于 剪枝的时候 很难出现同一列的3个字母都枚举过了,那么就多了很多没有作用的判断
于是我们考虑枚举同一列
同一列,(行1 + 行2) % n != 行3 && (行1 + 行2+1) % n != 行3 剪枝 (进位)
这叫优先枚举顺序,另外,枚举字母的值的时候也是从大到小枚举
#include
#define ll long long
#define fo(i,j,n) for(register int i=j; i<=n; ++i)
using namespace std;
char s[4][30];
bool vis[30];
int n,num[300];
void PF(){
for(int i=0; i<n; i++){
printf("%d%c",num['A'+i],i==n-1?'\n':' ');
}
exit(0);
}
void dfs(int x, int y, int jinwei){ // 从第x行第y列进行搜索
// cout<
if(y==0){ // 枚举ok~
if(jinwei==0)PF();
return;
}
// 剪枝
for(int i=y-1; i>=1; i--){ // 对高位进行剪枝(高位可能出现不正确的情况,及时剪枝)
int w1 = num[s[1][i]];
int w2 = num[s[2][i]];
int w3 = num[s[3][i]];
if(w1==-1||w2==-1||w3==-1)continue;
if((w1+w2)%n!=w3 && (w1+w2+1)%n!=w3)return; // 不可能的情况
}
// 根据当前位是否赋值了进行搜索,如果是一二行的则不用判断,如果是第三行的赋值了则要判断赋值是否正确
if(num[s[x][y]]==-1){ // 该字母没赋值
for(int i=n-1; i>=0; i--) // 倒着枚举,快很多,更容易在低位的时候进位吧 ~
{
if(!vis[i]){ // 数字没用过
if(x!=3){ // 不是第三行
num[s[x][y]]=i; // 对字母进行赋值
vis[i] = 1;
dfs(x+1,y,jinwei);
num[s[x][y]]=-1;
vis[i] = 0;
}else{
int sum = num[s[1][y]] + num[s[2][y]] + jinwei;
if(sum%n!=i)continue; // 不正确的剪掉
num[s[x][y]]=i;// 对计算"正确"(当前正确,高位不一定正确)的字母进行赋值
vis[i] = 1;
dfs(1,y-1,sum/n);
num[s[x][y]]=-1;
vis[i] = 0;
}
}
}
}else{ // 赋值了
if(x!=3)dfs(x+1,y,jinwei); // 1,2行的不用判断
else{
int sum = num[s[1][y]] + num[s[2][y]] + jinwei;
if(sum%n!=num[s[3][y]])return; // 发现当前位不可能的情况剪枝
dfs(1,y-1,sum/n); // 继续往下搜索
}
}
}
void solve(){
memset(num,-1,sizeof(num));
dfs(1,n,0); // 第一行,右边第n列
}
int main(){
scanf("%d",&n);
scanf("%s",s[1]+1);
scanf("%s",s[2]+1);
scanf("%s",s[3]+1);
solve();
return 0;
}