1. 程式人生 > >狀壓dp做題記錄(總結用材料)

狀壓dp做題記錄(總結用材料)

1.鋪方格(統計方案數)

#include<vector>

#include<map>

#include<ctime>

#define ll long long

using namespace std;

long long dp[12][3000];//最後結果要用long long

bool judge1(int x,int len)//第一行是否合法

{

    for(int i=0;i<len;++i)

    {

        if((x>>i)&1)

        {

            if(i==len-1)return 0;

            if((x>>(i+1))&1)

            {

                i++;

                continue;

            }

            else return 0;

        }

        else continue;

    }

    return 1;

}

bool judge2(int x,int y,int len)//兩行狀態是否能共存

{

    for(int i=0;i<len;++i)

    {

        if((y>>i)&1)//分類討論,要理清分類依據:當前行為1或0,上一行對應為,,,

        {               //小細節,判斷某位是否為1,別忘了&1

            if(!((x>>i)&1))continue;

            else

            {

                if(i==len-1)return 0;

                if((x>>(i+1))&1)

                {

                    if((y>>(i+1))&1)

                    {

                        i++;

                        continue;

                    }

                    else return 0;

                }

                else return 0;

            }

        }

        else

        {

            if((x>>i)&1)

            {

                continue;

            }

            else

            {

                return 0;

            }

        }

    }

    return 1;

}

int main()

{

    int h,w;

    while(cin>>h>>w&&h+w)

    {

        memset(dp,0,sizeof(dp));

        if(h<w)swap(h,w);

        for(int i=0;i<(1<<w);++i)

            if(judge1(i,w))dp[1][i]=1;

        for(int i=2;i<=h;++i)

        {

            for(int j=0;j<(1<<w);++j)

            {

                for(int k=0;k<(1<<w);++k)

                {

                    if(i==2)

                    {

                        if(judge2(k,j,w)&&judge1(k,w))dp[i][j]+=dp[i-1][k];

                    }

                    else

                        if(judge2(k,j,w))dp[i][j]+=dp[i-1][k];

                }

            }

        }

        long long ans=0;

        ans=dp[h][(1<<w)-1];

        cout<<ans<<endl;

    }

    return 0;

}

3.炮兵陣地(三維狀壓+空間複雜度優化)

#include<ctime>

#define ll long long

using namespace std;//為什麼要開三維陣列,因為二維無法表示對應狀態限制下的子解

int a[600];

int num[600];

int sta[110];

long long dp[110][65][65];

int getNum(int x,int n)

{

    int ans=0;

    for(int i=0;i<n;++i)

    {

        if((x>>i)&1)ans++;

    }

    return ans;

}

int init(int n)

{

    memset(a,0,sizeof(a));

    memset(num,0,sizeof(num));

    int tot=0;

    for(int i=0;i<(1<<n);++i)

    {

        if((i&(i>>1))||(i&(i>>2)))continue;

        else

        {

            a[++tot]=i;

            num[i]=getNum(i,n);

        }

    }

    return tot;

}

int main()

{

    int n,m;

    while(cin>>n>>m)

    {

        memset(sta,0,sizeof(sta));

        memset(dp,0,sizeof(dp));

        getchar();

        for(int i=1;i<=n;++i)

        {

            for(int j=1;j<=m;++j)

            {

                char ch;

                ch=getchar();

                if(ch=='P')sta[i]+=(1<<(m-j));

            }

            if(i!=n)getchar();

            sta[i]=~sta[i];

        }

        int tot=init(m);

        long long maxx=0;

        for(int i=1;i<=tot;++i)//第一行

        {

            if(n==1)//只有一行的情況要特判

            {

                if(!(sta[1]&a[i]))maxx=max(maxx,(long long)getNum(a[i],m));

                continue;

            }

            for(int j=1;j<=tot;++j)//第二行

            {

                if( !(sta[1]&a[i]) && !(sta[2]&a[j]) && !(a[i]&a[j]) )

                {

                    dp[2][j][i]=num[a[j]]+num[a[i]];

                    maxx=max(maxx,dp[2][j][i]);

                }

            }

        }

        for(int i=3;i<=n;++i)

        {

            for(int j=1;j<=tot;++j)//當前

            {

                for(int k=1;k<=tot;++k)//上一

                {

                    for(int l=1;l<=tot;++l)//上二

                    {

                        if( !(a[j]&sta[i])&& !(a[k]&sta[i-1])&&!(a[l]&sta[i-2]) &&!(a[j]&a[k]) &&!(a[j]&a[l]) &&!(a[k]&a[l]) )

                        {

                            dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][l]+num[a[j]]);

                            maxx=max(maxx,dp[i][j][k]);

                        }

                    }

                }

            }

        }

        cout<<maxx<<endl;

    }

    return 0;

}

