zoukankan      html  css  js  c++  java
  • P7093 [CERC2014]Can't stop playing (动态规划)

    题意:

    戳这里

    分析:

    DP

    来贡献一发最劣解的做法

    • 暴力

    我们发现由于长度之和不超过(2^{13}) ,所以放进去的块可以用一个不超过 (2^{14}) 的数字状压出来,其次我们手玩样例会发现一个性质,就是过程中拼出来的序列满足大小形状类倒着的 V

    证明:

    若存在 (a>b<c) ,那么 (b) 没有办法和任何一个块消掉

    所以我们把序列拆成左右两个单调的序列,然后每次判断一下能不能并到左边或者右边,只要满足以下任何一个条件即可:

    1. 左序列或者右序列为空
    2. 小于左序列或者右序列的最小值

    然后我们就得到了一个 (O(tn2^{26} imes13)) 的做法

    • 优化

    我们发现左右序列的和值固定,所以只需要枚举左序列的状态就可以了,复杂度降低为 (O(tn2^{13} imes 13)) ,但是由于直接枚举是跑满的,所以会 TLE

    • 正解

    我们发现状态转移是固定的,所以不需要枚举状态,直接 DFS 就可以了,并且预处理一下 highbit ,复杂度就变成了常数极小的 (O(n2^{13}))

    tip:

    1. 可能存在一种情况就是,左右序列都单调递增,所以转移的时候每次记得把左右序列相连的部分能合并就合并,不然会像我一样直接白给好多发

      比如 2|2 的情况添加一个 4,原本是添加不进去的,但先合并之后变成 4|0,然后就可以添加进去了

    2. 记忆化一下,保证复杂度正确

    代码:

    #include<bits/stdc++.h>
    #define inl inline
    #define reg register
    #define mk(x,y) make_pair(x,y)
    #define fir first
    #define sec second
    
    using namespace std;
    
    namespace zzc
    {
        typedef long long ll;
        inl ll read()
        {
            ll x=0,f=1;char ch=getchar();
            while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
            while(isdigit(ch)){x=x*10+ch-48;ch=getchar();}
            return x*f;
        }
    
        const ll maxn = 1005;
        const ll maxm = (1<<14);
        ll n,t;
        bool vis[maxn][maxm],ans[maxn];
        ll sum[maxn],a[maxn],hig[maxm];
    
        inl ll merge(ll x,ll y,ll k)//表示将 k 加到 x 序列上 
        {
            if(hig[x]>hig[y])//先将x序列最大值并到y序列上, 参考 tip:1 
            {
            	ll tmp=hig[x];
            	x-=tmp;
            	y+=tmp;
            }
            if(x&&(x&(-x))<k) return -1;x+=k;
            if(hig[x]==hig[y])//合并两个序列相连的部分 
            {
            	ll tmp=hig[x];
            	x+=tmp;
            	y-=tmp;
            }
            return x;
        }
        
    	bool dfs(ll pos,ll lef)
    	{
    		ll rig,now;
    		rig=sum[pos]-lef;
            if(pos==n)
            {
            	//将左右序列合并化到最简 
            	lef=merge(lef,rig,0);
            	rig=sum[pos]-lef;
            	if(lef==sum[n]&&(lef&(lef&(-lef)))==lef) return true;
            	if(rig==sum[n]&&(rig&(rig&(-rig)))==rig) return true;
            	return false;
            }
            if(vis[pos][lef]) return false;
            vis[pos][lef]=true;
            
            now=merge(lef,rig,a[pos+1]);
            if(now!=-1&&dfs(pos+1,now)) 
            {
                ans[pos+1]=true;
                return true;
            }
            
            now=merge(rig,lef,a[pos+1]);
            if(now!=-1&&dfs(pos+1,sum[pos+1]-now))
            {
                ans[pos+1]=false;
               	return true;
            }
            
            return false;
    	}
    	
    	void init()
    	{
    		for(reg ll i=1;i<(1<<14);i++)// 预处理 highbit 
    		{
    			if((i&(-i))==i) hig[i]=i;
    			else hig[i]=hig[i-1];
    		}
    	}
    	
        void work()
        {
        	init();
            t=read();
            while(t--)
            {
        	     memset(vis,false,sizeof(vis));
                 memset(ans,false,sizeof(ans));
                 n=read();
                 for(reg ll i=1;i<=n;i++) a[i]=read(),sum[i]=sum[i-1]+a[i];
                 if(!dfs(0,0)) printf("no");
                 else for(reg ll i=1;i<=n;i++) printf("%c",(ans[i]?'l':'r'));
                 puts("");
            }
        }
    
    }
    
    int main()
    {
        zzc::work();
        return 0;
    }
    
  • 相关阅读:
    工具
    选择排序
    c#中queue的用法
    c#加密
    话谈c#拷贝
    const与readonly的区别
    WinForm中使MessageBox实现可以自动关闭功能
    c#winform关闭窗口时触发的事件
    委托
    C# STA和MTA线程设置
  • 原文地址:https://www.cnblogs.com/youth518/p/14374159.html
Copyright © 2011-2022 走看看