1. 程式人生 > >狀壓dp的另一種形式

狀壓dp的另一種形式

  做的那麼多都是一些比較則麼說呢,都是在數網格一類的題目之中,這些題目有些有點固定的套路,而一些需要狀態壓縮的題目呢,則麼是真正對狀態轉移的考驗。

這道題呢,被徹底打臉了,以後一定要任性一點一道題做不出來就要堅持啃,不管你幹什麼,先a了再說。

但這道題我是真的傷,拿頭去寫估計也想不出來最後的解法。

第一眼,這不是很簡單的dp麼?設f[i]表示第i個狀態得到的最大價值那麼這個狀態就是由i這個狀態的所有子集所構成。當然本人哪想的出來什麼子集直接暴力枚舉了。

複雜度2^n^2^n沒錯這就是複雜度。只能的30分。

//#include<bits/stdc++.h>
#include<iostream> #include<iomanip> #include<cstdio> #include<cstring> #include<string> #include<ctime> #include<cstdlib> #include<cmath> #include<queue> #include<deque> #include<vector> #include<set> #include<bitset> #include
<cctype> #include<utility> #include<map> #include<algorithm> #include<stack> using namespace std; inline int read() { int x=0,f=1;char ch=getchar(); while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();} while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
return x*f; } inline void put(int x) { x<0?putchar('-'),x=-x:0; int num=0;char ch[50]; while(x)ch[++num]=x%10+'0',x/=10; num==0?putchar('0'):0; while(num)putchar(ch[num--]); putchar('\n');return; } const int maxn=17; int n,state,t=0; int v[1<<maxn];//v[i]表示第i個狀態的價值 int f[1<<maxn];//f[i]表示到達第i個狀態的最優解 void dfs(int x,int sum,int now) { if(now==x){f[x]=max(f[x],sum);return;} for(int i=1;i<=x;i++) { if(now&i)continue; dfs(x,sum+v[i],now|i); } } int main() { //freopen("1.in","r",stdin); n=read();state=(1<<n)-1; for(int i=1;i<=state;i++)v[i]=read(); dfs(state,0,0); //for(int i=1;i<=state;i++)dfs(i,0,0); put(f[state]); return 0; }
View Code

然後也沒心情聽那所謂的數學課,覺得是在浪費時間,學長也不講什麼那還不如自己學。

所以乾脆就一直想,然後一直沒想到優化的方法,然後叫了個學長幫我看看,康神看一眼就秒a了。

真的是強,幫我找出程式碼中TLE的原因的是wydalao 他說我的dfs應該列舉子集,對哦。

這是康神打的遞推列舉子集然後成功AC的程式碼,跑的挺快的。

//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<deque>
#include<vector>
#include<set>
#include<bitset>
#include<cctype>
#include<utility>
#include<map>
#include<algorithm>
#include<stack>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(int x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int maxn=17;
int n,state,t=0;
struct node{
    int v;
    int num;
}e[1<<maxn];
//v[i]表示第i個狀態的價值
int f[1<<maxn];//f[i]表示到達第i個狀態的最優解
int count(int x){
    int res=0;
    for(;x;x>>=1){
        if(x&1) res++;
    }
    return res;
}
bool cmp(node a,node b){
    return a.num<b.num;
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();state=(1<<n)-1;
    for(int i=1;i<=state;i++)f[i]=read(),e[i].v=i;
    for(int i=1;i<=state;i++)e[i].num=count(i);
    sort(e+1,e+state+1,cmp);
    for(int i=1;i<=state;i++){
        int s=e[i].v;
        for(int s1=s;s1!=0;s1=s&(s1-1)){
            int s2=s^s1;
            f[s]=max(f[s1]+f[s2],f[s]);
        }
    }
    //for(int i=1;i<=state;i++)dfs(i,0,0);
    put(f[state]);
    return 0;
}
View Code

細節處理也很對。敬佩三尺,真強啊。然後我十分的不服。

自己學了一下下列舉當前狀態的子集,怒打了一個記搜,也算是A了這道題,自己思考的程度很深了,這道題沒白費。

