hdu5745--La Vie en rose (DP+bitset)

好题,学到新姿势!

题意:给两个字符串 a 和 b ,b可以进行变换,规则是可以任意交换相邻两个字符的位置,但是不可以有交叉(例如3和4交换,5和6交换 互不影响,但是2和3,3和4就不可以)。求a中每一个位置能不能匹配b或b变幻得到的子串。

题解:考虑dp。dp[i][j][k]表示a[i]和b[j]匹配,k为1表示j未做交换,k=0表示j和j-1进行交换,k=2表示j和j+1进行交换。

这样写出来的代码:

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
const int N = 10005;
const int M = 5005;
char a[N], b[N];
int ans[N];
bool dp[N][M][3]; // 0和前面的交换 1不交换 2和后面的交换
int main()
{
//freopen("in", "r", stdin);
    int T;
    int la, lb;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &la, &lb);
        scanf("%s%s", a+1, b+1);
        memset(dp, false, sizeof dp);
        for (int i = 0; i <= la; ++i) dp[i][0][1] = true;
        for (int i = 1; i <= la; ++i) {
            for (int j = 1; j <= lb; ++j) {
                dp[i][j][0] = (dp[i-1][j-1][2] && a[i] == b[j-1]);
                dp[i][j][1] = (dp[i-1][j-1][1] && a[i] == b[j]) || (dp[i-1][j-1][0] && a[i] == b[j]);
                dp[i][j][2] = (dp[i-1][j-1][1] && a[i] == b[j+1]) || (dp[i-1][j-1][0] && a[i] == b[j+1]);
            }
            if (dp[i][lb][1] || dp[i][lb][0]) ans[i] = 1;
            else ans[i] = 0;
        }
        for (int i = lb; i <= la; ++i) printf("%d", ans[i]);
        for (int i = 1; i < lb; ++i) printf("0");
        printf("\n");
    }
    return 0;
}

然而很明显,这会爆内存。

容易想到使用滚动数组,因为递推时只与前一个状态有关。

加了滚动数组的代码:

#include <iostream>
#include <cstdio>
#include <vector>
#include <cstring>
using namespace std;
const int N = 100005;
const int M = 5005;
char a[N], b[N];
int ans[N];
bool dp[2][M][3]; // 0和前面的交换 1不交换 2和后面的交换
int main()
{
//freopen("in", "r", stdin);
    int T;
    int la, lb;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &la, &lb);
        scanf("%s%s", a+1, b+1);
        memset(dp, false, sizeof dp);
        dp[0][0][1] = dp[1][0][1] = true;
        int now = 1;
        for (int i = 1; i <= la; ++i) {
            for (int j = 1; j <= lb; ++j) {
                dp[now][j][0] = (dp[now^1][j-1][2] && a[i] == b[j-1]);
                dp[now][j][1] = ((dp[now^1][j-1][1] || dp[now^1][j-1][0]) && a[i] == b[j]);
                dp[now][j][2] = ((dp[now^1][j-1][1] || dp[now^1][j-1][0]) && a[i] == b[j+1]);
            }
            if (dp[now][lb][1] || dp[now][lb][0]) ans[i] = 1;
            else ans[i] = 0;
            now ^= 1;
        }
        for (int i = lb; i <= la; ++i) printf("%d", ans[i]);
        for (int i = 1; i < lb; ++i) printf("0");
        printf("\n");
    }
    return 0;
}

很棒,这回不MLE了,但是TLE。

其实O(N*M)的复杂度,T了倒也很正常,但是据说各种姿势的暴力都能过,为甚么这就过不去>_<

继续优化。

---

学习一下bitset。

可以当作一个bool型数组考虑,bitset<N> bs;  可以考虑成一个数组bool bs[N]。

相关操作:

bs.set(); 全部置1,bs.reset()全部置0;

bs.set(pos);等价于bs[pos]=1,bs.reset(pos)等价于bs[pos]=0;

最重点的来了,bitset<N> a, b;

a和b可以直接进行操作,

!a //按位取反
a^b //按位异或
a|b //按位或
a&b //按位与
a=b<<3 //整体移位
a.count(); //a中1的个数

