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

来源:互联网 发布:上海红歆财富 骗局知乎 编辑:程序博客网 时间:2024/05/29 13:37

题目链接

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 0x3f3f3f3fvector<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 0x3f3f3f3fstring 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 0x3f3f3f3fvector<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 0x3f3f3f3fvector<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 0x3f3f3f3fvector<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;}
原创粉丝点击