动态规划部分题目小结

来源:互联网 发布:标识设计软件 编辑:程序博客网 时间:2024/05/29 23:47

UVA10635 LCS转LIS

http://acm.bnu.edu.cn/bnuoj/problem_show.php?pid=19053

两个长度为p+1和q+1的序列A,B,序列中元素互不相同,且都是1到n^2的整数,求AB的LCS,将A中元素变为1到p+1,B中不在A中的元素直接删掉,问题转换为在B中求LIS

复杂度nlogn。

#include <bits/stdc++.h>using namespace std;const int maxn = 255*255;const int INF = 0x3f3f3f3f;int f[maxn],g[maxn],pos[maxn];int LIS(int n){    for(int i = 1; i <= n; ++i) g[i] = INF;    int ans = 0;    for(int i = 1; i <= n; ++i) {        int k = lower_bound(g+1,g+1+n,f[i]) - g;        ans = max(ans,k);        g[k] = f[i];    }    return ans;}int main(){    int T;scanf("%d",&T);    for(int cas = 1; cas <= T; ++cas) {        int n,p,q;        scanf("%d%d%d",&n,&p,&q);        memset(pos,0,sizeof(*pos)*(n*n+4));        for(int i = 0; i <= p; ++i) {            int x;            scanf("%d",&x);            pos[x] = i+1;        }        int idx = 0;        for(int i = 0; i <= q; ++i) {            int x;scanf("%d",&x);            if(pos[x]) {                f[++idx] = pos[x];            }        }        //for(int i = 1; i <= idx; ++i) printf("%d%c",f[i]," \n"[i==idx]);        int ans = LIS(idx);        printf("Case %d: %d\n",cas,ans);    }    return 0;}

UVA 10891 区间型dp

http://acm.bnu.edu.cn/bnuoj/problem_show.php?pid=19309

一个数组,AB轮流取数,只能取连续的,只能从两端取,A先取,都采取最优策略,得分为取的数的和,求A得分-B得分。令d[i][j]表示先手取区间[i,j]能得到的最大价值,转移为

d[i,j] = min(sum[i,j] - min(d[i,k]),sum[i,j] - min(d[k,j]))可以用辅助数组加速转移。

#include <bits/stdc++.h>using namespace std;const int maxn = 100 + 10;const int INF = 0x3f3f3f3f;int dp[maxn][maxn],f[maxn][maxn],g[maxn][maxn];int s[maxn],a[maxn];int main(){    int n;    while(scanf("%d",&n) == 1 && n) {        s[0] = 0;        for(int i = 1; i <= n; ++i) {            scanf("%d",a+i);            dp[i][i] = a[i];            f[i][i] = a[i];            g[i][i] = a[i];            s[i] = s[i-1] + a[i];        }        for(int L = 1; L <= n; ++L) {            for(int i = 1; i + L <= n; ++i) {                int j = i + L;                int m = 0;                m = min(m,f[i+1][j]);                m = min(m,g[i][j-1]);                dp[i][j] = s[j] - s[i-1] - m;                f[i][j] = min(dp[i][j],f[i+1][j]);                g[i][j] = min(dp[i][j],g[i][j-1]);            }        }        printf("%d\n",-s[n]+(dp[1][n]<<1));    }    return 0;}



uva11825 状压dp

http://acm.bnu.edu.cn/bnuoj/problem_show.php?pid=20243

把p1,p2,p3,...pn,n个集合分成尽量多的组,使得组内集合的并为全集,状压dp可做,注意枚举子集的技巧。令d[s]表示s最多能分多少组,转移为d[s] = max(d[s],d[s^s0]+1)需保证s0里集合并集为全集。

#include <bits/stdc++.h>using namespace std;const int maxn = 16;int d[1<<maxn],p[maxn],cover[1<<maxn];void init(int n){    for(int i = 0; i < n; ++i) {        p[i] = (1<<i);        int cnt;scanf("%d",&cnt);        for(;cnt;--cnt) {            int x;scanf("%d",&x);            p[i] |= (1<<x);        }    }    for(int s = 0; s < 1<<n; ++s) {        cover[s] = 0;        for(int b = 0; b < n; ++b) {            if((s>>b)&1) cover[s] |= p[b];        }    }}int main(){    int n,cas = 0;    while(scanf("%d",&n)==1&&n) {        init(n);        memset(d,0,sizeof d);        int ALL = (1<<n)-1;        for(int s = 1; s <= ALL; ++s) {            for(int ns = s; ns; ns = (ns-1)&s) {                if(cover[ns] == ALL) d[s] = max(d[s],d[s^ns]+1);            }        }        printf("Case %d: %d\n",++cas,d[ALL]);    }    return 0;}



UVA 10859 树形dp

http://acm.bnu.edu.cn/bnuoj/problem_show.php?pid=19277

给一个n点,m条边的森林,选最少的点,使得所有的边都被覆盖,且被两个点覆盖的边尽可能的多,原问题等价于选最少的点和使最少的边只被一个点覆盖,令w = M*v1 + v2,v1是点数,v2是被一个点被覆盖的边,只要保证M  > (maxv2-minv2)就能保证在v1最优的情况下在去优化v2。d[u][x]表示u为根的子树w的最小值,x表示u的父亲是否选择。选u的时候d[u][x] =M+sum(d[v][1])+test(u不是根),不选u,d[u][x] = sum(d[v][0]) + x,(不选u只有当u使根或者x等于1才可行)。

