题解 P1278 【单词游戏】

4 篇文章 0 订阅
1 篇文章 0 订阅

Io和Ao在玩一个单词游戏。

他们轮流说出一个仅包含元音字母的单词,并且后一个单词的第一个字母必须与前一个单词的最后一个字母一致。

游戏可以从任何一个单词开始。

任何单词禁止说两遍,游戏中只能使用给定词典中含有的单词。

游戏的复杂度定义为游戏中所使用的单词长度总和。

编写程序,求出使用一本给定的词典来玩这个游戏所能达到的游戏最大可能复杂度。

前言

首先,看到这道题目,我首先想到的是暴搜,通过 v e c t o r vector vector来搞,代码也是很短的。

这里用了一个类似于分治的思想

把一个大问题转化为小问题

先枚举第一个单词,之后把能拼接在它后面的单词都一个一个的去试,哪个最优选哪个

#include <bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T&x){
	T f=1;x=0;char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
	for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	x*=f;
}//快读,常数优化
template<typename T>inline void write(T x){
	if(x<0){
		putchar('-');
		write(x*-1);
		return;
	}
	if(x>9)write(x/10);
	putchar(x%10+48);
}//快写,常数优化
string st[18];
vector<int>v[210];//动态数组
int f[18];//标记数组
int dfs(int x){
	int ans=0;
	for(auto i:v[st[x][st[x].size()-1]])//v数组是存第1个字母的一个容器
		if(!f[i]){
			f[i]=1;//标记这个字符串已经用过了
			ans=max(ans,dfs(i));//打擂
			f[i]=0;//回溯
		}
	return ans+st[x].size();
}
int main(){
	int ans=0,n;
	read(n);//读入
	for(int i=1;i<=n;i++)cin>>st[i],v[st[i][0]].push_back(i);//读入,放入vector容器
	for(int i=1;i<=n;i++){
		f[i]=1;//表记
		ans=max(ans,dfs(i));//打擂法找到最优解
		f[i]=0;//回溯
	}
	write(ans);//输出
    return 0;
}

然后,你会发现你只得了70分,开 O ( 2 ) O(2) O(2)试试?TLEagain!

想一想更优秀的算法,加记忆化?是的!

正文

储存状态

如何存状态

我们发现每一个字符串的状态都要么是0,要么是1,所以我们可以用二进制的思想去压缩状态。

1 ≤ N ≤ 16 1≤N≤16 1N16

2 n ( 16 ) = 65536 2^{n(16)}=65536 2n(16)=65536

开数组很充裕,浪费也不要紧。

判断状态

如何去判断第 i i i个单词有没有用过

从右往左这个数二进制的第 i i i位是 1 1 1,就代表这个单词用过,反之 0 0 0就代表这个单词没用过。

但给你这么一个数,你该这么去判断呢?

用位运算!

如果第 i i i为是 1 1 1,那么 x > > ( i − 1 ) x>>(i-1) x>>(i1) m o d    2 \mod2 mod2就是 1 1 1

如果第 i i i为是 0 0 0,那么 x > > ( i − 1 ) x>>(i-1) x>>(i1) m o d    2 \mod2 mod2就是 0 0 0

那么判断这个单词是否用过,我们就可以这么写

if(!((y>>(i-1)&1))//按位与,只有两个数这一位都是1才为1,所以只有当最后一位是1,这个数才会是1,否则是0

标记状态

如何将这一位变成 1 1 1

将这一位变成 1 1 1,我们可以用位运算中的按位或——两位都是 0 0 0,这一位的得数才为 0 0 0

y|(1<<(i-1))

这应该是很显然的

总结

现在就可以看总的代码了

#include <bits/stdc++.h>
using namespace std;
template<typename T>inline void read(T&x){
	T f=1;x=0;char ch=getchar();
	for(;!isdigit(ch);ch=getchar())if(ch=='-')f=-1;
	for(;isdigit(ch);ch=getchar())x=(x<<1)+(x<<3)+(ch^48);
	x*=f;
}
template<typename T>inline void write(T x){
	if(x<0){
		putchar('-');
		write(x*-1);
		return;
	}
	if(x>9)write(x/10);
	putchar(x%10+48);
}
string st[18];
vector<int>v[210];
int f[17][1<<17];
int dfs(int x,int y){
	if(f[x][y])return f[x][y];
	int ans=0;
	for(auto i:v[st[x][st[x].size()-1]])
		if(!((y>>(i-1))&1))ans=max(ans,dfs(i,y|(1<<(i-1))));
	return f[x][y]=ans+st[x].size();
}
int main(){
	int ans=0,n;
	read(n);
	for(int i=1;i<=n;i++)cin>>st[i],v[st[i][0]].push_back(i);
	for(int i=1;i<=n;i++)ans=max(ans,dfs(i,(1<<(i-1))));
	write(ans);
    return 0;
}

刚开始我认为这应该没有多少重复运算,所以我写了个暴搜,但是,我写了记忆化之后惊奇地发现,暴搜总用时 4.00 s 4.00s 4.00s,也就是 4000 m s 4000ms 4000ms,而记忆化搜索总用时 73 m s 73ms 73ms,快了不只一点。但是空间确实消耗很大。

编程中有很多算法,用空间换时间,记忆化搜索就是这么一个代表,我们要学习这种思想,想出更巧妙的办法!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值