v·y「状压dp」

一直对状压dp怀有一种恐惧感

不会打,不会调,关键是不会调

做了这两道题,虽然还是不会状压dp,但总比之前好了一些

y

普通状压应该很好打

复杂度$O(2^d*n*(n+m))$

    for(ll i=2;i<=d;i++){
        for(ll state=0;state<=maxn;state++){
            for(ll x=1;x<=n;x++){
                for(ll j=head[x];j;j=nxt[j]){
                    ll y=ver[j];
                    if(!f[i-1][x][state]) continue ;
                    f[i][y][(state<<1)|edg[j]]|=f[i-1][x][state];
                }
            }
        }
    }

那么该怎么优化,

折半搜索,你起点是确定的,枚举中间点,这样复杂度就降低成$O(2^{\frac{d}{2}}*n*(m+n)+2^d*n)$

最终枚举中间点,

这样做思想很重要

代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define A 10101010
bool f1[21][93][1<<12],f2[21][93][1<<12],hash_map[1<<22];
ll nxt[A],ver[A],head[A],edg[A];
ll n,m,q,ans,tot,d;
void add(ll x,ll y,ll z){
    nxt[++tot]=head[x],head[x]=tot,ver[tot]=y,edg[tot]=z;
}
void turn(ll x,ll n)
{
     ll t=x,num=0,xx[100];
     while(x) xx[num++]=x%2,x/=2;
     for(ll i=num;i<n;i++)printf("0");
     for(ll i=num-1;i>=0;i--)cout<<xx[i];
 //    puts("");
}
int main(){
    scanf("%lld%lld%lld",&n,&m,&d);
    for(ll i=1,a,b,c;i<=m;i++){
        scanf("%lld%lld%lld",&a,&b,&c);
        add(a,b,c);add(b,a,c);
    }
    for(ll i=head[1];i;i=nxt[i]){
        ll y=ver[i];
        f1[1][y][edg[i]]=1;
    }
    ll d1=d/2,d2=d-d1;
    ll maxn1=(1<<(d1))-1,maxn2=(1<<(d2))-1;
//    printf("d1=%lld d2=%lld\n",d1,d2);
    for(ll i=2;i<=d1;i++){
        for(ll state=0;state<=((1<<i)-1);state++){
            for(ll x=1;x<=n;x++){
                for(ll j=head[x];j;j=nxt[j]){
                    ll y=ver[j];
                    if(!f1[i-1][x][state]) continue ;
                    f1[i][y][(state<<1)|edg[j]]|=f1[i-1][x][state];
                }
            }
        }
    }
    for(ll i=1;i<=n;i++){
        for(ll j=head[i];j;j=nxt[j]){
            ll y=ver[j];
            f2[1][y][edg[j]]=1;
        }
    }
//    printf("f2[1][1][1]=%lld\n",1ll*f2[1][1][1]);
    for(ll i=2;i<=d2;i++){
        for(ll state=0;state<=((1<<i)-1);state++){
            for(ll x=1;x<=n;x++){
                for(ll j=head[x];j;j=nxt[j]){
                    ll y=ver[j];
                    if(!f2[i-1][x][state]) continue ;

//                    printf("f2[%lld][%lld][%lld]=%lld\n",i-1,x,state,1ll*f2[i-1][x][state]);
                    f2[i][y][(state<<1)|edg[j]]|=f2[i-1][x][state];
//                    printf("f2now[%lld][%lld][%lld]=1\n",i,y,(state<<1)|edg[j]);
                }
            }
        }
    }
    for(ll state=0;state<=maxn1;state++){
        for(ll state1=0;state1<=maxn2;state1++)
            for(ll i=1;i<=n;i++){
//                printf("f1[%lld][%lld][%lld]=%lld f2[%lld][%lld][%lld]=%lld state1=%lld\n",d1,i,state,1ll*f1[d1][i][state],d2,i,state1,1ll*f2[d2][i][state1],maxn2);
                if(f1[d1][i][state]&&f2[d2][i][state1]){
                    ll sum=state<<d2|state1;
//                    printf("***** i=%lld state=%lld state2=%lld\n",i,state,state1);
//                    turn(sum,1);
//                    printf("\n");
                    if(!hash_map[sum])
                        hash_map[sum]=1,ans++;
                }
        }
    }
    printf("%lld\n",ans);
}

