C 逆序对大师cwb - 题解
题目大意
给定一个长度为 的正整数数组
,定义一个“逆序对”为一对下标
,满足
且
。对于每一个逆序对
,计算其对应的表达式
。任务是求出所有逆序对对应表达式之和,并将结果对
取模后输出。
解题思路
暴力解法(不可行)
最直观的方法是枚举所有可能的下标对 (
),检查是否满足
。如果满足,则累加
。此方法的时间复杂度为
,对于
的数据规模显然不可行。
优化解法:归并排序思想
我们可以利用归并排序(Merge Sort)来高效地找到所有逆序对。归并排序的核心思想是分治:将数组递归地分成两半,分别排序,然后合并两个有序子数组。在合并过程中,我们可以方便地统计跨越左右两部分的逆序对。
关键观察
在归并排序的合并步骤中,我们有两个已经排序好的子数组 和
(
在左,
在右)。假设
中当前考虑的元素是
,
中当前考虑的元素是
。
- 如果
,那么
不会与
及其右边的任何元素形成逆序对。
- 如果
,那么
会与
以及
中
右边的所有元素形成逆序对。设
的长度为
,当前处理到
,则
会与
个
中的元素构成逆序对。
计算平方和
当我们发现 与
构成逆序对时,我们需要计算
。为了高效计算跨越
和
的所有逆序对的
之和,我们可以利用前缀和。
设 的长度为
,
的长度为
。当处理
中的
且
时,
会与
中从
到末尾的所有元素
(
)形成逆序对。
对于这些逆序对,其 之和可以表示为:
为了快速计算 和
,我们可以在归并前预处理出
数组的后缀和(Suffix Sum)和后缀平方和(Suffix Square Sum)。
算法步骤
- 递归处理:在归并排序的递归函数入口,如果子数组长度大于 1,就递归处理左右两半。
- 预处理:对左右两半分别排序后,合并前,预处理右半部分的后缀和
suffix_sum和后缀平方和suffix_squares。 - 合并与统计:使用双指针
(指向左半) 和
(指向右半) 进行合并。
- 如果
,将
放入临时数组,
++。
- 如果
,说明
与
中从
开始到末尾的所有元素都能形成逆序对。
- 计算当前
贡献的平方和部分:
- 计算交叉项部分:
- 计算
部分的平方和:
- 将这三部分加到全局答案中(记得对
取模)。
- 将
放入临时数组,
++。
- 计算当前
- 如果
- 完成合并:合并完成后,将临时数组内容复制回原数组对应位置。
时间复杂度分析
- 归并排序本身的时间复杂度是
。
- 在每个合并步骤中,预处理后缀和、后缀平方和是
,计算贡献是
每次(利用预处理的后缀和)。总共有
层递归,每层处理
个元素。
- 总体时间复杂度为
。
空间复杂度分析
- 归并排序需要
的临时数组空间。
- 预处理后缀和、后缀平方和需要
的额外空间,但它们可以在合并完成后释放,所以最大空间占用仍然是
。
代码
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define endl "\n"
#define int long long
#define x first
#define y second
using namespace std;
const int mod = 1e9 + 7;
void solve()
{
int n;
cin >> n;
vector<int>a(n + 1, 0);
for (int i = 1; i <= n; i++)cin >> a[i];
int ans = 0;
function<vector<int>(int, int)>merge_sort = [&](int l, int r)->vector<int>
{
if (l > r)return {};
if (l == r)return {a[l]};
int mid = l + r >> 1;
auto la = merge_sort(l, mid);
auto ra = merge_sort(mid + 1, r);
int i = 0, j = 0;
vector<int>t, sa(la.size() + 1, 0), sb(la.size() + 1, 0);
for (int o = 1; o <= la.size(); o++)
{
sa[o] = (sa[o - 1] + la[o - 1]) % mod;
sb[o] = (sb[o - 1] + 1LL * la[o - 1] * la[o - 1] % mod) % mod;
}
while (i < la.size() && j < ra.size())
{
if (la[i] <= ra[j])t.push_back(la[i++]);
else
{
ans = (ans + 1LL * (la.size() - i) % mod * ra[j] % mod * ra[j] % mod) % mod;
ans = (ans + 2LL * ra[j] % mod * ((sa.back() - sa[i] + mod) % mod)) % mod;
ans = (ans + 1LL * (sb.back() - sb[i] + mod) % mod) % mod;
t.push_back(ra[j++]);
}
}
while (i < la.size())t.push_back(la[i++]);
while (j < ra.size())t.push_back(ra[j++]);
return t;
};
merge_sort(1, n);
cout << ans << endl;
}
signed main()
{
IOS
int T = 1;
//cin >> T;
while (T--) solve();
return 0;
}
其它解法-树状数组
#include <bits/stdc++.h>
#define IOS ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
#define endl "\n"
#define int long long
#define x first
#define y second
using namespace std;
const int mod = 1e9 + 7;
typedef struct FenwickTree
{
vector<int>tr;
int n;
FenwickTree() {}
FenwickTree(int n): n(n) {tr.resize(n + 1, 0);}
FenwickTree(vector<int>tr, int n)
{
this->n = n;
this->tr = tr;
}
int lowbit(int x)
{
return x & (-x);
}
void add(int x, int c)
{
for (int i = x; i <= n; i += lowbit(i))tr[i] = (tr[i] + c) % mod;
}
int sum(int x)
{
int ans = 0;
for (int i = x; i; i -= lowbit(i))ans = (ans + tr[i]) % mod;
return ans;
}
} FT;
typedef struct Discretization
{
int n;
vector<int> alls;
Discretization() {}
Discretization(vector<int>all)
{
for (auto x : all)alls.push_back(x);
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
n = alls.size();
}
void init()
{
sort(alls.begin(), alls.end());
alls.erase(unique(alls.begin(), alls.end()), alls.end());
n = alls.size();
}
int find(int x)
{
return lower_bound(alls.begin(), alls.end(), x) - alls.begin() + 1;
}
} DS;
void solve()
{
int n;
cin >> n;
vector<int>a(n + 1, 0);
DS ds;
for (int i = 1; i <= n; i++)cin >> a[i], ds.alls.push_back(a[i]);
ds.init();
FT ft1(ds.n), ft2(ds.n), ft3(ds.n);
int ans = 0;
for (int i = 1; i <= n; i++)
{
int w = ds.find(a[i]);
ft1.add(w, 1);
ft2.add(w, a[i]);
ft3.add(w, a[i] * a[i] % mod);
ans = (ans + (i - ft1.sum(w)) % mod * a[i] % mod * a[i] % mod) % mod;
ans = (ans + 2 * a[i] % mod * (ft2.sum(ds.n) - ft2.sum(w) + mod) % mod) % mod;
ans = (ans + (ft3.sum(ds.n) - ft3.sum(w) + mod) % mod) % mod;
}
cout << ans << endl;
}
signed main()
{
IOS
int T = 1;
//cin >> T;
while (T--) solve();
return 0;
}
H-tjh学长爱玩剧本杀
题目大意
给定张卡牌,每张卡牌可以选择:
- 不调查:耗时 0 ,得分 0;
- 普通表面调查:耗时 t1,得分v;
- 深入调查:耗时t1 + t2,得分2v;
问:在时间T内使用最优解所获得的价值能否超过给定价值V
解题思路
- 通过以上观察可发现该情况是动态规划中典型的01背包问题。
- 每一张卡牌可以看做两个物品:
- 第一个物品:占用空间t1,价值v
- 第二个物品:占用空间t1+t2,价值2v
- 现在问题就可以转换为在背包总容量为T的情况下,如何装这些物品是总价值达到最大。
- 如果对背包问题还不太熟悉或者没有了解过的请移步->[https://www.cnblogs.com/dx123/p/17301748.html]
时间复杂度分析
该题为01背包模板题,时间复杂度即01背包问题时间复杂度:O(N*T)
代码(普通版)
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits> // For INT_MIN
using namespace std;
const int MOD = 1e9 + 7;
int main() {
int N, T, V;
cin >> N >> T >> V;
vector<int> t1(N + 1), t2(N + 1), v(N + 1);
for (int i = 1; i <= N; i++) {
cin >> t1[i] >> t2[i] >> v[i];
}
// dp[i][j]: 考虑前i张卡牌,在时间j内能获得的最大价值
// 初始化为一个极小值,因为可能有些状态无法达到
vector<vector<long long>> dp(N + 1, vector<long long>(T + 1, LLONG_MIN));
// 初始状态:不考虑任何卡牌,时间0,价值0
for (int j = 0; j <= T; j++) {
dp[0][j] = 0;
}
for (int i = 1; i <= N; i++) {
for (int j = 0; j <= T; j++) {
// 选择1: 不调查第i张卡牌
dp[i][j] = dp[i - 1][j];
// 选择2: 表面调查第i张卡牌
if (j >= t1[i] && dp[i - 1][j - t1[i]] != LLONG_MIN) {
dp[i][j] = max(dp[i][j], dp[i - 1][j - t1[i]] + v[i]);
}
// 选择3: 深入调查第i张卡牌
if (j >= t1[i] + t2[i] && dp[i - 1][j - t1[i] - t2[i]] != LLONG_MIN) {
dp[i][j] = max(dp[i][j], dp[i - 1][j - t1[i] - t2[i]] + 2LL * v[i]);
}
}
}
bool possible = false;
for (int j = 0; j <= T; j++) {
if (dp[N][j] >= V) {
possible = true;
break;
}
}
if (possible) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
return 0;
}
代码(优化空间版)
#include <iostream>
#include <vector>
#include <algorithm>
#include <cstring>
using namespace std;
int main() {
int N, T, V;
cin >> N >> T >> V;
vector<int> t1(N), t2(N), v(N);
for (int i = 0; i < N; i++) {
cin >> t1[i] >> t2[i] >> v[i];
}
// 创建DP数组,dp[j]表示使用j小时能获得的最大价值
vector<int> dp(T + 1, 0);
// 动态规划过程
for (int i = 0; i < N; i++) {
// 逆序遍历时间,确保每个线索只被考虑一次
for (int j = T; j >= 0; j--) {
// 选择表面调查当前线索
if (j >= t1[i]) {
dp[j] = max(dp[j], dp[j - t1[i]] + v[i]);
}
// 选择深入调查当前线索(需要先完成表面调查)
if (j >= t1[i] + t2[i]) {
dp[j] = max(dp[j], dp[j - t1[i] - t2[i]] + 2 * v[i]);
}
}
}
// 检查是否能在时间限制内达到目标价值
int max_value = 0;
for (int j = 0; j <= T; j++) {
max_value = max(max_value, dp[j]);
}
// 输出结果
if (max_value >= V) {
cout << "YES" << endl;
} else {
cout << "NO" << endl;
}
return 0;
}
L-找平年
#include <bits/stdc++.h>
#define int long long
using namespace std;
const int mod = 1e9 + 7;
int solve(string s) {
vector<vector<int> > dp(5, vector<int>(400, 0)) ;
dp[0][0] = 1;
for (auto c : s) {
int num = c - '0';
for (int i = 4; i >= 1; i--) {
for (int j = 0; j < 400; j++) {
dp[i][(j * 10 + num) % 400] = (dp[i - 1][j] + dp[i][(j * 10 + num) % 400]) %mod;
}
}
}
int res = 0;
for (int i = 1; i < 400; i++) {
if(i%4 || i%100==0)res = (res + dp[4][i]) % mod;
}
return res % mod;
}
signed main() {
// 禁用同步以加速I/O
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
string s;
getline(cin, s);
//cout<<s<<endl;
int result = solve(s);
cout << result << endl;
return 0;
}
总体思路
我们设s是给出的字符串,我们需要从中找到子序列t满足是平年,设置dp数组储存结果 dp[位数][mod 400的结果]
当s[i]中出现了符合条件的t[j]符合条件的,

京公网安备 11010502036488号