4.(TSP弗洛伊德+狀壓)

using namespace std;

const ll INF=999999999;

int f[12][12];

void floyed(int n)

{

    for(int k=0;k<=n;++k)//弗洛伊德從0/1開始的差別:有無0點

    {

        for(int i=0;i<=n;++i)

        {

            for(int j=0;j<=n;++j)

            {

                f[i][j]=min(f[i][j],f[i][k]+f[k][j]);//直接在構建的圖上更新

            }

        }

    }

}

int dp[1500][12];

int main()

{

    int n;

    while(cin>>n&&n)

    {

        memset(f,0,sizeof(f));

        for(int i=0;i<=n;++i)

            for(int j=0;j<=n;++j)

                cin>>f[i][j];

        floyed(n);

        for(int i=0;i<(1<<n);++i)

            for(int j=0;j<=n;++j)dp[i][j]=INF;

        for(int i=0;i<n;++i)dp[1<<i][i+1]=f[0][i+1];//處理邊界

        for(int i=1;i<(1<<n);++i)//error:狀態描述中此狀態不包含j

        {

            for(int j=1;j<=n;++j)

            {

                if(i&(1<<(j-1)))

                {

                    for(int k=1;k<=n;++k)

                    {

                        if((i-(1<<(j-1)))&(1<<(k-1)))

                            dp[i][j]=min(dp[i][j],dp[i-(1<<(j-1))][k]+f[k][j]);

                    }

                }

            }

        }

        int ans=INF;

        for(int i=1;i<=n;++i)

        {

            ans=min(ans,dp[(1<<n)-1][i]+f[i][0]);

        }

        cout<<ans<<endl;

    }

    return 0;

}

錯題思路(或者未實現的思路):

#define ll long long

using namespace std;

const ll INF=999999999;

int f[12][12];

void floyed(int n)

{

    for(int k=1;k<=n;++k)

    {

        for(int i=1;i<=n;++i)

        {

            for(int j=1;j<=n;++j)

            {

                f[i][j]=min(f[i][j],f[i][k]+f[k][j]);

            }

        }

    }

}

int dp[1500][12];

int main()

{

    int n;

    while(cin>>n)

    {

        memset(f,0,sizeof(f));

        for(int i=0;i<=n;++i)

            for(int j=0;j<=n;++j)

                cin>>f[i][j];

        floyed(n);

        for(int i=0;i<(1<<n);++i)

            for(int j=0;j<=n;++j)dp[i][j]=INF;

        for(int i=0;i<=n;++i)dp[0][i]=f[0][i];

        for(int i=0;i<(1<<n);++i)

        {

            for(int j=1;j<=n;++j)

            {

                if(dp[i][j])

                {

                    for(int k=1;k<=n;++k)

                    {

                        if((i+(1<<(j-1)))&&(1<<(k-1)))continue;

                        else

                            dp[i+(1<<(j-1))][k]=min(dp[i+(1<<(j-1))][k],dp[i][j]+f[j][k])

                    }

                }

            }

        }

        int ans=INF;

        for(int i=1;i<=n;++i)

        {

            ans=min(ans,dp[((1<<n)-1)-(1<<(i-1))][i]+f[i][0]);

        }

        cout<<ans<<endl;

    }

    return 0;

}

修改正確後:

#include <iostream>

#include<cstdio>

#include<algorithm>

#define ll long long

using namespace std;

const ll INF=999999999;

int f[12][12];

void floyed(int n)

{

    for(int k=0;k<=n;++k)

    {

        for(int i=0;i<=n;++i)

        {

            for(int j=0;j<=n;++j)

            {

                f[i][j]=min(f[i][j],f[i][k]+f[k][j]);

            }

        }

    }

}

int dp[1500][12];

int main()