v

题解

记忆化搜索+hash表

关于题目中说的编号右移,二进制下模拟一下就行了

        st3=(st>>(l-p+1)<<(l-p))|((st&((1<<(l-p))-1)));

先右移再左移消掉这一位,然后后面保持原样

代码

#include<bits/stdc++.h>
using namespace std;
#define ll int
#define mod 19260817
struct hash_map{
    ll head[mod],nxt[mod],ver[mod];
    ll cnt;
    short len,L[mod];
    double val[mod];
    double &operator [] (const ll & st){
        int x=1ll*st*len%mod;
        for(ll i=head[x];i;i=nxt[i])
            if(ver[i]==st&&L[i]==len)
                return val[i];
        nxt[++cnt]=head[x],head[x]=cnt,ver[cnt]=st,L[cnt]=len,val[cnt]=-1;
        return val[cnt];
    }
}f;
ll n,k;
char c[33];
double dfs(ll l,ll st){
    if(l==n-k) return 0;
    f.len=l;
    if(f[st]>-0.5) return f[st];
    ll lst[33];
    ll rst=st;
    f[st]=0;
    for(ll i=1;i<=l;i++) lst[i]=rst&1,rst>>=1;
    for(ll i=1;i<=l/2;i++){
        ll j=l-i+1,st1=(st>>(l-i+1)<<(l-i))|(st&((1<<(l-i))-1)),st2=(st>>(l-j+1)<<(l-j))|(st&((1<<(l-j))-1));
        double ans1=dfs(l-1,st1)+lst[j],ans2=dfs(l-1,st2)+lst[i];
        f.len=l;f[st]+=2.0/((double)l)*max(ans1,ans2);
    }
    if(l&1){
        ll p=l/2+1,st3=(st>>(l-p+1)<<(l-p))|((st&((1<<(l-p))-1)));
        double ans3=dfs(l-1,st3)+lst[p];
        f.len=l;f[st]+=1.0/l*ans3;
    }
    return f[st];
}
int main(){
    scanf("%d%d",&n,&k);
    scanf("%s",c+1);
    ll st=0,cnt=0;
    for(ll i=1;i<=n;i++){
        st=((st<<1)|(c[i]==‘W‘));
        if(c[i]==‘W‘) cnt++;
    }
    if(k==n){
        printf("%d\n",cnt);
        return 0;
    }
    printf("%.8lf\n",dfs(n,st));
}

原文地址:https://www.cnblogs.com/znsbc-13/p/11616258.html

时间: 2024-11-03 16:07:38

v·y「状压dp」的相关文章

Loj 6433. 「PKUSC2018」最大前缀和 (状压dp)

题面 Loj 题解 感觉挺难的啊- 状压\(dp\) 首先,有一个性质 对于一个序列的最大前缀和\(\sum_{i=1}^{p} A[i]\) 显然对于每个\(\sum_{i=p+1}^{x}A[i](p+1 \leq x \leq n)<0\) 我们可以以\(p\)分成两个集合 \(n\leq 20\) 所以状压一下 \(sum[i]\)表示当前状态表示的和 \(f[i]\)表示用当前状态的数,组成最大前缀和为\(sum[i]\)的方案数 \(g[i]\)表示当前状态的数,组成的序列,每个前缀

状压dp,区间dp,矩阵快速幂

DP 首先先回忆一下dp,dp叫做记忆化搜索,是一种可以把暴力搜索中重复的部分重复利用,从而到达减小复杂度的目的.比如最应该熟悉的背包模型,如果你把选择的过程看成一步一步的,那么在这么多的搜索路径中一定有着很多很多的重复部分,dp就是一种把重复的部分加以利用的方法.相信大家都已经在以前的练习中已经明白了dp是什么样的思路了,接下来的两种dp会在大家已经了解经典的背包dp等模型下展开. 状态压缩dp: 首先先讲一个状压dp最最经典的模型,求哈密尔顿路径问题,也叫做旅行商问题:给你一张图,你要在每个

