zoukankan      html  css  js  c++  java
  • 题解 P1278 【单词游戏】

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

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

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

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

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

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

    前言

    首先,看到这道题目,我首先想到的是暴搜,通过vectorvector来搞,代码也是很短的。

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

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

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

    #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)试试?TLEagain!

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

    正文

    储存状态

    如何存状态

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

    1N161≤N≤16

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

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

    判断状态

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

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

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

    用位运算!

    如果第ii为是11,那么x>>(i1)x>>(i-1)mod  2mod2就是11

    如果第ii为是00,那么x>>(i1)x>>(i-1)mod  2mod2就是00

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

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

    标记状态

    如何将这一位变成11

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

    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.00s4.00s,也就是4000ms4000ms,而记忆化搜索总用时73ms73ms,快了不只一点。但是空间确实消耗很大。

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

  • 相关阅读:
    CAPTCHA---验证码 ---Security code
    Thymeleaf利用layout.html文件生成页面布局框架
    阿里云和腾讯云免费SSL证书 专题
    完整项目基础架构精简版-实现权限管理
    Android 基于XMPP Smack openfire 开发的聊天室
    Android 基于Bmob平台数据管理常用方法整理
    Android图表和图形创建库:EazeGraph
    Android 使用开源库StickyGridHeaders来实现带sections和headers的GridView显示本地图片效果
    Android MagicIndicator系列之一 —— 使用MagicIndicator打造千变万化的ViewPager指示器
    Android 比较两个时间段是否有交集或重复
  • 原文地址:https://www.cnblogs.com/zhaohaikun/p/12817016.html
Copyright © 2011-2022 走看看