{

    int n;

    while(cin>>n&&n)

    {

        memset(f,0,sizeof(f));

        for(int i=0;i<=n;++i)

            for(int j=0;j<=n;++j)

                cin>>f[i][j];

        floyed(n);

        for(int i=0;i<(1<<n);++i)

            for(int j=0;j<=n;++j)dp[i][j]=INF;

        for(int i=0;i<n;++i)dp[1<<i][i+1]=f[0][i+1];

        for(int i=0;i<(1<<n);++i)

        {

            for(int j=1;j<=n;++j)

            {

                if(dp[i][j]&&(i&(1<<(j-1))))

                {

                    for(int k=1;k<=n;++k)

                    {

                        if(i&(1<<(k-1)))continue;

                        else

                            dp[i+(1<<(k-1))][k]=min(dp[i+(1<<(k-1))][k],dp[i][j]+f[j][k]);

                    }

                }

            }

        }

        int ans=INF;

        for(int i=1;i<=n;++i)

        {

            ans=min(ans,dp[((1<<n)-1)][i]+f[i][0]);

        }

        cout<<ans<<endl;

    }

    return 0;

}

5.順路A,概率類題目(注意精度)

題解地址:https://blog.csdn.net/zhenlingcn/article/details/76021104?locationNum=5&fps=1

#include<ctime>

#define ll long long

using namespace std;

double a[50];

int main()

{

    int n;

    int cas=0;

    while(cin>>n&&n)

    {

        char ch[110];

        for(int i=1;i<=n;++i)

        {

            scanf("%s",&ch);

            a[i]=atof(ch);

        }

        int num=0;

        for(int i=0;i<strlen(ch);++i)

        {

            if(ch[i]=='.')

            {

                num=strlen(ch)-1-i;

                break;

            }

        }

        int ans=0;

        for(int i=1;i<=10000;++i)

        {

            int minn=0,maxx=0;

            for(int j=1;j<=n;++j)

            {

                 minn+=ceil( 1.0*i*(a[j]*pow(10,num)-0.5000)/100/pow(10,num) );//精度

                 maxx+=floor( 1.0*i*(a[j]*pow(10,num)+0.4999)/100/pow(10,num) );

            }

            if(i>=minn&&i<=maxx)

            {

                ans=i;

                break;

            }

        }

        if(ans)cout<<"Case "<<++cas<<": "<<ans<<endl;

        else cout<<"Case "<<++cas<<": "<<"error"<<endl;

    }

    return 0;

}

6.哈密爾頓路徑求最大權值:

/*

Wa:n==1沒特判、陣列開的太大超記憶體限制(1<<14,20,20)、減法狀態轉移陣列下溢;

PS:這題的結果處理上也是個亮點;

*/

#include<bits/stdc++.h>

#define ll long long

using namespace std;

int v[20];

int g[20][20];

int dp[9000][15][15];

ll num[9000][15][15];

bool judge(int i,int j,int k,int l)

{

    if(l==j||j==k||k==l)return 0;

    if(!g[j][k]||!g[k][l])return 0;

    if(  !((i&(1<<(j-1)))&&(i&(1<<(k-1)))&&(i&(1<<(l-1)))) )return 0;

    if(dp[i-(1<<(l-1))][j][k]==-1)return 0;//減法運算注意陣列下溢;

    return 1;

}

int main()

