2017暑期集训Day 14 区间dp+二分图匹配

题目链接

A Multiplication Puzzle

[Solution]

区间dp水题

[Code]

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1000 + 5;
#define inf 0x3f3f3f3f
vector<int > s[N];
int f[N][N];
int n, a[N];
int main()
{
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
        scanf("%d", &a[i]);
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= n; j++)
          f[i][j] = inf;
    for(int i = 1; i <= n; i++)
    {
        f[i][i] = 0;
        f[i][i + 1] = 0;
    }
    for(int p = 2; p < n; p++)
      for(int i = 1; p + i <= n; i++)
      {
          int j = i + p;
          for(int k = i + 1; k < j; k++)
            f[i][j] = min(f[i][j], f[i][k] + f[k][j] + a[i] * a[j] * a[k]);
      }
    printf("%d", f[1][n]);
    return 0;
}

B - Coloring Brackets

[Problem]

给定一个合法的括号序列,现在我们给括号染色,可以染成红色和蓝色,询问最终方案数。
[Solution]

使用f[i][j][l][r]代表(i,j)这个区间左端l状态,右端j状态的方案数,这样分i、j匹配和不匹配来分类讨论,注意为了简化编码复杂度,我们可以可以将不合法的状态方案数标记为0,这样累加的时候判断相对容易

[Code]

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
const int N = 700 + 50;
const int mo = 1e9 + 7;
#define inf 0x3f3f3f3f
string ss;
int n, m, k, Case = 0, l[N];
ll f[N][N][3][3];
int main()
{
  //  freopen("b.in", "r", stdin);
    cin>>ss;
    n = ss.length();
    stack<int> s;
    for(int i = 1; i <= n; i++)
        if (ss[i - 1] == '(')
            s.push(i);
        else
        {
            int x = s.top();
            l[x] = i;
            s.pop();
        }
    for(int i = 1; i < n; i++)
        if (ss[i - 1] == '(' && ss[i] == ')')
    {
        f[i][i + 1][0][1] = 1;
        f[i][i + 1][1][0] = 1;
        f[i][i + 1][0][2] = 1;
        f[i][i + 1][2][0] = 1;
    }
    for(int p = 3; p < n; p++)
        if (p % 2 == 1)
          for(int i = 1; i + p <= n; i++)
    {
        int j = i + p;
        if (l[i] == j)
        {
            for(int ll = 0; ll < 3; ll++)
                for(int rr = 0; rr < 3; rr++)
            {
                if (rr != 1)
                    f[i][j][0][1] = (f[i][j][0][1] + f[i + 1][j - 1][ll][rr]) % mo;
                if (ll != 1)
                    f[i][j][1][0] = (f[i][j][1][0] + f[i + 1][j - 1][ll][rr]) % mo;
                if (ll != 2)
                    f[i][j][2][0] = (f[i][j][2][0] + f[i + 1][j - 1][ll][rr]) % mo;
                if (rr != 2)
                    f[i][j][0][2] = (f[i][j][0][2] + f[i + 1][j - 1][ll][rr]) % mo;
            }
        }
        else
        {
            int x = l[i];
      //      printf("%d %d %d %d\n", i, x, x + 1, j);
            for(int l1 = 0; l1 < 3; l1++)
               for(int r1 = 0; r1 < 3; r1++)
                    for(int r2 = 0; r2 < 3; r2++)
                       for(int l2 = 0; l2 < 3; l2++)
                             if (!((l2 == 1 && r1 == 1) || (l2 == 2 && r1 == 2)) )
                             {
                                 f[i][j][l1][r2] = (f[i][j][l1][r2] + f[i][x][l1][r1] * f[x + 1][j][l2][r2]) % mo;
                                // printf("(%d %d %d %d)- %d   %d\n", l1, r1, l2, r2, f[i][x][l1][r1], f[x + 1][j][l2][r2]);
                             }

        }
    }
    ll ans = 0;
    for(int i = 0; i < 3; i++)
        for(int j = 0; j < 3; j++)
          ans = (ans + f[1][n][i][j]) % mo;
    cout<<ans;
    return 0;
}

C - Halloween Costumes

[Solution]

如果从状态上去考虑此道题目的会gg,因为对于每一天,身上穿的衣服有好多种情况,是不能存储的
我们从每天的决策去考虑,可以选择穿一件新衣服,或者脱掉一些衣服,即我们假设现在是第i天,需要用第k天的衣服,这样(k + 1, j-1)这些天就不能用前i天的衣服了,这样我们用f[i][j]代表(i,j)这些天独立的最优解
f[i][j] = f[i][k] + f[k + 1][j - 1]