#include <bits/stdc++.h>using namespace std;const int maxn = 1e3 + 10;const int M = 2000;vector<int> G[maxn];int d[maxn][2];int dp(int u,int x,int fa){    int & ans = d[u][x];    if(ans != -1) return ans;    ans = M;///u处放灯总是和法的    for(vector<int>::size_type i = 0; i < G[u].size(); ++i) {        int v = G[u][i];        if(v != fa) ans +=dp(v,1,u);    }    if(fa != -1 && x == 0) ++ans;///u不是根,且父亲没放灯    if(fa==-1 || x == 1) {///u是根,或者u的父亲已放灯,u可以不放灯        int sum = 0;        for(vector<int>::size_type i = 0; i < G[u].size(); ++i) {            int v = G[u][i];            if(v != fa) sum += dp(v,0,u);        }        sum += x;        ans = min(ans,sum);    }    return ans;}int main(){    int T;scanf("%d",&T);    while(T--) {        int n,m;scanf("%d%d",&n,&m);        for(int i = 0; i <= n; ++i) G[i].clear();        memset(d,-1,sizeof d);        for(int i = 0; i < m; ++i) {            int u,v;scanf("%d%d",&u,&v);            ++u,++v;            G[u].push_back(v);            G[v].push_back(u);        }        int ans = 0;        for(int i = 1; i <= n; ++i) {            if(d[i][0]==-1) ans += dp(i,0,-1);        }        printf("%d %d %d\n",ans/M,m-ans%M,ans % M);    }    return 0;}



LA3983 单调队列优化dp

http://acm.bnu.edu.cn/bnuoj/problem_show.php?pid=10159

n个垃圾,第i个坐标为(xi,yi,wi),wi为重量,机器人(初始在原点)按照编号从小到大的顺序将n个垃圾放到原点的垃圾桶处,每次可以选择一些垃圾放会垃圾桶,但总重量不得超过c,求机器人行走的最短距离,行走距离为曼哈顿距离。

令d[i] 表示将前i个垃圾放回原点的最小距离,则d[i] = min(d[j] + dist[j+1]+w[j+1,i]+dist[i]),复杂度为n^2,w[i,j]表示从i走到i+1,...j的曼哈顿距离,可以用单调队列优化一下,用sum[i]-sum[j]表示w[j,i],则转移变为d[i] = min(d[j]+dist[j+1]-w[j+1]) + dist[i]+w[i],单调队列维护d[j]+dist[j+1]-w[j+1]的最小值即可

#include <bits/stdc++.h>using namespace std;typedef long long ll;const int maxn = 1e5 + 10;int x[maxn],y[maxn];int total_dist[maxn],total_weight[maxn],dist2orgin[maxn];int d[maxn];int func(int i){    return d[i] - total_dist[i+1] + dist2orgin[i+1];}int main(){    int T;scanf("%d",&T);    while(T--) {        int n,c;scanf("%d%d",&c,&n);        total_dist[0] = total_weight[0] = 0;        x[0] = y[0] = 0;        for(int i = 1; i <= n; ++i) {            int w;            scanf("%d%d%d",x+i,y+i,&w);            dist2orgin[i] = abs(x[i]) + abs(y[i]);            total_dist[i] = total_dist[i-1] + abs(x[i]-x[i-1]) + abs(y[i]-y[i-1]);            total_weight[i] = total_weight[i-1] + w;        }        deque<int>Q;        Q.push_front(0);        for(int i = 1; i <= n; ++i) {            while(!Q.empty() && total_weight[i] - total_weight[Q.front()] > c)Q.pop_front();            d[i] = func(Q.front()) + total_dist[i] + dist2orgin[i];            while(!Q.empty() && func(i) <= func(Q.back())) Q.pop_back();            Q.push_back(i);        }        printf("%d\n",d[n]);        if(T)puts("");    }    return 0;}



LA4794 状压dp

http://acm.bnu.edu.cn/bnuoj/problem_show.php?pid=11676

长宽为x,y的矩形能否划分成面积为a1,a2,...an的小矩形,状压dp,f[S][r][c]表示r×c的矩形能否划分成S集合的一些小矩形,限制r<=c,可以将状态表示成f[S][r]。

#include <bits/stdc++.h>using namespace std;typedef long long ll;const int maxn = 16;const int maxw = 100 + 4;int sum[1<<maxn],f[1<<maxn][maxw];int a[maxn];const char *str[] = {"No","Yes"};int bitcount(int S){    int tot = 0;    for(; S ;S = (S-1) & S) ++tot;    return tot;}int dp(int S,int x){    int & ans = f[S][x];    if(ans + 1) return ans;    ans = 0;    if(bitcount(S) == 1) return ans = 1;    int y = sum[S]/x;    for(int S0 = (S-1)&S; S0; S0 = (S0-1)&S) {        int S1 = S ^ S0;        if(sum[S0]%x==0&&dp(S0,min(x,sum[S0]/x))&&dp(S1,min(x,sum[S1]/x))) return ans = 1;        if(sum[S0]%y==0&&dp(S0,min(y,sum[S0]/y))&&dp(S1,min(y,sum[S1]/y))) return ans = 1;    }    return ans = 0;}int main(){    int n;    for(int cas = 1; ; ++cas) {        scanf("%d",&n);        if(!n) return 0;        int x,y;scanf("%d%d",&x,&y);        for(int i = 0; i < n; ++i) {            scanf("%d",a + i);        }        int ALL = (1<<n) - 1;        for(int s = 0; s <= ALL; ++s) {            sum[s] = 0;            for(int i = 0; i < n; ++i) {                if((s>>i)&1) sum[s] += a[i];            }        }        int ans = 0;        if(sum[ALL] == x*y && sum[ALL] % x == 0) {            memset(f,-1,sizeof f);            ans = dp(ALL,min(x,y));        }        printf("Case %d: %s\n",cas,str[ans]);    }    return 0;}


0 0
原创粉丝点击