{

    /*區別上面弗洛依德演算法+狀壓dp解決旅行商問題:

        分析:

            *狀壓dp與圖聯絡,一般情況下用二維【sta】【停駐點】陣列,但因為 所求的路徑權值有三個來源:

              點的權值相加、邊的權值相加(端點的乘積)、額外三角形權值,為求得第三個權值來源,

              需要記錄下2個點的狀態,因此,開三維陣列,【sta】【停駐點上一點】【停駐點】。

              時間複雜度:

                    o(1024*8* 13*13*13==2e7);

              空間複雜度:

                    16384*20*20==6e6,完全ok。

        處理步驟:

            *n點數,m邊數,v[]存點權值, g[][]構圖(無向圖),dp存最大權值,num[]存路徑數;

            *變數初始化:因為求最大權值,陣列均初始化為0即可;

            *邊界處理:三維一般用兩層迴圈初始化邊界,本題中即含兩點的狀態:權值即點權值和+邊權值和,

              路徑數目即初始化為1。

            *狀態轉移:dp【sta+k點】【j】【k】=max dp【sta】【i】【j】+v【k】+v【k】*v【j】+(三角形判斷)

              上式執行的條件:sta中有i點、j點,無k點;i與j相連,j與k相連,dp【】【i】【j】

              已經賦值(沒賦值的即該狀態不滿足所有為1的點全被訪問過),(保證由已知推算未知);

                **另外附加判斷三角形,,,(略)

                **若新值>原來值,更新dp,同時num更新為新的哈密爾頓路徑數

                **若新值==原來值,原來的哈密路徑數+新的哈密路徑數

            *答案選擇,dp【二的n次方-1】【i隨機】【j隨機】中取max作為 哈密路徑最大權值,同時對應

            num【】【】【】為路徑數,又因為題目要求 一條路徑的反向與正向味同一條路,所以所取路徑數/2作為

            最終答案。

            *輸出哈密爾頓路徑最大權值,哈密爾頓路徑數;

    */

    int t;

    cin>>t;

    while(t--)

    {

        memset(g,0,sizeof(g));

        memset(v,0,sizeof(v));

        memset(dp,-1,sizeof(dp));

        memset(num,0,sizeof(num));

        int n,m;

        cin>>n>>m;

        for(int i=1;i<=n;++i)cin>>v[i];

        if(n==1)//對於1的情況要特判

        {

            cout<<v[1]<<" 1"<<endl;

            continue;

        }

        for(int i=1;i<=m;++i)

        {

            int x,y;

            cin>>x>>y;

            g[x][y]=g[y][x]=1;

        }

        for(int j=1;j<=n;++j)

            for(int k=1;k<=n;++k)

            {

                if(g[j][k])

                {

                    int sta=(1<<(j-1))+(1<<(k-1));

                    dp[sta][j][k]=v[j]+v[k]+v[j]*v[k];

                    num[sta][j][k]=1;

                }

            }

        for(int i=0;i<(1<<n);++i)

        {

            for(int j=1;j<=n;++j)

            {

                for(int k=1;k<=n;++k)

                {

                    for(int l=1;l<=n;++l)

                    {

                        if(judge(i,j,k,l))

                        {

                            int temp=dp[i-(1<<(l-1))][j][k]+v[l]+v[l]*v[k];

                            if(g[j][l])temp+=v[j]*v[k]*v[l];

                            if(temp>dp[i][k][l])

                            {

                                dp[i][k][l]=temp;

                                num[i][k][l]=num[i-(1<<(l-1))][j][k];

                            }

                            else if(temp==dp[i][k][l])

                            {

                                num[i][k][l]+=num[i-(1<<(l-1))][j][k];

                            }

                        }

                    }

                }

            }

        }

        ll ans=0,res=0;

        for(int i=1;i<=n;++i)

        {

            for(int j=1;j<=n;++j)

            {

                if(dp[(1<<n)-1][i][j]>ans)

                {

                    ans=dp[(1<<n)-1][i][j];

                    res=num[(1<<n)-1][i][j];

                }

                else if(ans==dp[(1<<n)-1][i][j])

                {

                    res+=num[(1<<n)-1][i][j];

                }

            }

        }

        cout<<ans<<" "<<res/2<<endl;

    }

    return 0;

}

7.旅行團參觀景點,難點權值的預處理(暴力列舉法)

#include <cstdio>

#include <cmath>

#include <cstring>

#define ll long long

using namespace std;

const ll INF=999999999;

int like[15][15];

int cost[15];

int cp[15][15];

int dp[1500][15];

int v[1500][15];

void init(int n,int m)

{

    memset(v,0,sizeof(v));

    for(int i=1;i<=m;++i)

    {

        for(int j=0;j<(1<<n);++j)

        {

            for(int k=1;k<=n;++k)

            {

                if(j&(1<<(k-1)))//滿足這個if才能繼續下面的語句,WA:下面那個for放在了if外面;

                {

                    v[j][i]+=like[k][i]-cost[i];

                    for(int l=k+1;l<=n;++l)

                    {

                        if(j&(1<<(l-1)))v[j][i]+=cp[k][l];

                    }

                }

            }

        }

    }

}

void DP(int n,int m)

