[hrbust 2030] 成语接龙(状态压缩 + dfs)


传送门:http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=2030

与前一篇博客同样的思路,首先有用的信息只有字符串的前四个字符和后四个字符。同样需要将字符串转化成对应的数字。先抛开如何存信息的问题,谈谈如何"接龙"。假设信息已经存好了,那么肯定是一个头对应一个尾。我们假设程序是这样存储的:

head1 - tail1

head2 - tail2

head3 - tail3

...

那么从第一个"成语"的"尾巴"开始,搜以这个数字为开头的"成语",由于成语的开头或者成语的结尾可能有重复,所以需要for一边所有这个开头的成语。这样一层层搜下去,每次找到下一个就将接龙长度+1。
关于成语的存储相当简单。开一个结构体,存的是以这个成语开头的所有结尾、结尾数量以及结尾时候被用过。
struct node
{
int val; // 成语开头
int num; // 结尾的数量
int next[30]; // 以这个字开头的成语的所有结尾
int used[30]; // 这个结尾是否被使用过
}tt[30];
由于每次输入一个成语都要检查时候已经登记过开头,为了高效率,再开一个map存放所有成语的开头:

map<int, int> mp;

// 如输入成语bbbbccccdddd

// aaaa转化为对应的整数是18279,且这个开头没有被存储过

// 若当前已经用掉了3个位置,即tt[0]、tt[1]和tt[2]都有成语了

// 那么新成语的开头应该放到tt[3]这个位置了

// 那么mp[18279] = 3;


完整代码:

#include<iostream>
#include<map>
using namespace std;

struct node
{
int val; // 成语开头
int num; // 结尾的数量
int next[30]; // 以这个字开头的成语的所有结尾
int used[30]; // 这个结尾是否被使用过
}tt[30];

//成语的头所在的tt数组的位置
map<int, int> mp;
int ind, re, ans;

void dfs(int cur)
{
if(mp.find(tt[cur].val) == mp.end())
return;
for(int i = 0; i < tt[cur].num; i++)
{
if(!tt[cur].used[i])
{
re++;
ans = ans < re ? re : ans;
tt[cur].used[i] = 1;
dfs(mp[tt[cur].next[i]]);
re--;
tt[cur].used[i] = 0;
}
}
}

int main()
{
int n;
char str[35];
while(~scanf("%d\n", &n))
{
mp.clear();
ind = 0; // 用来标记有多少个不同的成语的"开头"
for(int i = 0; i < 30; i++)
{
tt[i].num = 0;
memset(tt[i].used, 0, sizeof(tt[i].used));
}
for(int i = 0; i < n; i++)
{
scanf("%s", str);
int len = strlen(str);
int head = 0, tail = 0;
// 将成语的头和为转化成相应的整数
for(int j = 0; j < 4; j++)
head = head * 26 + str[j] - 'a';
for(int j = len-4; j < len; j++)
tail = tail * 26 + str[j] - 'a';

if(mp.find(head) != mp.end())// 成语的开头重复了,只需在这个头上再登记个结尾
{
tt[mp[head]].next[tt[mp[head]].num++] = tail;
}
else // 新成语
{
tt[ind].val = head;
tt[ind].next[tt[ind].num++] = tail;
mp[head] = ind;
ind++;
}
}
re = ans = 0;
dfs(0);
printf("%d\n", ans);
}
return 0;
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值