洛谷P1092虫食算
传送门 | 难度 |
---|---|
https://www.luogu.com.cn/problem/P1092 | 提高+/省选- |
这是一道暴力回溯+剪枝
的题目。
这道题目的坑是真的多。尝试的时候出现了好多次50、70、80的情况,卡TLE了。
暴力回溯顺序
- 开始的时候我使用的是从A到第N个字母遍历的顺序,结果发现这样做会导致超时,然后换成了下面这种遍历顺序:
剪枝策略
- 利用最高位不会有进位,得到第1个剪枝策略,即str1和str2的最高位相加的和≥N时被剪枝:
alp[temstr1[0]] + alp[temstr2[0]] >= n
- 利用题中加法进位只可能为0或1的特点,得到第2个剪枝策略,即str1和str2的对应位相加的和模N恒等于str3对应位的值(或str3对应位的值+1),不满足时剪枝:
(tem1 + tem2) % n != tem3 && (tem1 + tem2 + 1) % n != tem3
使用以上的剪枝策略进行回溯就可以AC了。
以下是完整代码,需要注意的是,这里有个坑,进行回溯的时候,从n-1往前回溯可以,从0往后回溯会有2个测试点TLE(代码中给出了强调)。这个可能跟数据有关吧。
#include<iostream>
#include<stdio.h>
#include<cstring>
using namespace std;
int n;
int alp[30];//每个字母所对应的数字,0存A的,1存B的……
bool flag[30];//记录数字是否已被使用
bool alpuseflag[30];//生成nextalp时使用
int nextalp[30];//从n-1到0蛇形扫描时下一个字母所在的位置
char str1[30], str2[30], str3[30];//字符串1,字符串2,字符串3
int temstr1[30], temstr2[30], temstr3[30];//字符串每一位-'A'后的值
bool over = false;//结束标志
int cnt = 0;//辅助next
bool check() {
int stepon = 0;
int temsum = 0;
int temc = 0;
for (int i = n - 1; i >= 0; --i) {
temsum = alp[temstr1[i]] + alp[temstr2[i]] + stepon;
temc = temsum % n;
if (temc != alp[temstr3[i]]) {
return false;
}
temsum -= n;
stepon = temsum >= 0 ? 1 : 0;
}
return true;
}
bool canPrune() {//剪枝策略
if (over)
return true;
if (alp[temstr1[0]] + alp[temstr2[0]] >= n)
return true;
for (int i = n - 1; i >= 0; --i) {
if (alp[temstr1[i]] == -1 || alp[temstr2[i]] == -1 || alp[temstr3[i]] == -1)
continue;
int tem1 = alp[temstr1[i]];
int tem2 = alp[temstr2[i]];
int tem3 = alp[temstr3[i]];
if ((tem1 + tem2) % n != tem3 && (tem1 + tem2 + 1) % n != tem3)
return true;
}
return false;
}
void createNext(int index) {//蛇形顺序枚举
if (!alpuseflag[index]) {
alpuseflag[index] = true;
nextalp[cnt++] = index;
}
}
void pr() {
for (int i = 0; i < n - 1; ++i)
printf("%d ", alp[i]);
printf("%d\n", alp[n - 1]);
over = true;
return;
}
void recur(int current) {
if (canPrune())
return;
if (current == n)
if (check())
pr();
else
return;
//for (int i = 0; i < n; ++i) { 这样写会导致超时,只能得80分!!!!!!!
for (int i = n-1; i >= 0; --i) { //这样写可以AC!!!!!!!!
if (flag[i])
continue;
alp[nextalp[current]] = i;
flag[i] = true;
recur(current + 1);
if (over)return;
alp[nextalp[current]] = -1;
flag[i] = false;
}
}
void recurin() {
recur(0);
}
int main() {
while (scanf("%d", &n) != EOF) {
over = false;
memset(flag, 0, sizeof(bool) * 30);
cnt = 0;
for (int i = 0; i < 30; ++i) {
alp[i] = -1;
alpuseflag[i] = false;
nextalp[i] = -1;
}
scanf("%s%s%s", str1, str2, str3);
for (int i = n - 1; i >= 0; --i) {
temstr1[i] = str1[i] - 'A';
temstr2[i] = str2[i] - 'A';
temstr3[i] = str3[i] - 'A';
}
for (int i = n - 1; i >= 0; --i) {
createNext(temstr1[i]);
createNext(temstr2[i]);
createNext(temstr3[i]);
}
recurin();
}
return 0;
}
还有一个使用高斯消元的解法,以后补吧。