{

    for(int i=0;i<(1<<n);++i)

        for(int j=1;j<=m;++j)dp[i][j]=-INF;//有負解,所以全部初始化為極小值,不能是0;

    for(int i=0;i<(1<<n);++i)dp[i][1]=v[i][1];

    for(int i=2;i<=m;++i)

    {

        for(int j=0;j<(1<<n);++j)

        {

            for(int k=j;k<(1<<n);++k)

            {

                if((j&k)==j)

                    dp[j][i]=max(dp[j][i],dp[k][i-1]+v[j][i]);

            }

        }

    }

}

int main()

{

    /*

        問題分析:

                n個遊客,m個景點,每個遊客對不同景點有不同的喜愛度like[1~n][1~m],不同景點不通花費cost[1~m],n個人中某兩個人在一起時會產生附加權值cp【1~n】【1~n】(正的)。遊客們按順序依次參觀景點,參觀過程可刪掉某一遊客且不能再次加入,問怎樣安排 能使最後的權值最大,最大為多少。

                狀態dp【sta】【當前景點】;

                轉移方程:dp【sta1】【當前景點】=max{dp【sta包含於,,,,,,sta2】【上一景點】+v【sta】【當期景點】}

        步驟描述:

            *陣列初始化 :求最大值,初始化為0即可,dp也可初始化為-1,具體看情況。

            *輸入:n,m,like(n,m),cost(m),cp,

            *邊界處理:dp【all sta】【1】=v【sta】【1】;

                **v【】【】的預處理:

                    迴圈:景點、狀態、數位/人員1、數位2;  o(10*1024*10*10)

            *狀態轉移:迴圈:景點、狀態1、狀態2,轉移條件:狀態1含於狀態二;

                                                                    o(10*1024*1024);

            *答案處理,dp【各種sta】【m】中選最大值,據題意輸出答案。

    */

    int n,m;

    while(cin>>n>>m&&n+m)

    {

        memset(like,0,sizeof(like));

        memset(cost,0,sizeof(cost));

        memset(cp,0,sizeof(cp));

     for(int i=1;i<=m;++i)cin>>cost[i];

        for(int i=1;i<=n;++i)

            for(int j=1;j<=m;++j)cin>>like[i][j];

        for(int i=1;i<=n;++i)

            for(int j=1;j<=n;++j)cin>>cp[i][j];

        init(n,m);

        DP(n,m);

        int ans=-1;

        for(int i=0;i<(1<<n);++i)ans=max(ans,dp[i][m]);

        if(ans>0)cout<<ans<<endl;

        else cout<<"STAY HOME"<<endl;

    }

    return 0;

}

8.二傻補作業,一定補不完(亮點:輸出方案路徑),,,

#include <cstdio>

#include <cmath>

#include <cstring>

#define ll long long

using namespace std;

const int INF=999999999;

string name[20];

int limit[20],cost[20];

int day[40000];

int dp[40000];

int memor[40000];

void init(int n)

{

    memset(day,0,sizeof(day));

    for(int i=0;i<(1<<n);++i)

    {

        int sum=0;

        for(int j=0;j<n;++j)

        {

            if(i&(1<<j))

            {

                sum+=cost[j+1];

            }

        }

        day[i]=sum;

    }

}

void print(int sta)

{

    if(sta==0)return ;

    print(sta-(1<<(memor[sta]-1)));

    cout<<name[memor[sta]]<<endl;

}

int main()