//#include<bits/stdc++.h>
#include<iostream>
#include<iomanip>
#include<cstdio>
#include<cstring>
#include<string>
#include<ctime>
#include<cstdlib>
#include<cmath>
#include<queue>
#include<deque>
#include<vector>
#include<set>
#include<bitset>
#include<cctype>
#include<utility>
#include<map>
#include<algorithm>
#include<stack>
using namespace std;
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(int x)
{
    x<0?putchar('-'),x=-x:0;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    putchar('\n');return;
}
const int maxn=17;
int n,state,t=0;
int v[1<<maxn];//v[i]表示第i個狀態的價值
int f[1<<maxn];//f[i]表示到達第i個狀態的最優解
int dfs(int x)
{
    if(f[x]!=0)return f[x];
    for(int i=x;i;i=(i-1)&x)
    {
        if(i==x)continue;
        int s1=x^i;
        dfs(i);dfs(s1);
        f[x]=max(f[x],f[s1]+f[i]);
    }
    return f[x]=max(f[x],v[x]);
}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();state=(1<<n)-1;
    for(int i=1;i<=state;i++)v[i]=read();
    dfs(state);
    put(f[state]);
    return 0;
}
View Code

程式碼中記搜和一些狀態初始值剛好形成巢狀關係,我也不知道自己則麼寫的把細節處理的這麼好,自己還是可以的。

學長的列舉子集方法比較難一點這裡不再贅述。放一下列舉子集的方法。

for(int i=x;i;i=(i-1)&x)
{
   int s1^i;      
}

i是當前集合的子集,s1是當前集合的補集。這樣複雜度就大大降低了。

這道題的話也是很簡單自己想的了狀壓dp,但是狀態的設定和轉移打了幾個h都整不好,最後是qydalao教的,但是我不認同他的狀態轉移,但是a了就是事實。

