离散化,二进制压缩dp,树状数组 HDU

离散化可以用来缩小所需空间,将一定数量但范围很大的数据压缩为范围很小的但仍能保持它们之间的相对性质的数据。 例如有一百个数据,这些数据范围为1-1e9,在处理这些数据的过程中仅用到它们之间的大小关系,便可以将这一百个数据用1-100来表示,也就是为不同数据赋予不同的ID值。由于所涉及的东西不多,离散化对于我的作用仅限于以上。

离散化(百度百科):

离散化,把无限空间中有限的个体映射到有限的空间中去,以此提高算法的时空效率。
通俗的说,离散化是在不改变数据相对大小的条件下,对数据进行相应的缩小。例如:
原数据:1,999,100000,15;处理后:1,3,4,2;
原数据:{100,200},{20,50000},{1,400};
处理后:{3,4},{2,6},{1,5};
离散化是程序设计中一个非常常用的技巧,它可以有效的降低时间复杂度。其基本思想就是在众多可能的情况中“只考虑我需要用的值”。离散化可以改进一个低效的算法,甚至实现根本不可能实现的算法。要掌握这个思想,必须从大量的题目中理解此方法的特点。
举例:建造线段树空间不够,可以考虑离散化。
 
有n个工程,每个工程所需能力数量和数值不同;m个工程师,每个工程师能力数量和数值不同。当且仅当工程师能力与工程能力具有完全匹配的数值该工程师才能进行这项工程,一个工程师只能进行一个工程,一个工程可以有多个工程师同时进行。求最多可能完成多少个工程。
分析后,一个工程最多有三个工程师同时进行,最多有十个工程师,最多有二十种不同能力数值,如果将二十个数值进行压缩为1-20(赋予1-20为该数值的ID),将ID设为二进制串的位数,则一个int值就可以表示一个工程师的所有能力。例如工程师C的能力为 24 56(ID为6, 12), 那这个工程师可用 (1 << (6-1)) + (1 << (12-1))  100000100000表示。同样用二进制串表示工程师的选择,一共(最多)有 (1<<10)选择方式,例如 0100011010 位选择第2 4 5 9个工程师。然后各种枚举,dp,数据处理,上代码。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#include <algorithm>
#include <queue>
#include <stack>
#include <cmath>
#include <map>
#include <bitset>
#include <set>
#include <vector>
#include <functional>

using namespace std;
bool hasnotong(int a, int b)
{
    while(a > 0 && b > 0)
    {
        if((a & 1) && (b & 1)) return false;
        a /= 2;
        b /= 2;
    }
    return true;
}
int main()
{
    int t;
    scanf("%d", &t);
    for(int ca = 1; ca <= t; ca++)
    {
        int n, m;
        int A1[15][6];
        scanf("%d%d", &n, &m);
        //读入工程
        for(int i = 0; i < n; i++)
        {
            scanf("%d", &A1[i][0]);
            // cout << "=== A1[i][0] = " << A1[i][0] << endl;
            for(int j = 1; j <= A1[i][0]; j++)
            {
                scanf("%d", &A1[i][j]);
            }
        }
        //读入工程师
        // cout << "===" << endl;
        int vis[105];
        int C[105];
        memset(vis, 0, sizeof(vis));
        int B1[15][6];
        int num = 0;
        for(int i = 0; i < m; i++)
        {
            scanf("%d", &B1[i][0]);
            for(int j = 1; j <= B1[i][0]; j++)
            {
                scanf("%d", &B1[i][j]);
                if(!vis[B1[i][j]])
                {
                    vis[B1[i][j]] = 1;    //记录出现的数据, 用于离散化
                    C[num++] = B1[i][j];
                }
            }
        }
        //离散化数据
        int B2[15];//工程师压缩
        sort(C, C + num);
        int ID[105];
        for(int i = 0; i < num; i++)
        {
            ID[C[i]] = i;
        }
        //工程师能力压缩成二进制数字
        for(int i = 0; i < m; i++)
        {
            B2[i] = 0;
            for(int j = 1; j <= B1[i][0]; j++)
            {
                B2[i] |= (1 << ID[B1[i][j]]);
            }
        }
        //Ve[i]获得完成i工程的所有工程师组合
        vector<int>Ve[15];
        int A2[15]; //工程压缩
        memset(A2, 0, sizeof(A2));

        for(int i = 0; i < n; i++)//枚举工程
        {
            int ok = 1;
            for(int k = 1; k <= A1[i][0]; k++)//处理工程
            {
                if(vis[A1[i][k]])
                {
                    A2[i] |= (1 << ID[A1[i][k]]);
                }
                else //存在没法完成的工作
                {
                    ok = 0;
                    break;
                }
            }
            if(!ok) continue;
            for(int j = 0; j <= (1 << m); j++) //枚举工程师组合
            {
                int p = 0, num = 0;
                for(int k = 0; k <= m; k++)
                {
                    if(j & (1 << k))
                    {
                        num++;
                        p |= B2[k];
                    }
                }
                if(num > 3) continue;
                if((p & A2[i]) == A2[i])
                {
                    Ve[i].push_back(j);
                }
            }
        }
        //dp状态转移
        int dp[15][1 << m];
        for(int i = 0; i < 14; i++)
            memset(dp[i], 0, sizeof(dp[i]));
        for(int i = 0; i < n; i++)
        {
            for(int k = 0; k <= (1 << m); k++)
            {
                for(int j = 0; j < Ve[i].size(); j++)
                {

                    if(!hasnotong(Ve[i][j], k)) continue;
                    if(i)
                    dp[i][Ve[i][j] | k] = max(dp[i][Ve[i][j] | k], dp[i-1][k] + 1);
                    else dp[i][Ve[i][j] | k] = 1;
                }
                if(i)
                    dp[i][k] = max(dp[i][k], dp[i-1][k]);
            }
        }
        int ans = 0;
        for(int i = 0; i < (1 << m); i++)
        {
            ans = max(ans, dp[n-1][i]);
        }
        cout << "Case #" << ca << ": ";
        cout << ans << endl;
    }
    return 0;
}
View Code

