18.8.14 考试总结 (dp专场)

最长上升子序列(lis)
给出一个数列{An},找出其中最长上升子序列的长度。上升子序列中前一个数严格小于后一个数。


【输入格式】
第一行一个数n,表示数的个数。
第二行n个数,第i个数表示Ai。


【输出格式】
一个整数,表示最长上升子序列的长度。

 

这道题还是比较简单了 数据范围要求的是nlog的算法

一些dalao写的是老李讲过的在前面二分

我比较蒟蒻 我自己yy...

就弄了一个值域线段树 因为数是1e9大小 所以在离散化一下就可以了

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 100000 + 5;

int n,m,a[N],f[4 * N],b[N];

int query(int o,int l,int r,int L,int R) {
    
    if(l >= L && r <= R) return f[o];
    int mid = (l + r) >> 1;
    int ans = 0;
    if(L <= mid) ans = max(ans,query(2 * o,l,mid,L,R));
    if(mid < R) ans = max(ans,query(2 * o + 1,mid + 1,r,L,R));
    return ans;
}

void update(int o) {
    
    f[o] = max(f[2 * o],f[2 * o + 1]);
}

void modify(int o,int l,int r,int pos,int val) {
    
    if(l == r) {
        f[o] = max(f[o],val);
        return ;
    }
    int mid = (l + r) >> 1;
    if(pos <= mid) modify(2 * o,l,mid,pos,val);
    else modify(2 * o + 1,mid + 1,r,pos,val);
    update(o);
}

int main( ) {
    
    freopen("lis.in","r",stdin);
    freopen("lis.out","w",stdout);
    scanf("%d",& n);
    for(int i = 1;i <= n;i ++) {
        scanf("%d",& a[i]);
        b[i] = a[i];
    }
    sort(b + 1,b + n + 1);
    m = unique(b + 1,b + n + 1) - b - 1;
    for(int i = 1;i <= n;i ++) {
        int pos = lower_bound(b + 1,b + m + 1,a[i]) - b;
        pos ++;
        int ma = query(1,1,m + 1,1,pos - 1);
        modify(1,1,m + 1,pos,ma + 1);
    }
    printf("%d",f[1]);
    return 0;
}

 

隔离村庄(isolate)
有n个村庄,通过n-1条双向路连通。Jyb同学财大气粗,想要买其中的恰好k个村庄。为了做一些秘密的事情,

他想要通过破坏原来的路,在保证自己的k个村庄相互连通的情况下,把这k个村庄与其他村庄隔离开。

请问他应该买哪k个村庄,最少破坏多少条路呢?


【输入格式】
第一行两个数n, k,表示村庄的个数和jyb想要买的村庄个数。
接下来n-1行,每一行两个数u, v。表示村庄u与v相连。


【输出格式】
一个整数,表示最少破坏路的条数。

 

我思维真的太垃圾了 一开始我还想的网络流搞一个最小割 就是因为这个垃圾数据范围是n,k <= 150

什么枚举连通块然后跑最小割什么的呕

然后就有了更骚的贪心算法... 就是每个点加入块中会产生新的需要割的点和减去原来相连的玩意儿

我就贪心弄出来最小的费用 然鹅我并不会定义优先队列... 咸鱼摊

正解是一个树上dp

有两种做法

1.我们可以发现每个连通块他需要割掉的边的条数是(sigma d) - (2 * k - 2) sigma d是所有点的度数和

后面那一坨是一个常数 然后就转化成了求最小度数的连通块 就把每个点的权值搞成他的度数就可以了

就跑一个树上背包就可以了

2.同样是树上背包 定三维 dp[u][k][0/1]表示以u为根的子树保留k个 保留的当中有没有u的最小切割方案

然后就是对转移进行讨论 一个是直接从一棵子树转移而来 还可能从不止一棵子树转移而来

不止一棵的时候一定要从dp[son][p][1]转移过来 这样才能保证它是一个连通块 很多细节多注意一下就可以了

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 155;
int tot,head[N],nex[2 * N],tov[2 * N],size[N],dp[N][N][2],k,n;

void add(int u,int v) {
    
    tot ++;
    nex[tot] = head[u];
    tov[tot] = v;
    head[u] = tot;
}

void dfs(int u,int fa) {
    
    int flag = 0;  size[u] = 1;
    for(int i = head[u];i;i = nex[i]) {
        int v = tov[i];
        if(v == fa) continue;
        dfs(v,u);
        flag ++;
        size[u] += size[v];
    }
    dp[u][1][1] = flag;
    for(int i = head[u];i;i = nex[i]) {
        
        int v = tov[i];
        if(v == fa) continue;
        int ma = min(size[v],k);
        for(int j = ma;j >= 1;j --) {
            dp[u][j][0] = min(dp[u][j][0],dp[v][j][0]);
            dp[u][j][0] = min(dp[u][j][0],dp[v][j][1] + 1);
            dp[u][j][1] = min(dp[u][j][1],dp[v][j - 1][1] + flag - 1);
        }
    }
    int s[155],ma = min(size[u],k) - 1;
    memset(s,0x3f,sizeof(s));
    s[0] = flag;
    for(int i = head[u];i;i = nex[i]) {
        int v = tov[i];
        if(v == fa) continue;
        int cat = min(size[v],k);
        for(int j = ma;j >= 0;j --) {
            for(int t = 0;t <= cat;t ++) {
                if(j - t < 0) break;
                s[j] = min(s[j],s[j - t] + dp[v][t][1] - 1);
            }
        }
    }
    for(int i = 1;i <= ma + 1;i ++) dp[u][i][1] = min(dp[u][i][1],s[i - 1]);
}

