状压dp做题记录(总结用材料)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_41661919/article/details/82955860

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;i<=5;++i)

       {

           if(a[1]==a[i+1])

           {

               dp[1<<(i-1)][2]=1;

           }

       }

 

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

       {

           for(int j=0;j<(1<<10);++j)//WA cao,这里是10,不是n,,,,,,

           {

               if(dp[j][i])

               {

                   if(j&1)

                   {

                       dp[j>>1][i+1]=1;

                       continue;

                   }

                   else

                   {

                       int tt=0;

                       for(int k=1;k<=9&&i+k<=n;++k)//

                       {

                           if(!(j&(1<<k))&&(a[i]==a[i+k])&&(k-tt<=5))

                           {

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

                           }

                           else if(j&(1<<k))tt++;

                       }

                   }

               }

           }

       }

       if(dp[0][n+1]==1)cout<<"1"<<endl;

       else cout<<"0"<<endl;

 

   }

   return 0;

 

}

 

14.射击外星人(难度较大思路很好想:难点在于优化,核心运用贪心的思想,状态转移过程也需要细节优化,删减无用状态在这里不好用,另外数组开小TLE了好久)

#include <cstdio>

#include <cmath>

#define ll long long

using namespace std;

const double INF=999999999;

struct point

{

    double x,y;

}a[50];

double v[50][50];

double dp[1100000];

int sta[600000];

double getDis(point a,point b)

{

    double ans=sqrt((a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y));

    return ans;

}

bool cmp(point a,point b)

{

    point p;

    p.x=0,p.y=0;

    return getDis(a,p)<getDis(b,p);

}

void init(int n,point s)

{

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

    {

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

        {

            v[i][j]=getDis(s,a[i])+getDis(a[i],a[j]);

        }

    }

 

 

}

int main()

{

 

    /*

    思路:

        状态:dp【sta】

        转移方程:dp【sta+j+k】=min{dp【sta】+v【j】【k】};

        目标状态:dp【满状态】

 

    步骤:

        *输入:n次射击、2*n组坐标point a[2n].x/y、dp【sta】初始化极大值;

        *边界处理:含有2个1的sta;

        *权值v【】【】预处理:

            调用计算两点间距离double getDis(point,point)

        *状态转移:

            转移条件:sta中不含j点、k点

 

        *输出结果

 

    */

    int t;

    cin>>t;

    int cas=0;

    while(t--)

    {

        int sx,sy;

        cin>>sx>>sy;

        point start;

        start.x=sx;

        start.y=sy;

        int n;

        cin>>n;

        for(int i=1;i<=2*n;++i)cin>>a[i].x>>a[i].y;

        sort(a+1,a+1+2*n,cmp);//这里最关键,贪心优化

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

        init(n,start);

        dp[0]=0;

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

        {

            if(dp[i]==INF)continue;//这里很关键

            int j=0;

            while(((i>>j)&1) &&(j<2*n-1))j++;//贪心优化

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

            {

                if(!(i&(1<<j))&&!(i&(1<<k))&&j!=k)

                {

                    double temp=min(dp[i]+v[j+1][k+1],dp[i]+v[k+1][j+1]);

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

                }

 

            }

        }

        printf("Case #%d: %.2lf\n",++cas,dp[(1<<(2*n))-1]);

    }

    return 0;

}

17.郑厂长排兵布阵

(直接套炮兵阵地的模板,主要改动的地方:状态之间的关系、筛选状态的条件;WA原因:筛选状态的数组开小、统计可行状态中1数量的数组开小、行之间状态的关系判断少步骤(区分自身有误相邻的判断))。

#include <cstdio>

#include <cmath>

#include <cstring>

#define ll long long

using namespace std;//为什么要开三维数组,因为二维无法表示对应状态限制下的子解

int a[600];

int num[1000];

int sta[200];

int dp[110][200][200];

int getNum(int x,int n)//状态中1的个数

{

    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>>2)))continue;

        else

        {

            a[++tot]=i;

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

        }

 

    }

    return tot;

}

 

int main()

{

    int n,m;

    while(cin>>n>>m)//n行,m列

    {

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

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

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

        {

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

            {

                int x;

                cin>>x;

                if(x==1)x=0;

                else x=1;

                sta[i]+=x*(1<<(m-j));

            }

            //sta[i]=~sta[i];

        }

        int tot=init(m);  //cout<<tot<<endl;

        int maxx=0;

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

        {

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

            {

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

                continue;

            }

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

            {

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

                {

                    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)//当前

            {

                if(a[j]&sta[i])continue;

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

                {

                    if(a[k]&sta[i-1])continue;

                    if((a[j]&(a[k]>>1))||((a[j]>>1)&a[k]))continue;

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

                    {

                        //if(a[l]&sta[i-2])continue;

                        if(a[l]&a[j])continue;

                        if(dp[i-1][k][l]!=-1)

                        {

                            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;

                        }

                    }

                }

            }

        }

        cout<<maxx<<endl;

 

    }

    return 0;

}

 

猜你喜欢

转载自blog.csdn.net/qq_41661919/article/details/82955860