第二道例题 World is Exploding HDU - 5792

在一个A[n]数组中找出有多少对 a < b &&  c< d && (a != b != c != d)&& A[a] < A[b] && A[b] > A[d].

这道题需要用到树状数组。

树状数组是一个在数组编辑中快速可以求和的一种数据结构。

百度百科:

树状数组(Binary Indexed Tree(B.I.T), Fenwick Tree)是一个查询和修改复杂度都为log(n)的数据结构。主要用于查询任意两位之间的所有元素之和,但是每次只能修改一个元素的值;经过简单修改可以在log(n)的复杂度下进行范围修改,但是这时只能查询其中一个元素的值(如果加入多个辅助数组则可以实现区间修改与区间查询)。
这种数据结构(算法)并没有C++和Java的库支持,需要自己手动实现。在Competitive Programming的竞赛中被广泛的使用。树状数组和线段树很像,但能用树状数组解决的问题,基本上都能用线段树解决,而线段树能解决的树状数组不一定能解决。相比较而言,树状数组效率要高很多。
 
本体思路:求出有多少ab, 有多少cd,相乘后减去重复的部分。
数据离散化后树状数组,是个神奇的过程,于是我打算,上代码。
#include <iostream>
#include <cstring>
#include <string>
#include <algorithm>
#include <cstdio>
#define LL long long
#define maxn 50005
#define mst(a, b) memset(a, b, sizeof(a))
using namespace std;
int A[maxn], B[maxn];
int lefsmal[maxn];
int lefbig[maxn];
int rigsmal[maxn];
int rigbig[maxn];
int num[maxn];
int m;
int lowbit(int x)
{
    return x & -x;
}
void Add(int x)
{
    while(x <= m)
    {
        num[x]++;
        x += lowbit(x);
    }
}
int GetNum(int x)
{
    int ans = 0;
    while(x > 0)
    {
        ans += num[x];
        x -= lowbit(x);
    }
    return ans;
}
int main()
{
    int n;
    while(scanf("%d", &n) != EOF)
    {
        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;
//        for(int i = 1; i <= n; i++) printf("%d ", B[i]);
//        printf("
");
        for(int i = 1; i <= n; i++)
            {
                A[i] = lower_bound(B+1, B+m, A[i]) - B;
//                printf("A[%d] = %d
", i, A[i]);
            }
        mst(num, 0);
        LL sum1 = 0;
        LL sum2 = 0;
        for(int i = 1; i<= n; i++)
        {
            lefsmal[i] = GetNum(A[i] - 1);
            sum1 += lefsmal[i];
            lefbig[i] = GetNum(m) - GetNum(A[i]);
            Add(A[i]);
        }
        mst(num, 0);
        for(int i = n; i>= 1; i--)
        {
            rigsmal[i] = GetNum(A[i] - 1);
            rigbig[i] = GetNum(m) - GetNum(A[i]);
            sum2 += rigsmal[i];
            Add(A[i]);
        }
        LL ans = sum1 * sum2;
        for(int i = 1; i<= n; i++)
        {
            ans -= lefsmal[i] * rigsmal[i];
            ans -= lefbig[i] * rigbig[i];
            ans -= lefbig[i] * lefsmal[i];
            ans -= rigbig[i] * rigsmal[i];
        }
        cout << ans << endl;
    }
    return 0;
}
View Code
 
 
原文地址:https://www.cnblogs.com/wolf-yasen/p/7488194.html