前言

题目链接:洛谷

这道题有很多做法,但是模拟赛寄了,故记之。

Update on 2024.7.23

修复了一处时间复杂度的笔误。

题意简述

给你两个长为 \(n\) 的序列 \(A\) 和 \(B\),和一个常数 \(c \leq 20\),有 \(q\) 次修改。每次修改后求:

\[\large \sum _ {S \subseteq \lbrace i \rbrace _ {i = 1} ^ {n} \land |S| \geq c} \prod _ {x \in S} A_x \prod _ {x \not \in S} B_x
\]

当然,对 \(10^4 + 7\) 取模。

题目分析

法 \(1\):线段树

先来考虑部分分。很明显的 DP,记 \(f[i][j]\) 表示在前 \(i\) 个中,选出了 \(j\) 个的乘积。转移很朴素,答案就是 \(\sum \limits _ {i = c} ^ {n} f[n][i]\)。发现对于本题,\(j \geq c\) 的状态意义相同,都表示满足了要求。所以不妨将状态压缩一下。可以得到如下转移方程:

\[f[i][j] =
\begin{cases}
f[i - 1][j] \times B_i & \text{ if } j = 0 \\
f[i - 1][j] \times B_i + f[i - 1][j - 1] \times A_i & \text{ if } 0 < j < c\\
f[i - 1][j] \times (A_i + B_i) + f[i - 1][j - 1] \times A_i & \text{ if } j = c
\end{cases}
\]

可以得到 \(30 \%\) 的部分分。考虑优化。这一看就矩阵啊,于是用上线段树优化矩乘,对于一个 \(i\),它的转移如下:

\[\begin{bmatrix}
f_0 & f_1 & \cdots & f_{c - 1} & f_c
\end{bmatrix}

\begin{bmatrix}
B_i & A_i & 0 & \cdots & 0 & 0 \\
0 & B_i & A_i & \cdots & 0 & 0 \\
0 & 0 & B_i & \cdots & 0 & 0 \\
\vdots & \vdots & \vdots & \ddots & A_i & 0 \\
0 & 0 & 0 & 0 & B_i & A_i \\
0 & 0 & 0 & 0 & 0 & A_i + B_i
\end{bmatrix}
\]

可是,\(\Theta(c ^ 3)\) 的复杂度怎么都接受不了啊,所以考虑换一个方向思考。

我们发现,对于一个位置的 \((A_i, B_i)\),它是什么时候加到状态里的不会影响最终结果。换句话说,我们 DP 的顺序没有要求。而且发现两个状态合并也很容易。比如合并 \(a\) 和 \(b\) 到 \(f\) 中,分别用 \(i\) 和 \(j\) 枚举 \(a\) 和 \(b\) 的状态,表示一个里面选出了 \(i\) 个位置,另一个里面选出了 \(j\) 个位置,对 \(f\) 的贡献可以表示为 \(f[\min \lbrace c, i + j \rbrace] \gets f[\min \lbrace c, i + j \rbrace] + a_i \times b_j\)。

发现,合并两个区间,就是线段树的工作。用个线段树维护,单点修改,查询根节点的 \(f[c]\)。果然,学矩阵学傻了。

时间复杂度:\(\Theta(c^2 (n + q \log n))\)。

法 \(2\):线段树分治

结合法 \(1\) 的物品添加顺序不会影响最终结果,并且问题可离线,每个物品出现的时间在时间轴上是连续的一段区间,添加物品很容易,想到线段树分治。没什么好说的了……剩下的就是板子了。

由于每次添加单个物品是 \(\Theta(c)\) 的,时间复杂度:\(\Theta((n + q) c \log q)\)。

法 \(3\):撤销背包

既然物品添加的顺序无关,如果我们想要修改某一个物品,那么把它看做是最后一个添加到状态里的,然后撤销,并增加修改后的物品。至于如何撤销,我们记添加 \((a, b)\) 物品前 DP 状态是 \(g\),添加后是 \(f\),根据我们上文提到的转移方程:

\[f[j] =
\begin{cases}
g[j] \times b & \text{ if } j = 0 \\
g[j] \times b + g[j - 1] \times a & \text{ if } 0 < j < c\\
g[j] \times (a + b) + g[j - 1] \times a & \text{ if } j = c
\end{cases}
\]

移项移一下,得:

\[g[j] =
\begin{cases}
f[j] \times b ^ {-1} & \text{ if } j = 0 \\
(f[j] - g[j - 1] \times a) \times b ^ {-1} & \text{ if } 0 < j < c \\
(f[j] - g[j - 1] \times a) \times (a + b) ^ {-1} & \text{ if } j = c
\end{cases}
\]

然而,如果 \(b\) 或 \(a + b\) 没有逆元,即 \(b \bmod 10^4 + 7 = 0\) 或 \((a + b) \bmod 10^4 + 7 = 0\) 时,我们无法撤销。怎么办呢?

首先,讨论两个东西太恶心了,考虑转化一下,之保留一个可能不存在逆元的数。

发现,\(b\) 是逃不掉的,而 \(a + b\) 是由于我们规定了大于等于 \(c\) 的看做相同。那这时候,为什么不能重新规定,只用计算恰好 \(< c\) 的状态,再用总方案数 \(\prod A_i + B_i\) 减去 \(\sum \limits _ {i < c} f[i]\) 呢?转移方程变为了:

\[f[j] =
\begin{cases}
g[j] \times b & \text{ if } j = 0 \\
g[j] \times b + g[j - 1] \times a & \text{ if } 0 < j < c\\
\end{cases}
\]

即,少了最后一个 \(j = c\) 的转移。同理,移项移一下,得:

\[g[j] =
\begin{cases}
f[j] \times b ^ {-1} & \text{ if } j = 0 \\
(f[j] - g[j - 1] \times a) \times b ^ {-1} & \text{ if } 0 < j < c \\
\end{cases}
\]

现在,我们只要考虑 \(b \bmod 10^4 + 7 = 0\) 的情况。发现如果有很多 \(b\) 都在模意义下为 \(0\),转移后的状态会全是 \(0\),此时,在继续转移下去不会变化。具体地,这个临界值是 \(c\),即如果有超过 \(c\) 个物品的 \(b\) 在模意义下为 \(0\),我们计算的 \(\sum \limits _ {i < c} f[i] = 0\)。而 \(c\) 又很小,我们完全可以在查询前暴力添加到状态中。所以,我们可以尝试用 multiset<> 来记录所有 \(b \bmod 10^4 + 7 = 0\) 的物品。

至于 \(\prod A_i + B_i\) 的撤销会涉及到 \(A_i + B_i\) 没有逆元,这时候,可以用一个 cnt 记录有多少满足的,以及用 tot 记录除了这些的乘积。这样删除的时候,只需要 \(cnt \gets cnt - 1\),而查询的时候,如果存在一个 \((A_i + B_i) \bmod 10^4 + 7 = 0\),我们让总方案数设为 \(0\),反之设为 \(tot\) 即可。

时间复杂度:\(\Theta(nc\log n + qc^2\log n)\)。实际上界很松。

代码

法 \(1\):线段树

法 \(2\):线段树分治

法 \(3\):撤销背包

优化了取模,超过了原来的 rank1。