bitset有什么用呢(也许还有其他的用处,这里讲的是本题用到的)

如果有一个bool数组 a[N] 和b[N] 把每一个位异或的话,一定是

for (int i = 0; i < N; ++i) c[i] = a[i] ^ b[i];

但是如果用bitset直接a^b的话,只需要O(N/机器字节数)

这样可以实现常数优化。

---

这和这道题有什么关系呢?

观察dp方程,

dp[i][j][1] = ((dp[i-1][j-1][1] || dp[i-1][j-1][0]) && a[i] == b[j]);

先考虑一个,其余两个同理。

可以发现dp[i]只与dp[i-1]有关。

那么把dp数组定义为bitset<N> dp[2][3] ,就可以把dp方程写成dp[j][1] = ((dp[j-1][1] | dp[j-1][0]) << 1);

这里应该很好理解,也很神奇,其中左移是因为这里j-1求出的是j。

但是a[i]==b[j]要怎么处理呢?

因为前面是用一个bitset处理的,我们希望把a[i]==b[j]也化成一个bitset的形式,也就是对于每一个j,有一个bitset<N> bs,bs[i]其中来表示a[i]==b[j]的值,但是这样又是N*M,明显不可能的,其实不需要对于每一个j都有一个bitset,因为一共有26个字母,于是只需对每一个字母求出,即bitset<N>26,然后使用b[j]那个的字母bitset就可以了。

至于滚动数组就很好实现了,

这题正解反而卡常数卡的厉害,姿势不对很容易超时,没人性!! T^T

AC代码(3166MS)

#include <cstdio>
#include <bitset>
#include <iostream>
#include <cstring>
using namespace std;
const int N = 100005;
const int M = 5005;
char a[N];
char b[M];
//bool dp[N][M][3];    0和前面的交换 1不交换 2和后面的交换
bitset<N> dp[2][3];
bitset<N> w[30];    // 记录对于每个字母 p[i]是否为这个字母
int main()
{
    //freopen("in", "r", stdin);
    int T;
    int la, lb;
    scanf("%d", &T);
    while (T--) {
        scanf("%d%d", &la, &lb);
        scanf("%s%s", a, b);

        for (int i = 0; i < 26; ++i) w[i].reset();
        for (int i = 0; i < la; ++i) w[a[i]-‘a‘][i] = 1;
        for (int i = 0; i < 2; ++i) for (int j = 0; j < 3; ++j) dp[i][j].reset();
        dp[0][1] = w[b[0]-‘a‘];
        if (lb > 1) dp[0][2] = w[b[1]-‘a‘];

        int now = 0;
        for (int j = 1; j < lb; ++j) {
            now ^= 1;
            dp[now][0] = ((dp[now^1][2]) << 1) & w[b[j-1]-‘a‘];
            dp[now][1] = ((dp[now^1][0] | dp[now^1][1]) << 1) & w[b[j]-‘a‘];
            if (j < lb - 1) dp[now][2] = ((dp[now^1][0] | dp[now^1][1]) << 1 ) & w[b[j+1]-‘a‘];
         }

         for (int i = 0; i < la; ++i) {
             if (dp[now][0][i+lb-1] || dp[now][1][i+lb-1]) printf("1");
             else printf("0");
         }
         puts("");

    }
    return 0;
}
时间: 2024-11-05 14:58:36

hdu5745--La Vie en rose (DP+bitset)的相关文章

hdu 5745 La Vie en rose(dp+bitset)

题目链接:hdu 5745 La Vie en rose 题意: 给两个字符串 a 和 b ,b可以进行变换,规则是可以任意交换相邻两个字符的位置,但是不可以有交叉(例如3和4交换,5和6交换 互不影响,但是2和3,3和4就不可以).求a中每一个位置能不能匹配b或b变换得到的子串. 题解: 考虑dp,dp[i][j][k]表示a[i]和b[j]匹配,k为1表示j未做交换,k=0表示j和j-1进行交换,k=2表示j和j+1进行交换. 然后bitset优化一下常数. dp方程为: dpi,j,0=d