[Code]

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
const int N = 1000 + 50;
const int mo = 1e9 + 7;
#define inf 0x3f3f3f3f
vector<int > s[N];
typedef long long ll;
int n, m, k, f[N][N], a[N], Case = 0;
int main()
{
 //  freopen("b.in", "r", stdin);
    int T;
    scanf("%d", &T);
    while(T--)
    {
        scanf("%d", &n);
        for(int i = 1; i <= n; i++)
            scanf("%d", &a[i]);
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
              f[i][j] = inf;
        for(int i = 1; i < n; i++)
        {
            f[i][i] = 1;
            if (a[i] == a[i + 1])
                f[i][i + 1] = 1;
            else
                f[i][i + 1] = 2;
        }
        f[n][n] = 1;
        for(int p = 2; p < n; p++)
            for(int i = 1; i + p <= n; i++)
        {
            int j = i + p;
            int t = 1;
            if (a[j] == a[j - 1])
                t = 0;
            f[i][j] = min(f[i][j], f[i][j - 1] + t);
            for(int k = i; k + 1 <= j - 1; k++)
              if (a[k] == a[j])
                f[i][j] = min(f[i][j], f[i][k] + f[k + 1][j - 1]);
        }
        printf("Case %d: %d\n", ++Case, f[1][n]);

    }
    return 0;
}

D - Food Delivery

[Problem]

坐标轴上有n个点,初始值自己位于一个点,你需要经过每个点一次,每个点有个愤怒值,会随着时间的增加而线性增加,询问总愤怒值最小化的方案。

[Solution]
本题很容易想到部分贪心策略,即在初始点的同一侧的两个点x, y, 离初始点较近的点肯定优先的到满足,因此对于满足[l, r]区间后,肯定位于l位置或r位置,因此我们用f[i][j]代表处理区间(i, j)后,位于i位置,g[i][j]则代表位于j位置的最优解,但是[i][j]区间的最小代价的转移需要到现在位置花费的时间,而我们每次取最小代价维护的最小时间是不对的,因为可能此次尽可能地让时间小一些,虽然此次决策的代价稍大一些,但是下一步的决策要优很多

我们从另一个方向上去考虑吧,我们每次转移的时候不能同时保证两个状态最优,因此我们累加答案可以换一个姿势,先前我们通过点的方式来累加答案,但是时间这个变量难以维护,这下我们通过时间的方式,即此次转移,[i][j]区间之外的点都会增加愤怒值,这样转移就不会出现两个最优性方案了

[Code]

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N = 1000 + 5;
#define inf 0x3f3f3f3f
vector<int > s[N];
ll f[N][N], g[N][N];
ll sum[N];
struct node{
   int x, y;
};
node point[N];
int n, v, st, res;
bool cmp(node a, node b)
{
    return a.x < b.x;
}
int main()
{
//    freopen("b.in", "r", stdin);
    while(~scanf("%d%d%d", &n, &v, &st))
    {
        for(int i = 1; i <= n ;i++)
            scanf("%d%d", &point[i].x, &point[i].y);
        n++;
        point[n].x = st;
        point[n].y = 0;
        sort(point + 1, point + n + 1, cmp);
        for(int i = 1; i <= n; i++)
            if (point[i].x == st)
        {
            res = i;
            break;
        }
        sum[0] = 0LL;
        for(int i = 1; i <= n; i++)
            sum[i] = sum[i - 1] + point[i].y;
        for(int i = 1; i <= n; i++)
            for(int j = 1; j <= n; j++)
        {
            f[i][j] = inf;
            g[i][j] = inf;
        }
        f[res][res] = 0;
        g[res][res] = 0;
        for(int i = res; i >= 1; i--)
            for(int j = res; j <= n; j++)
        {
            if (i == j && i == res)
                continue;
            f[i][j] = min(f[i][j], f[i + 1][j] + 1LL * abs(point[i + 1].x - point[i].x) * (sum[n] - sum[j] + sum[i]));
            f[i][j] = min(f[i][j], g[i + 1][j] + 1LL * abs(point[j].x - point[i].x) * (sum[n] - sum[j] + sum[i]));
            g[i][j] = min(g[i][j], g[i][j - 1] + 1LL * abs(point[j].x - point[j - 1].x) * (sum[n] - sum[j - 1] + sum[i - 1]));
            g[i][j] = min(g[i][j], f[i][j - 1] + 1LL * abs(point[j].x - point[i].x) * (sum[n] - sum[j - 1] + sum[i - 1]));
         //   printf("f[%d][%d] = %d\n", i, j, f[i][j]);
         //   printf("g[%d][%d] = %d\n", i, j, g[i][j]);
        }
        cout<<min(f[1][n], g[1][n]) * v<<endl;
    }
    return 0;
}

F - Asteroids

[Problem]

N * N 的网格中有k个路障,每次可以清除一列或一行的路障,询问最小次数(n < 500)

[Solution]

对于二分图,有最大匹配数等于最小点覆盖数,即我们选取选取尽可能少的点,使得每个边都有点覆盖,选取的最小点数即最小点覆盖数。
这样我们要清除掉所有的路障,因此我们把路障类比为边, 那什么类比成点呢,每条边连接的东西便是点,而路障连接的不是点数和列数吗?
因此我们把每一行的编号类比成点,每一行的列类比成二分图的另外一部分的点,这样是一个二分图,把路障类比成边,这样跑一遍匈牙利算法算出最小点覆盖数即可

