hdu 5792 离散化+树状数组

本文介绍了一种解决特定组合计数问题的高效算法。针对给定整数序列,利用离散化与树状数组技术预处理数据,有效计算满足特定顺序及大小关系的四元组数量。



链接:戳这里


World is Exploding

Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)

Problem Description
Given a sequence A with length n,count how many quadruple (a,b,c,d) satisfies: a≠b≠c≠d,1≤a<b≤n,1≤c<d≤n,Aa<Ab,Ac>Ad.
 
Input
The input consists of multiple test cases. 
Each test case begin with an integer n in a single line.

The next line contains n integers A1,A2⋯An.
1≤n≤50000
0≤Ai≤1e9
 
Output
For each test case,output a line contains an integer.
 
Sample Input
4
2 4 1 3
4
1 2 3 4
 
Sample Output
1
0


题意:

给出一个长度为n的整数序列,要求输出有多少个四元组{a,b,c,d}。

满足1<=a<b<=n,1<=c<d<=n,a!=b!=c!=d 且 Va<Vb && Vc>Vd


思路:

预处理出mn[i],mx[i],hmn[i],hmx[i]

mn[i]:[1,i-1]之间有多少个数比ai小

mx[i]:[1,i-1]之间有多少个数比ai大

hmn[i]:[i+1,n]之间有多少个数比ai小

hmx[i]:[i+1,n]之间有多少个数比ai大

sum[i]:表示以当前第i个数结尾,有多少对a,b满足条件。也就是前缀和mn[i]

这些都可以通过离散化+树状数组实现

接下来枚举当前ai作为Vd,那么就存在mx[i]个Vc,以及sum[n]对Va,Vb。ans+=mx[i]*sum[n]

由于答案会多算进去a=c || a=d || b=c || b=d的情况,那么枚举这四种情况减去就可以了(a=c那么必定b!=d,同理其他

a=c:ans-=hmn[i]*hmx[i]

a=d:ans-=mx[i]*hmx[i]

b=c:ans-=mn[i]*hmn[i]

b=d:ans-=mx[i]*mn[i]


代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<string>
#include<vector>
#include <ctime>
#include<queue>
#include<set>
#include<map>
#include<stack>
#include<iomanip>
#include<cmath>
#include<bitset>
#define mst(ss,b) memset((ss),(b),sizeof(ss))
///#pragma comment(linker, "/STACK:102400000,102400000")
typedef long long ll;
typedef long double ld;
#define INF (1ll<<60)-1
#define Max 1e9
using namespace std;
int n,m;
int a[50010],b[50010];
int num[50010];
int mn[50010],mx[50010];   /// 分别表示[1,i-1]有多少个数比ai小  大
int hmn[50010],hmx[50010]; /// 分别表示[i+1,n]有多少个数比ai小  大
int lowbit(int x){
    return x&-x;
}
void Add(int x){
    while(x<=m) {
        num[x]++;
        x+=lowbit(x);
    }
}
int query(int x){
    int ans=0;
    while(x>=1){
        ans+=num[x];
        x-=lowbit(x);
    }
    return ans;
}
ll sum[50010];
int main(){
    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+1);
        for(int i=1;i<=n;i++) a[i]=lower_bound(b+1,b+m+1,a[i])-b;
        mst(num,0);
        mst(sum,0);
        for(int i=1;i<=n;i++){
            mn[i]=query(a[i]-1);
            mx[i]=query(m)-query(a[i]);
            sum[i]=sum[i-1]+mn[i]*1LL;
            Add(a[i]);
            //printf("%d %d\n",mn[i],mx[i]);
        }
        mst(num,0);
        for(int i=n;i>=1;i--){
            hmn[i]=query(a[i]-1);
            hmx[i]=query(m)-query(a[i]);
            Add(a[i]);
            //printf("%d %d\n",hmn[i],hmx[i]);
        }
        ll ans=0;
        for(int i=1;i<=n;i++){
            ll num1=1LL*mx[i];
            ll num2=1LL*sum[n];
            ans+=num1*num2;
        }
        /// a=c b!=d
        for(int i=1;i<=n;i++){
            ll num1=hmn[i];
            ll num2=hmx[i];
            ans-=num1*num2;
        }
        /// a=d b!=c
        for(int i=1;i<=n;i++){
            ll num1=mx[i];
            ll num2=hmx[i];
            ans-=num1*num2;
        }
        /// b=c a!=d
        for(int i=1;i<=n;i++){
            ll num1=mn[i];
            ll num2=hmn[i];
            ans-=num1*num2;
        }
        /// b=d a!=c
        for(int i=1;i<=n;i++){
            ll num1=mx[i];
            ll num2=mn[i];
            ans-=num1*num2;
        }
        printf("%I64d\n",ans);
    }
    return 0;
}



### 关于“粉刷树”的概念 “粉刷树”并不是一种标准的数据结构名称,但在某些特定场景中可能被用来描述某种算法模型或问题背景。根据已有的引用内容[^3]以及常见的算法竞赛题目,“粉刷树”通常可以理解为一类涉及 **动态规划 (Dynamic Programming)** 和 **图论** 的问题。 #### 动态规划视角下的“粉刷树” 在一些经典的算法题中(如剑指 Offer II 091 或 LeetCode 上类似的题目),涉及到房屋涂色的问题可以通过构建一棵虚拟的决策树来表示状态转移过程。这种树被称为“粉刷树”,其核心思想如下: - 每个节点代表当前状态下的一种选择。 - 节点之间的边表示从一个状态转移到另一个状态的过程。 - 使用动态规划的思想,在每个节点上记录最优解的状态值,并通过递推关系逐步更新到根节点。 具体来说,对于给定的房子数量 $ n $ 和可用的颜色种类 $ k $,定义三维 DP 数组 `dp[i][j][c]` 表示前 $ i $ 座房子中最后一座房子选择了颜色 $ c $ 并满足条件时所需的最小代价。最终答案即为所有合法路径上的最小值。 以下是实现上述逻辑的一个 Python 示例代码片段: ```python def minCost(costs): if not costs: return 0 dp = [[float('inf')] * 3 for _ in range(len(costs))] # 初始化第一个房子的成本 for j in range(3): dp[0][j] = costs[0][j] # 进行动态规划迭代 for i in range(1, len(costs)): for j in range(3): dp[i][j] = costs[i][j] + min(dp[i-1][(j+1)%3], dp[i-1][(j+2)%3]) return min(dp[-1]) # 返回最后一个房子三种颜色中的最小成本 ``` 此代码实现了基于二维数组的动态规划方法解决 “粉刷房子” 类型的问题。其中每一层对应着不同阶段的选择情况,从而形成了一棵隐式的“粉刷树”。 --- ### 图论视角下的“粉刷树” 另一种解释是从实际应用出发。“粉刷树”也可能特指某类特殊的加权无向图或者有向图,这些图形经过特殊处理后呈现出类似于树状拓扑特征。例如 HDU OJ 中提到的一道关于电话号码簿管理的问题 [^4] 就采用了字典树(Trie Tree)作为基础框架并加以扩展优化。虽然严格意义上这不是传统意义上的“粉刷树”,但它体现了如何利用树形结构解决问题的理念延伸。 --- ### 总结 综上所述,“粉刷树”更多时候是一种形象化的称呼而非正式术语。它可以泛指任何借助树形结构表达复杂组合可能性的情境;既可以体现在离散数学领域内的经典动态规划案例里,也可以出现在工程实践中针对字符串匹配等问题设计出来的高效解决方案之中。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值