动态规划部分题目小结

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
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值