int main( ) {
    
    freopen("isolate.in","r",stdin);
    freopen("isolate.out","w",stdout);
    scanf("%d%d",& n,& k);
    for(int i = 1;i < n;i ++) {
        int u,v;
        scanf("%d%d",& u,& v);
        add(u,v); add(v,u);
    }
    memset(dp,0x3f,sizeof(dp));
    dfs(1,1);
    printf("%d",min(dp[1][k][1],dp[1][k][0]));
}

多米诺骨牌(domino)
有一个r * c的矩形,和一些1*2的多米诺骨牌。Jyb想用这些骨牌刚好填满这个矩形,

使得没有位置是空出来的,多米诺骨牌也没有重叠。

请问jyb有多少种方法刚好填满这个矩形呢?一种可能的填法如下图:

设定矩形是有方向的,旋转之后相同和相互对称的填法应当计算为不同的填法。


【输入格式】
第一行两个数r,c。表示矩阵的列数和行数


【输出格式】
一个整数,表示填法总数。

 

很经典的轮廓线dp 做过好几遍了 就是细心 然后特判最后一列就可以了

做的时候我tm空间开小了掉了40分 昨天又是开大了 难受

代码

#include <bits/stdc++.h>
typedef long long ll;
using namespace std;

int r,c;
ll dp[13][13][1 << 13];

int main( ) {
    
    freopen("domino.in","r",stdin);
    freopen("domino.out","w",stdout);
    scanf("%d%d",& r,& c);
    for(int i = 0;i <= r;i ++) {
        if(i == 0) {
            dp[1][2][(1 << c) - 1] = 1;
            dp[1][1][(1 << (c - 1)) - 1] = 1;
        }
        else {
            for(int j = 1;j <= c;j ++) {
                  for(int s = 0;s < (1 << c);s ++) {
    
                    if(j == c) {
                      if((s & 3) == 3) {
                          dp[i + 1][2][(s >> 2) | (1 << (c - 1)) | (1 << (c - 2))]
                          += dp[i][j][s];
                      }
                      if((s & 1) == 0) {
                          dp[i + 1][1][(s >> 1) | (1 << (c - 1))] += dp[i][j][s];
                      } 
                      if(s & 1) {
                          dp[i + 1][1][s >> 1] += dp[i][j][s];
                      }
                    }
                    else {
                      if((j < c - 1) && ((s & 3) == 3) && (c > 2)) {
                          dp[i][j + 2][(s >> 2) | (1 << (c - 1)) | (1 << (c - 2))]
                          += dp[i][j][s];
                      }
                      if((s & 1) == 0) {
                          dp[i][j + 1][(s >> 1) | (1 << (c - 1))] += dp[i][j][s];
                      }
                      if(s & 1) {
                          dp[i][j + 1][s >> 1] += dp[i][j][s];
                      }
                    }
                  }
              }
          }
    }
    printf("%I64d",dp[r][c][(1 << c) - 1]);
    return 0;
}      

凑硬币(coin)
jyb想给他的女朋友买一块很贵的巧克力,jyb有n个硬币,第i个硬币的价值为ci元,

巧克力的价格为k。jyb想用一些硬币凑出恰好k元为他的女朋友买一块巧克力。

看着他的硬币,他想到了一个问题,假设他不给女朋友巧克力,而是给她k元的硬币,

她用这恰好k元的硬币可以凑出哪些价值呢?他想不出来,于是请你帮他解答这个问题。
更准确地说,jyb想要知道所有的价值x,在jyb的硬币中能够找出一个恰好k元的子集S,

使得存在S的子集S2,S2硬币价值的和是x。显然0和k都是符合条件的x。


【输入格式】
第一行两个正整数n, k。
第二行n个正整数,表示硬币的价值ci。


【输出格式】
第一行一个整数n,表示能凑出的价值的个数。
第二行n个不同的整数,从小到大排序,列举题意描述的能凑出的值。

 

做的时候我打的搜索狗了40 搜完了之后的dp其实和正解的思路很像了 但是就是没想到啊

思维太蒟了...  dp[k][x]表示子集和为k的子集能不能凑出值为x的数 

就或一下就好了 小心超界

代码

#include <bits/stdc++.h>

using namespace std;

const int N = 2500 + 10;

int n,k,a[N];
bool dp[N][N];

int main( ) {
    
    freopen("coin.in","r",stdin);
    freopen("coin.out","w",stdout);
    scanf("%d%d",& n,& k);
    for(int i = 1;i <= n;i ++) scanf("%d",& a[i]);
    dp[0][0] = true;
    for(int i = 1;i <= n;i ++) {
        for(int j = k;j >= a[i];j --) {
            for(int s = 0;s <= j;s ++) {
                
                dp[j][s] = dp[j][s] | dp[j - a[i]][s];
            }
            for(int s = a[i];s <= j;s ++) {
                dp[j][s] |= dp[j - a[i]][s - a[i]];
            }
        }
    }
    int ans = 0;
    for(int i = 0;i <= k;i ++) ans += dp[k][i];
    printf("%d\n",ans);
    for(int i = 0;i <= k;i ++) if(dp[k][i]) printf("%d ",i);
}

最可惜的就是第三题...  还要继续加油 争取冲到前面去...!!

冲鸭.....!!!!!!!!!!!!!!!!!!!w

posted @ 2018-08-14 16:12  阿澈说他也想好好学习  阅读(383)  评论(0编辑  收藏  举报