//#include<bits/stdc++.h>
#include<iomanip>
#include<iostream>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<cstdlib>
#include<cstring>
#include<string>
#include<set>
#include<bitset>
#include<queue>
#include<deque>
#include<stack>
#include<cctype>
#include<utility>
#include<algorithm>
#include<map>
#include<vector>
#define INF 214748364
using namespace std;
inline long long read()
{
    long long x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(long long x)
{
    x<0?x=-x,putchar('-'):0;
    long long num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(ch[num--]);
    return;
}
const int MAXN=70000;
int n,m;
int f[1<<17];//f[i]表示第i個編碼形成所需要的最小次數。
char a[102][102],an[202];
int b[602],cnt=0,ans,maxx=10000000,sum=0,c[602],t=0;
int p[18]={0,1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768};
int contrast(int x,int y)
{
    int xx=0;
    while(1)
    {
        if(x==0&&y==0)break;
        if((x&1)!=(y&1))xx++;
        x=x>>1;y=y>>1;
    }
    return xx;
}
int main()
{
    //freopen("1.in","r",stdin);
    m=read();n=read();
    scanf("%s",an+1);
    for(int i=1;i<=n;i++)scanf("%s",a[i]+1);
    for(int i=m,j=1;i>=1;i--,j++)cnt+=an[i]=='1'?p[j]:0;
    for(int i=0;i<=(1<<m);i++)f[i]=INF;
    for(int i=1;i<=n;i++)
    {
        for(int j=m,t=1;j>=1;j--,t++)
        {
            b[i]+=a[i][j]=='1'?p[t]:0;
        }
    }
    for(int i=1;i<=n;i++)
    {    
        for(int j=1;j<=n;j++)
        {
            f[b[i]^b[j]]=min(f[b[i]^b[j]],1);
        }
        f[b[i]]=2;
    }
    for(int i=1;i<=n;i++)
        for(int j=0;j<(1<<m);j++)
        {
            if(f[j]>1000000)continue;
            f[j^b[i]]=min(f[j^b[i]],f[j]+1);
        }
    for(int i=(1<<m)-1;i>=0;i--)
    {
        if(f[i]!=INF)
        {
            int u=contrast(cnt,i);
            if(u==maxx){if(f[i]<sum)sum=f[i],ans=i;if(f[i]==sum)ans=min(ans,i);}
            if(u<maxx){maxx=u;sum=f[i];ans=i;}
        }
    }
    put(sum);puts("");
    while(ans)
    {
        if(ans&1)c[++t]=1;
        else c[++t]=0;
        ans=ans>>1;
    }
    if(t<m)t+=m-t;
    for(int i=t;i>=1;i--)put(c[i]);
    return 0;
}
View Code

關鍵是狀態轉移之處,最後的細節處理當然是簡單的了。

這道題是本人自己親自相出來的思路,那天可能太聰明瞭,導致推出了正解,看著資料範圍是狀壓。

也可以是狀壓,但是不免的是隨機化搜尋什麼的,模擬退火好像也可以A了這道題。

但是本人親自相出的思路那肯定是不一樣的。對思維的真實鍛鍊。

大體思路就是設f[i][j]表示第i個狀態到達了第j個節點所需費用的最小值。

那麼這樣的話考慮填表法得出,列舉當前狀態到達了哪個節點,由哪個節點到達這個節點的一堆狀態的轉移可以得出最優解。

自己的思路,AC了就是很爽呢。

//#include<bits/stdc++.h>
#include<iomanip>
#include<utility>
#include<cctype>
#include<vector>
#include<deque>
#include<map>
#include<stack>
#include<queue>
#include<bitset>
#include<set>
#include<cstdlib>
#include<algorithm>
#include<iostream>
#include<cstdio>
#include<ctime>
#include<cmath>
#include<cstring>
#include<string>
#define INF 214748364.5
inline int read()
{
    int x=0,f=1;char ch=getchar();
    while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
    while(ch>='0'&&ch<='9'){x=x*10+ch-'0';ch=getchar();}
    return x*f;
}
inline void put(int x)
{
    x<0?x=-x,putchar('-'):0;
    int num=0;char ch[50];
    while(x)ch[++num]=x%10+'0',x/=10;
    num==0?putchar('0'):0;
    while(num)putchar(num--);
    putchar('\n');return;
}
const int MAXN=20;
int n,s1,s2,w;
double ans=INF,a[MAXN][MAXN];
int x[MAXN],y[MAXN];
int p[18]={0,1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,65536};
double f[1<<17][17];//f[i][j]表示達到第i個狀態時所在的節點那麼答案就是MIN{f[(1<<n)-1][j]};
int b[MAXN],t=0;
double distance(int u1,int u2,int x1,int x2)
{
    return sqrt(((u1-x1)*(u1-x1)*1.0+(u2-x2)*(u2-x2)*1.0)*1.0);
}
void getstate(int x)
{
    int cnt=1;
    while(x)
    {
        if(x&1)b[++t]=cnt;
        x=x>>1;cnt++;
    }
}
double min(double x,double y){return x<y?x:y;}
int main()
{
    //freopen("1.in","r",stdin);
    n=read();
    for(int i=1;i<=n;i++){x[i]=read();y[i]=read();}
    x[0]=read();y[0]=read();
    for(int i=0;i<=(1<<n);i++)for(int j=0;j<=n;j++)f[i][j]=INF;
    f[0][0]=0;
    for(int i=0;i<=n;i++)
        for(int j=0;j<=n;j++)
            a[i][j]=distance(x[i],y[i],x[j],y[j]);
    for(int i=1;i<(1<<n);i++)
    {
        t=0;
        getstate(i);
        for(int j=0;j<=t;j++)
            for(int k=0;k<=t;k++)
            {
                if(b[j]!=b[k])f[i][b[j]]=min(f[i][b[j]],f[i-p[b[j]]][b[k]]+a[b[k]][b[j]]);
            }
    }
    for(int i=0;i<=n;i++)ans=min(ans,f[(1<<n)-1][i]);
    printf("%.2lf",ans);
    return 0;    
}
View Code

狀壓dp學的還行,自己dp的水平也在不斷上漲呢,覺得自己越來越強了。

加油!