{

    /*

    分析:

        dp【sta】表示在sta狀態(已做完的科目用1表示)下扣除的分數;

        狀態轉移方程:dp【sta】=min{dp【sta-k】+(sta-k)代表的天數+k所需要的天數-k的限制時間};

        其中 【狀態】對應的天數可以暴力列舉預處理 day【sta】,迴圈:狀態、數位,o(32768*15)複雜度不超時限。

    處理步驟:

    *資料輸入:n科目數、string【1~n】科目名、limit【1~n】科目時限、cost【1~n】科目花費時間、time【sta】預處理不同狀態所對應的天數;

    *狀態轉移:

        迴圈:狀態、科目;

        轉移條件:狀態中含有該科目;轉移過程同時記錄 狀態最優值對應的最後一門科目meor【sta】=k;

        注意:減法注意陣列下溢;

    *輸出最優解:即目標狀態dp【sta】,方案遞迴輸出;

    */

    int t;

    cin>>t;

    while(t--)

    {

        memset(limit,0,sizeof(limit));

        memset(cost,0,sizeof(cost));

        int n;

        cin>>n;

        for(int i=1;i<=n;++i)cin>>name[i]>>limit[i]>>cost[i];

        init(n);

        memset(memor,0,sizeof(memor));

        for(int i=1;i<(1<<n);++i)

        {

            dp[i]=INF;

            for(int j=n;j>=1;--j)//正向WA,這裡逆向考慮,因為小狀態到大狀態轉移時不是每次從最小的選起,例如選最後一門科目時,應該最大序的科目,而按正向考慮,選擇的是最小序的科目。

            {

                if(i&(1<<(j-1)))

                {

                    int temp=day[i-(1<<(j-1))]+cost[j]-limit[j];

                    if(temp<0)temp=0;

                    if(dp[i-(1<<(j-1))]+temp<dp[i])

                    {

                        dp[i]=dp[i-(1<<(j-1))]+temp;

                        memor[i]=j;

                    }

                }

            }

        }

        cout<<dp[(1<<n)-1]<<endl;

        print((1<<n)-1);

    }

    return 0;

}

9.方格取數(這麼高的複雜度能水過?)

沒啥說的就這麼幾步:

/*

         *輸入資料

         *縮減可行狀態數,(不然陣列會爆,時間也會爆)

*打表預處理權值

*狀態轉移

*選擇最優解輸出資料

*/

#include <cstdio>

#include <cmath>

#define ll long long

using namespace std;

const int INF=999999999;

int g[25][25];

ll dp[20000][25];

ll sta[20000];

ll v[20000][25];

int  pre(int n)//縮減可行解&&預處理權值打表

{

    memset(sta,0,sizeof(sta));

    memset(v,0,sizeof(v));

    int tot=0;

    for(int i=0;i<(1<<n);++i)

    {

        if(i&(i>>1)){}

        else sta[++tot]=i;

    }

    for(int k=1;k<=n;++k)//權值打表;

        for(int i=1;i<=tot;++i)

        {

            int sum=0;

            for(int j=0;j<n;++j)

            {

                if(sta[i]&(1<<j))sum+=g[k][j+1];

            }

            v[i][k]=sum;

        }

    return tot;//max<20000

}

void init(int n)

{

    memset(g,0,sizeof(g));

    for(int i=1;i<=n;++i)

        for(int j=1;j<=n;++j)cin>>g[i][j];

}

void solve(int n,int tot)

{

    memset(dp,0,sizeof(dp));

    for(int i=1;i<=tot;++i)dp[i][1]=v[i][1];

    for(int i=2;i<=n;++i)

    {

        for(int j=1;j<=tot;++j)

        {

            for(int k=1;k<=tot;++k)

            {

                if(!(sta[j]&sta[k]))

                {

                    dp[j][i]=max(dp[j][i],dp[k][i-1]+v[j][i]);

                }

            }

        }

    }

}

int main()

{

    int n;

    while(cin>>n)

    {

        init(n);

        int tot=pre(n);

        solve(n,tot);

        ll ans=0;

        for(int i=1;i<=tot;++i)ans=max(ans,dp[i][n]);

        cout<<ans<<endl;

    }

}

10.刪除迴文串最少次數(玄學,dp過程子集合正向列舉不行,for不做小技巧不行)

#include <cstdio>

#include <cmath>

#include <cstring>

#define ll long long

using namespace std;

const int INF=999999999;

bool mark[(1<<16)+100];

int dp[70000];

char ch[20];

void pre(string str)//迴文串判斷預處理;

{

    memset(mark,0,sizeof(mark));

    int n=str.length();

    for(int i=0;i<(1<<n);++i)

    {

        int tot=0;

        for(int j=0;j<n;++j)

        {

            if(i&(1<<j))ch[++tot]=str[j];

        }

        bool flag=1;

        for(int j=1;j<=tot/2;++j)

            if(ch[j]!=ch[tot-j+1])flag=0;

        mark[i]=flag?1:0;

    }

}

void DP(string str)//DP過程不是很好處理,也可能是感冒,,,

