• 题解 P1278 【单词游戏】


    前言

    首先,看到这道题目,我首先想到的是暴搜,通过(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))试试?TLEagain!

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

    正文

    储存状态

    如何存状态

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

    [1≤N≤16 ]

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

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

    判断状态

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

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

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

    用位运算!

    如果第(i)为是(1),那么(x>>(i-1))(mod2)就是(1)

    如果第(i)为是(0),那么(x>>(i-1))(mod2)就是(0)

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

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

    标记状态

    如何将这一位变成(1)

    将这一位变成(1),我们可以用位运算中的按位或——两位都是(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.00s),也就是(4000ms),而记忆化搜索总用时(73ms),快了不只一点。但是空间确实消耗很大。

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

  • 相关阅读:
    spring mvc controller间跳转 重定向 传参
    SpringMVC拦截器(资源和权限管理)
    Spring3 MVC 拦截器拦截不到的问题
    使用HandlerInterceptor实现简单的授权
    同一个form里,不管哪个 submit 都是直接提交form表单里的内容
    AJax+springMVC+JQURY.GET--注册界面即时刷新用户名是否存在
    Ajax异步检查用户名是否存在(附Demo下载)
    Ajax注册表单用户名实时验证
    SpringMVC记住密码功能(实例)
    CocoaPods停在Analyzing dependencies解决方案
  • 原文地址:https://www.cnblogs.com/zhaohaikun/p/12326293.html
Copyright © 2020-2023  润新知