poj 1699 Best Sequence(AC自动机+状压DP)

题目链接:poj 1699 Best Sequence 题目大意:给定N个DNA序列,问说最少多长的字符串包含所有序列. 解题思路:AC自动机+状压DP,先对字符串构造AC自动机,然后在dp[s][i]表示匹配了s,移动到节点i时候的最短步数. #include <cstdio> #include <cstring> #include <queue> #include <vector> #include <iostream> #include &

[NOIP2016]愤怒的小鸟 D2 T3 状压DP

[NOIP2016]愤怒的小鸟 D2 T3 Description Kiana最近沉迷于一款神奇的游戏无法自拔. 简单来说,这款游戏是在一个平面上进行的. 有一架弹弓位于(0,0)处,每次Kiana可以用它向第一象限发射一只红色的小鸟,小鸟们的飞行轨迹均为形如y=ax2+bx的曲线,其中a,b是Kiana指定的参数,且必须满足a<0. 当小鸟落回地面(即x轴)时,它就会瞬间消失. 在游戏的某个关卡里,平面的第一象限中有n只绿色的小猪,其中第i只小猪所在的坐标为(xi,yi). 如果某只小鸟的飞行

BZOJ1087:[SCOI2005]互不侵犯King(状压DP)

[SCOI2005]互不侵犯King Description 在N×N的棋盘里面放K个国王,使他们互不攻击,共有多少种摆放方案.国王能攻击到它上下左右,以及左上左下右上右下八个方向上附近的各一个格子,共8个格子. Input 只有一行,包含两个数N,K ( 1 <=N <=9, 0 <= K <= N * N) Output 方案数. Sample Input 3 2 Sample Output 16 分析: 经典的状压DP题目,可我竟然调了很长时间都没对,后来发现是DP枚举范围错

HDU 1565&amp;1569 方格取数系列(状压DP或者最大流)

方格取数(2) Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submission(s): 6206    Accepted Submission(s): 1975 Problem Description 给你一个m*n的格子的棋盘,每个格子里面有一个非负数. 从中取出若干个数,使得任意的两个数所在的格子没有公共边,就是说所取数所在的2个格子不能相邻,并且取出的

POJ 2288 Islands and Bridges(状压dp)

Language: Default Islands and Bridges Time Limit: 4000MS   Memory Limit: 65536K Total Submissions: 9312   Accepted: 2424 Description Given a map of islands and bridges that connect these islands, a Hamilton path, as we all know, is a path along the b

hdu 3341 Lost&#39;s revenge(AC自动机+变进制状压DP)

题目链接:hdu 3341 Lost's revenge 题目大意:给定一些需要匹配的串,然后在给定一个目标串,现在可以通过交换目标串中任意两个位置的字符,要求最 后生成的串匹配尽量多的匹配串,可以重复匹配. 解题思路:这题很明显是AC自动机+DP,但是dp的状态需要开40?40?40?40(记录每种字符的个数),空间承受 不了,但是其实因为目标串的长度有限,为40:所以状态更本不需要那么多,最多只有10?10?10?10,但是通过 40进制的hash转换肯定是不行,可以根据目标串中4种字符的个

【BZOJ-1097】旅游景点atr SPFA + 状压DP

1097: [POI2007]旅游景点atr Time Limit: 30 Sec  Memory Limit: 357 MBSubmit: 1531  Solved: 352[Submit][Status][Discuss] Description FGD想从成都去上海旅游.在旅途中他希望经过一些城市并在那里欣赏风景,品尝风味小吃或者做其他的有趣的事情.经过这些城市的顺序不是完全随意的,比如说FGD不希望在刚吃过一顿大餐之后立刻去下一个城市登山,而是希望去另外什么地方喝下午茶.幸运的是,FGD