// #pragma GCC optimize(3)
// #pragma GCC optimize("Ofast", "inline", "-ffast-math")
// #pragma GCC target("avx", "sse2", "sse3", "sse4", "mmx")
#include <cstring>
#include <vector>
#include <cstdio>
#include <set>
using namespace std; namespace ModInt {
using mint = short;
using sint = int; constexpr const mint mod = 1e4 + 7; constexpr inline mint add(const mint a, const mint b) {
return a + b >= mod ? a + b - mod : a + b;
} constexpr inline mint sub(const mint a, const mint b) {
return a - b < 0 ? a - b + mod : a - b;
} constexpr inline mint mul(const mint a, const mint b) {
return (sint(a)) * (sint(b)) % mod;
} constexpr inline mint add(const mint a) { return a; }
constexpr inline mint mul(const mint a) { return a; } template <typename... Types>
constexpr inline mint add(const mint a, const Types... b) {
return add(a, add(b...));
} template <typename... Types>
constexpr inline mint mul(const mint a, const Types... b) {
return mul(a, mul(b...));
} template <typename... Types>
inline mint& toadd(mint &a, Types... b) {
return a = add(a, b...);
} template <typename... Types>
inline mint& tomul(mint &a, Types... b) {
return a = mul(a, b...);
} inline mint& tosub(mint &a, const mint b) {
return a = sub(a, b);
} constexpr inline mint pow(const mint a, const mint p) {
mint res = 1, base = a, b = p;
while (b){
if (b & 1) tomul(res, base);
tomul(base, base), b >>= 1;
}
return res;
} constexpr inline mint inv(const mint a) {
return pow(a, mod - 2);
}
} using namespace ModInt; namespace Fast{
// fread(buf, 1, MAX, stdin)
// fwrite(obuf, 1, o - obuf, stdout) constexpr const int MAX = 1 << 24, yzh_i_love_you = 1314520736; char buf[MAX], *p = buf, obuf[MAX], *o = obuf; #ifdef XuYueming
# define fread(buf, size, n, file)
#else
# define getchar() (*p++)
#endif
#define putchar(c) (*o++ = c) constexpr inline bool isdigit(const char c) { return c >= '0' && c <= '9'; } inline void read(int &x){
x = 0; char c = 0;
for (;!isdigit(c); c = getchar());
for (; isdigit(c); c = getchar()) x = (x << 3) + (x << 1) + (c ^ 48);
} inline void write(int x){
static short Stack[50], top = 0;
do Stack[++top] = x % 10, x /= 10; while (x);
while (top) *o++ = Stack[top--] | 48;
}
} using namespace Fast; int n, m, k;
mint A[100010], B[100010];
multiset<mint> st;
int zero;
mint tot = 1; mint f[22]; inline void modify(int p, mint a, mint b) {
if (!B[p]) st.erase(st.find(A[p]));
else {
mint q = inv(B[p]);
f[0] = mul(f[0], q);
for (int i = 1; i <= k - 1; ++i)
f[i] = mul(q, sub(f[i], mul(f[i - 1], A[p])));
}
if (add(A[p], B[p])) tomul(tot, inv(add(A[p], B[p])));
else --zero; if (!b) st.insert(a);
else {
for (int i = k - 1; i; --i)
f[i] = add(mul(f[i], b), mul(f[i - 1], a));
f[0] = mul(f[0], b);
}
if (add(a, b)) tomul(tot, add(a, b));
else ++zero;
A[p] = a, B[p] = b;
} inline mint query() {
mint res = zero ? 0 : tot;
if (st.empty()) {
for (int i = 0; i <= k - 1; ++i)
tosub(res, f[i]);
} else if ((int)st.size() <= k) {
static mint g[22];
memcpy(g, f, sizeof (mint) * k);
for (const auto& a: st) {
for (int i = k - 1; i; --i)
g[i] = mul(g[i - 1], a);
g[0] = 0;
}
for (int i = 0; i <= k - 1; ++i)
tosub(res, g[i]);
}
return res;
} signed main() {
fread(buf, 1, MAX, stdin);
read(n), read(k);
for (int i = 1, a; i <= n; ++i)
read(a), A[i] = a % mod;
for (int i = 1, b; i <= n; ++i)
read(b), B[i] = b % mod;
read(m), f[0] = 1;
for (int i = 1; i <= n; ++i) {
if (!B[i]) st.insert(A[i]);
else {
for (int j = k - 1; j; --j)
f[j] = add(mul(f[j], B[i]), mul(f[j - 1], A[i]));
f[0] = mul(f[0], B[i]);
}
if (add(A[i], B[i])) tomul(tot, add(A[i], B[i]));
else ++zero;
}
for (int i = 1, p, a, b; i <= m; ++i) {
read(p), read(a), read(b);
modify(p, a % mod, b % mod), write(query()), putchar('\n');
}
fwrite(obuf, 1, o - obuf, stdout);
return 0;
}