HDU 5745 La Vie en rose (DP||模拟) 2016杭电多校联合第二场

题目:传送门. 这是一道阅读理解题,正解是DP,实际上模拟就能做.pij+1 指的是 (pij)+1不是 pi(j+1),判断能否交换输出即可. #include <iostream> #include <algorithm> #include <cstdio> #include<cstring> using namespace std; int t,n; char str1[100009],str2[5009]; char tmp[5009]; int m

hdu 5745 la vie en rose

这道题的官方题解是dp,但是可以暴力出来.改天再研究怎么dp. 暴力的时候,如果计算sum的时候,调用strlen函数会超时,可见这个函数并不是十分的好.以后能不用尽量不用. La Vie en rose Time Limit: 14000/7000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 861    Accepted Submission(s): 461 Problem

La ve en rose 玫瑰人生

玫瑰人生是是法国著名女歌手爱迪特的一首代表作品,这首歌是她对自己人生的概括,虽然她一生遭逢诸多不幸(这些都在她的作品中表现了),但她的这首玫瑰人生却仍然透过爱情的方式来展示她自己快乐幸福的人生态度.这首歌在法国流传甚广,刘欢和苏菲玛索在去年的春晚上演唱过它.下面是这首歌的歌词和译文. Des yeux qui font baiser les miens 他的双唇吻我的眼 Un rire qui se perd sur sa bouche 一抹微笑掠过他的嘴边 Voilà le portrait

HOBBIES-LA VIE EN ROSE

 http://www.acfun.tv/a/aa3929900 http://www.acfun.tv/a/aa3930098 http://www.acfun.tv/a/aa3930298 http://www.acfun.tv/a/aa3930494 http://www.acfun.tv/a/aa3930845 http://www.acfun.tv/a/aa3931039 http://www.acfun.tv/a/aa3931231 http://www.acfun.tv/a/a

LA 4256 Salesmen 线性dp

// LA 4256 Salesmen 线性dp // // 像LCS和LIS问题类似,因为每次修改一个值,都是根据 // 前一个值决定的,那么最后一个结尾的数字肯定要作为 // 状态,而长度作为状态是一目了然的 // // d[i][j]表示长度为i,最后以j结尾的数组修改的最小次数 // // 则状态转移方程为 // // d[i][j] = min(d[i][j],d[i-1][k]+(j,k是否相同或者相邻?0:1)); // // 个人感觉还是比较明显的,最后的答案就是min(d[L]

字符串匹配dp+bitset,滚动数组优化——hdu5745(经典)

bitset的经典优化,即把可行性01数组的转移代价降低 bitset的适用情况,当内层状态只和外层状态的上一个状态相关,并且内层状态的相关距离是一个固定的数,可用bitset,换言之,能用滚动数组是能用bitset优化的前提 /* dp[i,j][0|1|2]表示p串的第i位,s串的第j位相匹配,pi和pi-1换,pi不换,pi和pi+1换的状态下是否能匹配 dp[i,j][0] = dp[i-1,j-1][2] & p[i-1]==s[j] dp[i,j][1] = (dp[i-1,j-1]

hdu5313Bipartite Graph(二分图染色+DP(bitset优化))

题意:给n个点m条边,问最多可以添加几条边使图为完全二分图 分析:如果二分图没有限制,看到是两边分别为n/2个点和n-n/2个点的最优但是可   能出现大于此点的情况,比如n=4,m=3,边为1 2,1 3,1 4.此时完全二分图边最多为3,所以要求得二分图左边或者右边可达到的离n/2最近的点数是多少为最优解,于是采用染色分别求出各个联通快的2种颜色的各个点数,然后用推出能达到的点数,这里dp[i]的下一个状态是dp[i+1]+d[i+1][0]和dp[i+1][1](dp[i]为前i个二分图的

HDU 5313——Bipartite Graph——————【二分图+dp+bitset优化】

Bipartite Graph Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)Total Submission(s): 840    Accepted Submission(s): 285 Problem Description Soda has a bipartite graph with n vertices and m undirected edges. Now he w