{

    int n=str.length();

    for(int i=0;i<(1<<n);++i)dp[i]=INF;

    dp[0]=0;

    /*for(int i=1;i<(1<<n);++i)//母狀態

    {

        for(int j=1;j<=i;(++j)&=i)//刪去的子狀態

        {

            if(mark[j])

                dp[i]=min(dp[i],dp[i-j]+1);

        }

    }*/

    for(int i=1;i<(1<<n);i++)

    {

        for(int j=i;j>0;(--j)&=i)//列舉j狀態下的子集

        {

            if(mark[j])//如果該子集合法則列入方程計算

            {

                dp[i]=min(dp[i],dp[i-j]+1);

            }

        }

    }

}

int main()

{

    /*

        分析:容易想到方程dp【sta-子sta】=min{ dp【sta-子狀態】+1 };

        條件:被減的子狀態應當是迴文串;

    */

    int t;

    cin>>t;

    while(t--)

    {

        string str;

        cin>>str;

        pre(str);

        DP(str);

        cout<<dp[(1<<str.length())-1]<<endl;

    }

    return 0;

}

11.哈密爾頓迴路(選定首尾點、計數問題一般用long long);

題意:給n個珠子,以及構圖規則(無向圖),求其中哈密爾頓迴路的數目,正向與反向不一樣。

#define ll long long

using namespace std;

ll dp[270000][20];

int g[20][20];

int main()

{

    int n,m;

    while(cin>>n>>m)

    {

        memset(dp,0,sizeof(dp));

        memset(g,0,sizeof(g));

        for(int i=1;i<=m;++i)

        {

            int x,y;

            cin>>x>>y;

            g[x][y]=g[y][x]=1;

        }

        //for(int i=1;i<=n;++i)dp[1<<(i-1)][i]=1;//WA

        dp[1][1]=1;//設定環的規則,從一開始到一結束,因此其他單個珠子2,3...在起點時無法形成環項鍊,因此初值為0

        for(int i=2;i<(1<<n);++i)

        {

            for(int j=1;j<=n;++j)//當前

            {

                for(int k=1;k<=n;++k)//上一個

                {

                    if((i&(1<<(j-1)))&&(i&(1<<(k-1)))&&g[j][k])//沒加dp!=-1的原因,無其他附加權值,+0對結果無影響,(區別哈密爾頓路徑那個地方);

                    {

                        dp[i][j]+=dp[i-(1<<(j-1))][k];

                    }

                }

            }

        }

        ll ans=0;

        for(int i=1;i<=n;++i)//上文邊界處理呼應

        {

            if(g[1][i])ans+=dp[(1<<n)-1][i];

        }

        cout<<ans<<endl;

    }

    return 0;

}

13.(連連看,難想,狀態上限和n混淆,思路較新穎)

#include <cstdio>

#include <cmath>

#include <cstring>

#define ll long long

using namespace std;

int dp[2050][1500];

int a[1500];

int main()

{

   /*

    思路:dp不太好想,這裡考慮每個點可操作的其他點為該點往下的9個點,為什麼不是5個點呢,因為該點上面的點至多可操作該點下的第1、2、3、4個點;

所以,狀態可以用10位二進位制表示,因此dp【sta】【i】表示當前判斷以i為首狀態為sta時第i個數是否可消除,狀態轉移關係dp【sta】【i】=(i+1~i+9逐個判斷權值是否相同)

並且距離小於等於“邏輯6”,若可消除,生成新的狀態dp【new sta】【i+1】。

    處理步驟:

    *輸入:n存資料個數,a【1~n】存元素,dp【1<<10】【1000】,初始化0,表示狀態不存在;

    *邊界處理:i=1時,只有“實際距離”6之內的可能被消除,按條件處理狀態。

    *狀態轉移:從第二層開始轉移,轉移條件:這一層有這一狀態.

    *目標狀態:dp【0】【n+1】是否存在,原因:若序列可以完全消除,到最後一個數據時,其位元位必定為1,否則不可消除。進而轉移

到dp【0】【n+1】=1.

    *輸出答案

    */

   int n;

   while(cin>>n)

   {

       memset(a,0,sizeof(a));

       memset(dp,0,sizeof(dp));

       for(int i=n;i>=1;--i)cin>>a[i];

       for(int i=1