初学线性规划

来源:互联网 发布:湖南银楼软件下载 编辑:程序博客网 时间:2024/05/18 01:39

01背包:

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int MAXN = 1010;int w[MAXN];int v[MAXN];int dp[MAXN][MAXN];int d[MAXN][MAXN];int main(){    int t;    cin>>t;    while(t--)    {        int n,c;        scanf("%d %d",&n,&c);        int i,j;        for(i = 1;i <= n;++i)        {            scanf("%d",&w[i]);        }        for(i = 1;i <= n;++i)        {            scanf("%d",&v[i]);        }        memset(dp,0,sizeof(dp));        for(i = 0;i <= c;++i)            dp[0][i] = 0;        for(i = 1;i <= n;++i)        {            dp[i][0] = 0;            for(j = 0;j <= c;++j)            {                if(j >= v[i]){                        //在放入与不放入之间选择,放入之后看i-1个物品容量为j-v[i]的最大重量                    dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - v[i]] + w[i]);                }                else                    dp[i][j] = dp[i - 1][j];//当前物品不能放入容量为j的背包,直接不放,附上上一个的值            }        }        printf("%d\n",dp[n][c]);        //下面是用滚动数组来进行空间优化的,i总是用到i-1层的值        //在递推法中,如果计算顺序很特殊,而且计算新状态,利用的原状态不多,(从底层状态推出最终状态),可以尝试用滚动数组减少内存输出        memset(d,0,sizeof(d));        for(i = 1;i <= n;++i)        {            for(j = c;j >= v[i];--j)            {                //d[j]代表着上一层的d[i - 1][j],d[j - v[i]]代表上一层的d[i - 1][j - v[i]],j必须是逆序,不然在算到d[j - v[i]]已经是d[i][j - v[i]];                d[j] = max(d[j],d[j - v[i]] + w[i]);            }        }        printf("%d\n",d[c]);    }    return 0;}

DAG上的动态规划:

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;//在这里要注意特殊值,-1代表没计算过,inf代表当前的s状态到不了0,其他值则代表状态s到状态0的最长路径长度//将大问题一步一步转移变成小问题const int MAXN = 10010;const int inf = 0x3f3f3f3f;int v[MAXN];int d[MAXN];int vis[MAXN];int w[MAXN];int n;int dp(int s){    int &ans = d[s];    if(vis[s]) return ans;    vis[s] = 1;    ans = inf;//代表的d[s]到不了0    for(int i = 1;i <= n;++i)    {        if(s >= w[i])        {            ans = min(ans,dp(s - w[i]) + v[i]);            //cout<<i<<" "<<w[i]<<" "<<ans<<endl;        }    }    //cout<<ans<<endl;    return ans;}void print_ans(int s){    printf("%d ",s);    for(int i = 1;i <= n;++i)    {        if(s >= w[i] && d[s] == d[s - w[i]] + v[i])        {            print_ans(s - w[i]);            break;        }    }}int main(){    int t;    cin>>t;    while(t--){   int E,F;   scanf("%d %d",&E,&F);    int i,j;    scanf("%d",&n);    for(i = 1;i <= n;++i)    {        scanf("%d %d",&v[i],&w[i]);    }    int s;    s = F - E;    memset(d,-1,sizeof(d));    memset(vis,0,sizeof(vis));    d[0] = 0;    vis[0] = 1;    int k = dp(s);    if(k == inf)        printf("This is impossible.\n");    else{        printf("The minimum amount of money in the piggy-bank is %d.\n",d[s]);      }    }    /*for(i = 0;i <= s;++i)    {        printf("%d ",d[i]);    }    printf("\n");*/    return 0;}

多段图的最短路:

#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int MAXN = 1001;const int inf = 0x3f3f3f3f;int d[MAXN][MAXN];int Next[MAXN][MAXN];int a[MAXN][MAXN];int main(){    int m,n;    scanf("%d %d",&m,&n);    int i,j;    for(i = 0;i < m;++i)    {        for(j = 0;j < n;++j)        {            scanf("%d",&a[i][j]);        }    }    int ans = inf,first;    //好多动态规划问题都是逆推,因为边界在不在刚开始,总是从边界推出正确的值    for(i = n - 1;i >= 0;--i)//逆推    {        for(j = 0;j < m;++j)        {            if(i == n - 1)                d[j][i] = a[j][i];            else{                int row[3] = {j , j - 1 , j + 1};                if(j == 0) row[1] = m - 1;                if(j == m - 1) row[2] = 0;                sort(row,row + 3);//保证按照字典序,尽量选上面的                d[j][i] = inf;                for(int k = 0;k < 3;++k)                {                    int v = d[row[k]][i + 1] + a[j][i];                    if(d[j][i] > v){                        d[j][i] = v;                        Next[j][i] = row[k];                    }                }            }            //选择最小的出发点            if(i == 0 && d[j][i] < ans){                ans = d[j][i];                first = j;            }        }    }    printf("%d",first + 1);    for(int i = Next[first][0],j = 1;j < n;i = Next[i][j],++j)        printf(" %d",i + 1);    printf("\n%d\n",ans);    return 0;}

树的最大独立集:

/*一.先简单讲下什么是填表法,什么是刷表法。填表法 :就是一般的动态规划,当前点的状态,可以直接用状态方程,根据之前点的状态推导出来。(如果当前状态既与之前的状态有关,又与之后的状态有关,一般填表法适用于只与之前的状态有关,dp[i]与dp[i - a],dp[i - b],dp[i * 2]有关)刷表法:由当前点的状态,更新其他点的状态。需要注意:只用当每个状态所依赖的状态对它的影响相互独立。*****我认为填表法是当前状态根据状态方程由上一个状态递推出来,不会牵连前几个多个状态。*****刷表法就是当前状态既与之前的状态有关,又与之后的状态有关,从i找i的孩子和孙子,太不方便了,换个角度,用i更新i的父亲和祖父。*/#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>#include<vector>using namespace std;const int MAXN = 1005;//用可变化二维数组vector来储存vector<int>G[MAXN];//存储边的信息int d[MAXN];//代表节点i的最大独立集大小int f[MAXN];//存储i节点的根节点int wide[MAXN];//代表了该节点在第几层int Cmax[MAXN];//i孩子的dp和int Smax[MAXN];//i孙子的dp和int wide_Max;//将无根树建成有根树的最高层也就是叶子节点那儿int n;//节点数//将树打印出来void print_Tree(){    for(int i = 1;i <= n;++i)    {        printf("%d: ",i);        for(int j = 0;j < G[i].size();++j)        {            printf("%d ",G[i][j]);        }        printf("\n");    }}void ReadTree(){    scanf("%d",&n);    for(int i = 0;i < n - 1;++i)    {        int s,e;        scanf("%d %d",&s,&e);        G[s].push_back(e);        G[e].push_back(s);    }}//将无根树建成有根树//建立以v为根节点的树void dfs(int v,int fa){    int d = G[v].size();    //记录该节点的层数    wide[v] = fa == -1 ? 0 : wide[fa] + 1;    //一直更新最大层数    if(wide[v] > wide_Max)        wide_Max = wide[v];    //遍历所连接的每个点    for(int i = 0;i < d;++i)    {        int k = G[v][i];        //这里防止出现死递归,不能将自己的父亲节点再认为儿子        if(k != fa)            dfs(k,f[k] = v);    }}int rootDP(int root){    //初始化根节点的父亲节点    f[root] = -1;    //初始化最大层数    wide_Max = -1;    //建立以root为根的树    dfs(root,-1);    //从叶子节点往上推,用刷表法更新i的父亲和祖父    for(int i = wide_Max;i >= 0;--i)    {        //遍历节点找出来所在层数对应的节点        for(int j = 1;j <= n;++j)        {            if(wide[j] == i)            {                d[j] = max(Cmax[j],Smax[j] + 1);                if(i >= 1)//至少有一个孩子                    Cmax[f[i]] += d[i];                if(i >= 2)//至少有孩子和孙子                    Smax[f[f[i]]] += d[i];            }        }    }}int solve(){    int ans = 0;    for(int i = 1;i <= n;++i)    {        memset(Cmax,0,sizeof(Cmax));        memset(Smax,0,sizeof(Smax));        int tmp = rootDP(i);        ans = max(ans,tmp);    }    return ans;}int main(){     ReadTree();     print_Tree();    return 0;}

最优矩阵链乘::

//线性DP/*最优矩阵链乘:f(i,j) = min{f(i,k) + f(k + 1,j) + p[i - 1]*p[k]*p[r]}*//*#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int MAXN = 105;const int inf = 0x3f3f3f3f;int d[MAXN][MAXN];int p[MAXN];int n;int dp(int l,int r){    int &ans = d[l][r];    if(ans != -1) return ans;//这步叫记忆化搜索    if(l == r) return ans = 0;    if(l + 1 == r){qq        return ans = p[l - 1] * p[l] * p[r];    }    //这里进来说明d[l][r]没计算过,要将之初始化。(还是特殊值)    ans = inf;    for(int k = l + 1;k < r;++k)    {        ans = min(ans,dp(l,k) + dp(k + 1,r) + p[l - 1] * p[k] * p[r]);    }    return ans;}int main(){    while(~scanf("%d",&n))    {        int i,j;        for(i = 0;i < n;++i)        {            scanf("%d",&p[i]);        }        memset(d,-1,sizeof(d));        int m = dp(1,n - 1);        printf("%d\n",m);    }    return 0;}*/

最优三角剖分:

/* 最优三角剖分,d(i,j) = max{d(i,k) + d(k,j) + w(i,j,k)}*/#include<iostream>#include<cstdio>#include<cstring>#include<algorithm>using namespace std;const int MAXN = 105;int d[MAXN][MAXN];int w[MAXN];int n;int dp(int i,int j){    int &ans = d[i][j];    if(ans != -1) return ans;    if(j == i + 1) return ans = 0;    if(j == i + 2){        return ans = w[i] + w[i + 1] + w[i + 2];    }    ans = -(1<<30);    for(int k = 1;k <= n;++k)    {        ans = max(ans,dp(i,k) + dp(k,j) + w[i] + w[j] + w[k]);    }    return ans;}int main(){    while(~scanf("%d",&n))    {        int i,j;        for(i = 1;i <= n;++i)        {            scanf("%d",&w[i]);        }        memset(d,-1,sizeof(d));        int m = dp(1,n);        printf("%d\n",m);    }    return 0;}