[Code]

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int N = 505;
vector<int > s[N];
int girl[N];
bool used[N];
int n, m, k;
bool found(int x)
{
    for(int i = 0; i < s[x].size(); i++)
    {
        int y =  s[x][i];
        if (used[y])  continue;
        used[y] = true;
        if (girl[y] == 0 || found(girl[y]))
        {
            girl[y] = x;
            return 1;
        }
    }
    return 0;
}
int main()
{
   // freopen("b.in", "r", stdin);
    while(~scanf("%d%d", &n, &k))
    {
        memset(girl, 0, sizeof(girl));
        for(int i = 1; i <= n; i++)
            s[i].clear();
        for(int i = 1; i <= k; i++)
        {
            int x, y;
            scanf("%d%d", &x, &y);
            s[x].push_back(y);
        }
        int ans = 0;
        for(int i = 1; i <= n; i++)
        {
            memset(used, 0, sizeof(used));
            if (found(i))
                ans++;
        }
        printf("%d\n", ans);
    }
    return 0;
}

G - Chessboard

[Problem]
N* M的网格中有一些障碍物,现有1*2格纸若干,判断是否将格纸覆盖住网格,使得每一个网格上只有一层贴纸。

[Solution]
十分经典的二分图匹配,首先,同一个贴纸的两个点行数与列数和的奇偶性肯定相反,这样我们把行列数为奇数的为一类点,行列数为偶数的为一类点,这样把一个点与其四周的点,连边,最后跑一边二分图最大匹配即可
[Code]

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<stack>
using namespace std;
typedef long long ll;
const int N = 1000 + 50;
const int mo = 1e9 + 7;
#define inf 0x3f3f3f3f
vector<int > s[N];
typedef long long ll;
int n, m, k, a[N][N], girl[N];
bool used[N];
int dx[] = {1, 0, -1, 0};
int dy[] = {0, 1, 0, -1};
bool found(int x)
{
    for(int i = 0; i < s[x].size(); i++)
    {
        int y =  s[x][i];
        if (used[y])  continue;
        used[y] = true;
        if (girl[y] == 0 || found(girl[y]))
        {
            girl[y] = x;
            return 1;
        }
    }
    return 0;
}
int main()
{
  //  freopen("b.in", "r", stdin);
    int n, m, k;
    scanf("%d%d%d", &n, &m, &k);
    int top1 = 0, top2 = 0;
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
            if ((i + j) % 2 == 0)
              a[i][j] = ++top1;
            else
              a[i][j] = ++top2;
    if ((n * m - k) % 2 == 1)
    {
        printf("NO");
        return 0;
    }
    for(int i = 1; i <= k; i++)
    {
        int x, y;
        scanf("%d%d", &y, &x);
        a[x][y] = -1;
    }
    for(int i = 1; i <= n; i++)
        for(int j = 1; j <= m; j++)
           if (a[i][j] > 0 && (i + j) % 2 == 0)
    {
        int x = a[i][j];
        for(int v = 0; v < 4; v++)
        {
            int xx = i + dx[v];
            int yy = j + dy[v];
            if (xx <= 0 || xx > n || yy <= 0 || yy > m)
              continue;
            if (a[xx][yy] <= 0)
                continue;
            int y = a[xx][yy];
            s[x].push_back(y);
          //  printf("---%d %d\n",x , y);
        }
    }
    int ans = 0;
    for(int i = 1; i <= top1; i++)
    {
            memset(used, 0, sizeof(used));
            if (found(i))
                {
                    ans++;
                    //printf("%d\n", i);
                }
    }
   // printf("%d\n", ans);
    int x = n * m - k;
    printf("%s", x == 2 * ans ? "YES" : "NO");
    return 0;
}

H - Book Club

[Problem]

n个人有n本书,每个人都有自己喜欢的书,判断是否存在一种交换方案,使得每个人获得一本自己喜爱的书。

[Solution]

很明显的二分图最大匹配

[Code]

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
using namespace std;
const int N = 10000 + 5;
vector<int > s[N];
int girl[N];
bool used[N];
int n, m, k;
bool found(int x)
{
    for(int i = 0; i < s[x].size(); i++)
    {
        int y =  s[x][i];
        if (used[y])  continue;
        used[y] = true;
        if (girl[y] == 0 || found(girl[y]))
        {
            girl[y] = x;
            return 1;
        }
    }
    return 0;
}
int main()
{
  //  freopen("b.in", "r", stdin);
    while(~scanf("%d%d", &n, &k))
    {
        memset(girl, 0, sizeof(girl));
        for(int i = 1; i <= n; i++)
            s[i].clear();
        for(int i = 1; i <= k; i++)
        {
            int x, y;
            scanf("%d%d", &x, &y);
            x++;
            y++;
            s[x].push_back(y);
        }
        int ans = 0;
        for(int i = 1; i <= n; i++)
        {
            memset(used, 0, sizeof(used));
            if (found(i))
                {
                    ans++;
                   // printf("%d\n", i);
                }
        }
        if (ans == n)
            printf("YES");
        else
            printf("NO");
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值