[COCI2015-2016#1] RELATIVNOST 题解的更多相关文章

  1. P6666 [清华集训2016] 数据交互 题解

    ## P6666 [清华集训2016] 数据交互 题解 ### 简要题意: n个点的树,m次操作,分别为添加一条路径$(u_i,v_i,w_i)$,和撤消一条路径,每一次操作后求出一条路径使得与这条路 ...

  2. [COCI]coci2015/2016 nekameleoni

    题意: 初始数列,每个数都在1~k以内 支持两种操作:1.修改一个数,修改后的数在1~k内                           2.查询一个最短包含1~k的序列的长度 查询100000 ...

  3. COCI 2015、2016 1st round 题解(官方)

    官方题解: 官方代码: Code-KARTE: #include <cstdio> #include <iostream> #include <cstring> u ...

  4. CODE FESTIVAL 2016 Grand Final 题解

    传送门 越学觉得自己越蠢--这场除了\(A\)之外一道都不会-- \(A\) 贪心从左往右扫,能匹配就匹配就好了 //quming #include<bits/stdc++.h> #def ...

  5. noip 2016 提高组题解

    前几天写的那个纯属搞笑.(额,好吧,其实这个也不怎么正经) 就先说说day2吧: T1:这个东西应该叫做数论吧. 然而我一看到就照着样例在纸上推了大半天(然而还是没有看出来这东西是个杨辉三角) 然后就 ...

  6. NCPC 2016:简单题解

    A .Artwork pro:给定N*M的白色格子,然后Q次黑棒,输出每次加黑棒后白色连通块的数量.(N,M<1e3, Q<1e4) sol:倒着离线做,并查集即可. (在线做法:http ...

  7. 2016 ACM-ICPC EC-Final题解

    题目链接 A. Number Theory Problem 题意:给你一个数N,求形如2k-1且小于2N的数中有多少能被7整除. 解法:观察二进制位找规律,答案是N/3. #include<bi ...

  8. NOIP 2016 组合数问题 题解

    一道sb题目,注意范围,可打表解决,打出杨辉三角,在用前缀和求解即可 代码(一维前缀和) #include<bits/stdc++.h> using namespace std; int ...

  9. CODE FESTIVAL 2016 qual C题解

    传送门 \(A\) 什么玩意儿-- const int N=105; char s[N];int n,f1,f2; int main(){ scanf("%s",s+1),n=st ...

  10. CODE FESTIVAL 2016 qual B题解

    传送门 \(A\) 什么玩意儿-- const char t[]={"0CODEFESTIVAL2016"}; char s[25];int res; int main(){ sc ...

随机推荐

  1. 网站_域名_DNS_端口_web访问过程

    网站基本概念 服务器:能够提供服务器的机器,取决于机器上所安装的服务软件 web服务器:提供web服务(网站访问),需要安装web服务软件,Apache,tomcat,iis等 域名 (Domain ...

  2. Base64编码和解码字符串

    Base64编码和解码字符串 package com.example.core.mydemo.cpic; import org.apache.commons.codec.binary.Base64; ...

  3. 随机二次元图片API上线

    Tips:当你看到这个提示的时候,说明当前的文章是由原emlog博客系统搬迁至此的,文章发布时间已过于久远,编排和内容不一定完整,还请谅解` 随机二次元图片API上线 日期:2017-12-6 阿珏 ...

  4. Windows/Linux 通过 ssh 打开 远程服务器 GUI程序

    背景 在 Windows + ssh(Cygwin) + Linux(运行在虚拟机中的Ubuntu) 是一个很舒服的方案,但是偶尔需要用到 图形界面. 如果需要通过ssh打开远程服务器端的程序,需要X ...

  5. Nuxt3 的生命周期和钩子函数(八)

    title: Nuxt3 的生命周期和钩子函数(八) date: 2024/6/30 updated: 2024/6/30 author: cmdragon excerpt: 摘要:本文介绍了Nuxt ...

  6. C#数据结构与算法入门教程,值得收藏学习!

    前言 最近看到DotNetGuide技术社区交流群有不少小伙伴提问:想要系统化的学习数据结构和算法,不知道该怎么入门,有无好的教程推荐的?,今天大姚给大家推荐2个开源.免费的C#数据结构与算法入门教程 ...

  7. 对于 vue3.0 特性你有什么了解的吗?

    Vue 3.0 的目标是让 Vue 核心变得更小.更快.更强大,因此 Vue 3.0 增加以下这些新特性: (1)监测机制的改变3.0 将带来基于代理 Proxy 的 observer 实现,提供全语 ...

  8. JavaSE 计算2个List集合中的交集、差集、并集、去重并集

    VideoOrder.java 重写里面的equals和hashCode方法 class VideoOrder { private int price; private String title; p ...

  9. yb课堂实战之播放记录表设计和模块开发 《十五》

    play_record表设计 DROP TABLE IF EXISTS `play_record`; CREATE TABLE `play_record` ( `id` int(11) NOT NUL ...

  10. 洛谷P1451

    背景:复习 简单的dfs,也可以说是有点像连通块 #include<iostream> #include<utility> using namespace std; typed ...