题意:给定数轴上 [ 1 , n ] [1,n] [1,n] 区间(每个整点抽象成一个格子),有以下四种操作:
操作次数 q ≤ 1 × 1 0 5 q \leq 1\times 10^5 q≤1×105, 1 ≤ n ≤ 1 × 1 0 9 1\leq n \leq 1\times 10^9 1≤n≤1×109,强制在线。
解法:区间平推想到珂朵莉树,对同一颜色进行修改可以对每个颜色维护一个权值修改的时间戳前缀和序列。此题 https://codeforces.com/contest/1638/problem/E 为本题的一个简化版本。
考虑维护一颗珂朵莉树来表示当前 [ 1 , n ] [1,n] [1,n] 序列的颜色。有两种珂朵莉树写法:第一个对每个颜色单独考虑,维护 ( l , r , c ) (l,r,c) (l,r,c) 表示起始、终止、颜色;另一种写法更加简单:维护一个 map
,记录每个区间的起始点和颜色二元组 ( l , c ) (l,c) (l,c),区间长度由 *next(l)-1
决定。对于一二两个操作,均可在珂朵莉树上实现平推操作并合并区间。
由于存在给颜色加权值操作,因而维护修改权值的时间戳,每次要进行区间合并的时候,将该区间上的数字进行区间加法。注意:此时珂朵莉树上可能为一个区间,但是真实可能为多个区间,因而不可暴力修改,需要利用动态开点线段树进行区间修改。第四个即是在线段树上进行查询。
对于单个操作时间复杂度摊还 O ( log n ) \mathcal O(\log n) O(logn),总复杂度 O ( q log n ) \mathcal O(q \log n) O(qlogn)。
#include
using namespace std;
const int N = 100000, inf = 0x3f3f3f3f;
int n;
class segment_tree
{
struct node
{
int l, r;
long long sum;
node()
{
l = r = sum = 0;
}
} tr[N * 40], NIL;
int root, cnt;
void node(int &place)
{
place = ++cnt;
tr[place] = NIL;
}
void update(int &place, int left, int right, int start, int end, long long w)
{
if (!place)
node(place);
if (start <= left && right <= end)
{
tr[place].sum += w;
return;
}
int mid = (left + right) >> 1;
if (start <= mid)
update(tr[place].l, left, mid, start, end, w);
if (end > mid)
update(tr[place].r, mid + 1, right, start, end, w);
}
long long query(int &place, int left, int right, int x)
{
if (!place)
return 0;
int mid = (left + right) >> 1;
long long ans = tr[place].sum;
if (x <= mid)
return ans + query(tr[place].l, left, mid, x);
else
return ans + query(tr[place].r, mid + 1, right, x);
}
public:
void update(int left, int right, long long w)
{
update(root, 1, n, left, right, w);
}
long long query(int pos)
{
return query(root, 1, n, pos);
}
void clear()
{
root = cnt = 0;
}
} T;
class Ctholly
{
public:
map<int, int> p;//p[x]=color
map<int, int>::iterator find(int pos)
{
return prev(p.upper_bound(pos));
}
void spilt(int pos)
{
auto it = find(pos);
p[pos] = it->second;
}
};
int main()
{
int caset, q, op;
long long w;
scanf("%d", &caset);
while(caset--)
{
scanf("%d%d", &n, &q);
vector<long long> add(q + 1, 0);
T.clear();
Ctholly t;
t.p[0] = t.p[n + 1] = -1;
t.p[1] = 0;
int lastans = 0, x, y, c;
for (int o = 1; o <= q;o++)
{
scanf("%d", &op);
if (op == 1)
{
scanf("%d%d", &x, &c);
x = ((x - 1) ^ lastans) % n + 1;
c = ((c - 1) ^ lastans) % ((n - 1) / 2) + 1;
int l = max(1, x - c), r = l + 2 * c;
if (r > n)
l = n - 2 * c, r = n;
t.spilt(l);
t.spilt(r + 1);
for (auto i = t.find(l); i->first != r + 1; i = t.p.erase(i))
T.update(i->first, next(i)->first - 1, add[i->second]);
t.p[l] = o;
}
else if (op == 2)
{
scanf("%d%d", &x, &y);
x = ((x - 1) ^ lastans) % n + 1;
y = ((y - 1) ^ lastans) % n + 1;
auto i = t.find(x), j = t.find(y);
if (i->second == j->second)
continue;
T.update(j->first, next(j)->first - 1, add[j->second] - add[i->second]);
j->second = i->second;
while (j->second == next(j)->second)
t.p.erase(next(j));
while (j->second == prev(j)->second)
{
j = prev(j);
t.p.erase(next(j));
}
}
else if (op == 3)
{
scanf("%d%lld", &x, &w);
x = ((x - 1) ^ lastans) % n + 1;
add[t.find(x)->second] += w;
}
else
{
scanf("%d", &x);
x = ((x - 1) ^ lastans) % n + 1;
long long ans = T.query(x) + add[t.find(x)->second];
printf("%lld\n", ans);
lastans = ans & 1073741823;
}
}
}
return 0;
}
题意:定义一个数字 n n n 的 Good 集合为 { d ∣ d ∣ n , ∀ p , p ∣ n ↔ p ∣ d } \{d|d|n,\forall p,p|n \leftrightarrow p|d\} {d∣d∣n,∀p,p∣n↔p∣d},对于 ∀ i ∈ [ 1 , n ] \forall i \in [1,n] ∀i∈[1,n],从其 Good 集合中选出一个数字恰好为自己的概率为 p i p_i pi,求 ∑ i = 1 n i p i \displaystyle \sum_{i=1}^n ip_i i=1∑nipi。 n ≤ 1 × 1 0 12 n \leq 1\times 10^{12} n≤1×1012。
解法:对于此类 n n n 约为 1 × 1 0 12 1\times 10^{12} 1×1012 数量级的数论题,通常考虑杜教筛或 min_25 筛。
容易注意到对于数 N = ∏ i = 1 k N p N , i α N , i \displaystyle N=\prod_{i=1}^{k_N} p_{N,i}^{\alpha_{N,i}} N=i=1∏kNpN,iαN,i,其对答案的贡献为 f ( N ) = N ∏ i = 1 k N α N , i f(N)=\dfrac{N}{\prod_{i=1}^{k_N} \alpha_{N,i}} f(N)=∏i=1kNαN,iN,因而答案为:
1 M ∑ i = 1 M i ∏ j = 1 k i α i , j \dfrac{1}{M}\sum_{i=1}^M \dfrac{i}{\prod_{j=1}^{k_i} \alpha_{i,j}} M1i=1∑M∏j=1kiαi,ji
注意到 f f f 是有积性的,因而考虑配凑积性函数 g , h g,h g,h 使得 f = h ∗ g f=h*g f=h∗g,其中 ∗ * ∗ 为迪利克雷卷积运算。对于 f ( n ) = ∑ d ∣ n h ( n d ) g ( d ) \displaystyle f(n)=\sum_{d|n}h\left(\dfrac{n}{d}\right)g(d) f(n)=d∣n∑h(dn)g(d),首先对于质数 f ( p ) = p f(p)=p f(p)=p,因而可以设 g ( n ) = n g(n)=n g(n)=n。对于 f ( p k ) = p k k f(p^k)=\dfrac{p^k}{k} f(pk)=kpk, f ( p k ) = p k k = ∑ i = 0 k h ( p i ) g ( p k − i ) = ∑ i = 0 k h ( p i ) p k − i \displaystyle f(p^k)=\dfrac{p^k}{k}=\sum_{i=0}^k h\left(p^{i}\right)g(p^{k-i})=\sum_{i=0}^kh(p^i)p^{k-i} f(pk)=kpk=i=0∑kh(pi)g(pk−i)=i=0∑kh(pi)pk−i。因而有 ∑ i = 0 k h ( p i ) p i = 1 k \displaystyle \sum_{i=0}^k \dfrac{h(p^i)}{p^i}=\dfrac{1}{k} i=0∑kpih(pi)=k1。设 h ( p i ) p i = a i \dfrac{h(p^i)}{p^i}=a_i pih(pi)=ai,则有 S k = ∑ i = 0 k a i = 1 k \displaystyle S_k=\sum_{i=0}^ka_i=\dfrac{1}{k} Sk=i=0∑kai=k1。
当 i > 1 i>1 i>1 时, a i = S i − S i − 1 = 1 i − 1 i − 1 = − 1 i ( i − 1 ) a_i=S_{i}-S_{i-1}=\dfrac{1}{i}-\dfrac{1}{i-1}=-\dfrac{1}{i(i-1)} ai=Si−Si−1=i1−i−11=−i(i−1)1,因而 h ( p i ) = − p i i ( i − 1 ) h(p^i)=-\dfrac{p^i}{i(i-1)} h(pi)=−i(i−1)pi。对于 i = 1 i=1 i=1 的情况,考虑 f ( p ) = h ( p ) g ( 1 ) + h ( 1 ) g ( p ) = h ( p ) + p h ( 1 ) = p f(p)=h(p)g(1)+h(1)g(p)=h(p)+ph(1)=p f(p)=h(p)g(1)+h(1)g(p)=h(p)+ph(1)=p,又 h ( 1 ) h(1) h(1) 由积性函数定义必须为 1 1 1,因而 h ( p ) = 0 h(p)=0 h(p)=0。
考虑利用 g , h g,h g,h 如何快速计算:
∑ i = 1 N f ( i ) = ∑ i = 1 N ∑ j ∣ i i j h ( j ) = ∑ j = 1 N ∑ k = 1 ⌊ N j ⌋ k h ( j ) = ∑ j = 1 N h ( j ) ( ∑ k = 1 ⌊ N j ⌋ k ) = 1 2 ∑ j = 1 N h ( j ) ( ⌊ N j ⌋ + 1 ) ⌊ N j ⌋ \begin{aligned} \sum_{i=1}^Nf(i)=&\sum_{i=1}^N\sum_{j|i}\dfrac{i}{j}h(j)\\ =&\sum_{j=1}^N\sum_{k=1}^{\lfloor \frac{N}{j}\rfloor}kh(j)\\ =&\sum_{j=1}^N h(j)\left(\sum_{k=1}^{\lfloor \frac{N}{j}\rfloor}k\right)\\\ =&\dfrac{1}{2}\sum_{j=1}^Nh(j)\left(\left\lfloor\dfrac{N}{j} \right\rfloor+1\right)\left\lfloor\dfrac{N}{j} \right\rfloor \end{aligned} i=1∑Nf(i)=== =i=1∑Nj∣i∑jih(j)j=1∑Nk=1∑⌊jN⌋kh(j)j=1∑Nh(j)⎝ ⎛k=1∑⌊jN⌋k⎠ ⎞21j=1∑Nh(j)(⌊jN⌋+1)⌊jN⌋
对于 h ( j ) h(j) h(j) 的计算,由上面的推导可以得到,只有当 j j j 为一 Powerful Number (PN)即每个质因子次数至少为 2 2 2 的数字时才有值。而 PN 的个数仅为 O ( N ) O\left(\sqrt N\right) O(N),因而使用 PN 筛可以在 O ( N ) O\left(\sqrt N\right) O(N) 的时间内通过。
此题中由于 h ( x ) h(x) h(x) 公式已知,因而可以考虑暴力枚举每个质因子的指数。
#include
using namespace std;
const int N = 1000000;
int prime[N + 5], tot;
bool vis[N + 5];
long long invi[64];
const long long mod = 29ll << 57 | 1;
void sieve(int n)
{
for (int i = 2; i <= n;i++)
{
if (!vis[i])
prime[++tot] = i;
for (int j = 1; j <= tot && (long long)prime[j] * i <= n;j++)
{
int num = prime[j] * i;
vis[num] = 1;
if (i % prime[j] == 0)
break;
}
}
}
long long power(long long a, long long x)
{
long long ans = 1;
while(x)
{
if (x & 1)
ans = (__int128_t)ans * a % mod;
a = (__int128_t)a * a % mod;
x >>= 1;
}
return ans;
}
long long inv(long long a)
{
return power(a, mod - 2);
}
long long calh(long long p, long long k, long long pk)
{
return mod - (__int128_t)pk * invi[k] % mod * invi[k - 1] % mod;
}
long long PN(int prime_pos, long long upper, long long base)//base利用h的积性,新的h(x*p^k)=h(x)*h(p^k)
{
long long ans = (__int128_t)upper * (upper + 1) % mod * invi[2] % mod * base % mod;
for (int i = prime_pos; i <= tot; i++)//枚举当前的新质因子p^k
{
int k = 1;
long long now = upper / prime[i];
long long pk = prime[i];
if (now < prime[i])
break;
while (now >= prime[i])
{
k++;
now /= prime[i];
pk *= prime[i];
ans = (ans + PN(i + 1, now, (__int128_t)base * calh(prime[i], k, pk) % mod)) % mod;
}
}
return ans;
}
int main()
{
sieve(N);
for (int i = 1; i < 64; i++)
invi[i] = inv(i);
int t;
long long m;
scanf("%d", &t);
while(t--)
{
scanf("%lld", &m);
long long ans = PN(1, m, 1) * (__int128_t)inv(m) % mod;
printf("%lld\n", ans);
}
return 0;
}
题意:给定一棵 n n n 个节点的 1 1 1 为根的树,走树边代价为 w w w,祖孙方向深度差为 k k k 的一对节点之间走的代价为 v v v,求从 s s s 到 t t t 的最小代价。 n ≤ 1 × 1 0 6 n \leq 1\times 10^6 n≤1×106。
解法:树边直接建,同时根据树深度 m m m 建立 m m m 个虚点 ( n + i ) (n+i) (n+i),对于第 i i i 层节点,深度为 d e p i {\rm dep}_i depi,单向连到 n + d e p i ± k n + {\rm dep}_i \pm k n+depi±k,边权为 v v v; n + i n+i n+i 单向连到深度为 i i i 的所有节点,边权为 0 0 0。之后跑单源最短路即可。
#include
#define fp(i, a, b) for (int i = a, i##_ = (b) + 1; i < i##_; ++i)
using namespace std;
const int N = 2e6 + 5;
using ll = int64_t;
const ll Inf = 1e18;
int n, m, k, P, S, T, dep[N];
ll dis[N];
struct Edge { int v, w; };
vector<Edge> G[N];
void dfs(int u, int p) {
m = max(m, dep[u] = dep[p] + 1);
for (auto e : G[u])
if (e.v != p)
dfs(e.v, u);
}
void dij() {
priority_queue<pair<ll, int>> q;
fp(i, 1, n + m) dis[i] = Inf;
q.push({dis[S] = 0, S});
vector<int> vis(n + m + 1);
while (!q.empty()) {
int u = q.top().second, v; q.pop();
if (vis[u]) continue;
vis[u] = 1;
for (auto e : G[u])
if (dis[v = e.v] > dis[u] + e.w)
q.push({-(dis[v] = dis[u] + e.w), v});
}
}
void add(int u, int v, int w) {
G[u].push_back({v, w});
G[v].push_back({u, w});
}
void Solve() {
scanf("%d", &n), m = 0;
for (int i = 1, u, v, w; i < n; ++i)
scanf("%d%d%d", &u, &v, &w), add(u, v, w);
scanf("%d%d%d%d", &k, &P, &S, &T);
dfs(1, 0);
fp(i, 1, n) {
if (dep[i] > k) G[i].push_back({n + dep[i] - k, P});
if (dep[i] <= m - k) G[i].push_back({n + dep[i] + k, P});
G[n + dep[i]].push_back({i, 0});
}
fp(i, n + 1, n + m - k) add(i, i + k, P);
dij();
printf("%lld\n", dis[T]);
fp(i, 1, n + m) G[i].clear();
}
int main() {
int t = 1;
scanf("%d", &t);
while (t--) Solve();
return 0;
}
题意:给定平面 m m m 个矩形建筑物(边平行于坐标轴)和 n n n 个观测点,从中选出最少的观测点个数,使得每个观测点可以被其他选中的观测点看到,且能无阻挡的看到每个建筑物的四个角。 n ≤ 20 n \leq 20 n≤20, m ≤ 100 m \leq 100 m≤100。
解法: n ≤ 20 n \leq 20 n≤20 考虑状压。预处理出每个观测点能看到的其他观测点和建筑物的四角,直接暴力判断围成建筑物的四条线段是否会挡住当前的视野。然后暴力枚举每一种选择方案,使用 bitset
维护可看到这一信息即可。复杂度 O ( n m 2 + n 2 m + 2 n n m / w ) \mathcal O(nm^2+n^2m+2^nnm/w) O(nm2+n2m+2nnm/w)。
#include
#define fp(i, a, b) for (int i = a, i##_ = (b) + 1; i < i##_; ++i)
#define fd(i, a, b) for (int i = a, i##_ = (b) - 1; i > i##_; --i)
using namespace std;
using T = int64_t;
const T eps = 1e-8, pi = acos(-1);
int sgn(T x) { return (x > eps) - (x < -eps); }
struct Vec {
T x, y;
bool operator<(Vec p) const { return tie(x, y) < tie(p.x, p.y); }
bool operator==(Vec p) const { return tie(x, y) == tie(p.x, p.y); }
Vec operator+(Vec p) const { return {x + p.x, y + p.y}; }
Vec operator-(Vec p) const { return {x - p.x, y - p.y}; }
Vec operator*(T d) const { return {x * d, y * d}; }
T operator*(Vec p) const { return x * p.x + y * p.y; }
T cross(Vec p) const { return x * p.y - y * p.x; }
T cross(Vec a, Vec b) const { return (a - *this).cross(b - *this); }
int half() const { return y > 0 || (y == 0 && x > 0) ? 1 : -1; }
int onLeft(Vec p) const { return sgn(cross(p)); }
};
struct Line {
Vec p, v; // point on line, direction vector
int onLeft(Vec a) const { return v.onLeft(a - p); }
};
struct Seg {
Vec a, b; // endpoint of line segment
int on(Vec p) const { return p == a || p == b || (p.cross(a, b) == 0 && (p - a) * (p - b) < -eps); }
bool inter(const Seg &s) const {
const Line u{a, b - a}, v{s.a, s.b - s.a};
if (u.onLeft(s.a) * u.onLeft(s.b) == -1 && v.onLeft(a) * v.onLeft(b) == -1)
return 1;
if (on(s.a)) return 1;
if (on(s.b)) return 1;
if (s.on(a)) return 1;
if (s.on(b)) return 1;
return 0;
}
};
const int N = 1 << 20, M = 400;
using bits = bitset<M>;
int n, m;
void Solve() {
scanf("%d%d", &n, &m), m *= 4;
vector<int> see(n);
vector<bits> f(n);
vector<Vec> a(n), b(m);
vector<Seg> s(m);
for (auto &p : a) scanf("%lld%lld", &p.x, &p.y);
for (int i = 0; i < m; i += 4) {
fp(j, i, i + 3) scanf("%lld%lld", &b[j].x, &b[j].y);
fp(j, i, i + 2) s[j] = {b[j], b[j + 1]};
s[i + 3] = {b[i + 3], b[i]};
}
auto check = [&](Seg t) {
for (auto &p : s)
if (!(t.b == p.a || t.b == p.b) && t.inter(p))
return 0;
return 1;
};
fp(i, 0, n - 1) fp(j, 0, m - 1)
f[i][j] = check({a[i], b[j]});
fp(i, 0, n - 1) fp(j, 0, n - 1) if (i != j)
see[i] |= check({a[i], a[j]}) ? 1 << j : 0;
int ans = n + 1, sz;
bits t;
vector<int> val(n);
fp(k, 1, (1 << n) - 1) {
sz = 0, t = 0;
fp(i, 0, n - 1)
if (k >> i & 1) t |= f[i], val[sz++] = i;
if (t.count() != m) continue;
int ok = 1;
fp(i, 0, sz - 1)
ok &= any_of(val.begin(), val.begin() + sz, [&](int j) { return see[j] >> val[i] & 1; });
if (ok) ans = min(ans, __builtin_popcount(k));
}
if (ans <= n) printf("%d\n", ans);
else puts("No Solution!");
}
int main() {
int t = 1;
scanf("%d", &t);
while (t--) Solve();
return 0;
}
题意:给定一个长度为 n n n 的字符串 S S S,每次可以修改、删除、插入一个字符,问将该串变为长度为 4 4 4 的倍数,对于 ∀ i ∈ [ 1 , n ′ 4 ] \forall i \in \left[1,\dfrac{n'}{4}\right] ∀i∈[1,4n′], S 4 i + 1 = S 4 i + 4 S_{4i+1}=S_{4i+4} S4i+1=S4i+4, S 4 i + 2 = S 4 i + 3 S_{4i+2}=S_{4i+3} S4i+2=S4i+3 最少要进行多少次操作,其中 n ’ n’ n’ 为修改后的长度。 n ≤ 1 × 1 0 6 n \leq 1\times 10^6 n≤1×106。
解法:显然不会将超过 8 8 8 个划为一组——对于 n ( n ≥ 8 ) n(n \geq 8) n(n≥8) 个,可以删除后面的 n − 8 n-8 n−8 个,剩下分成两组,每组只需要 2 2 2 次(直接修改)即可完全修改完成。而直接分为一组,为了删除就得要 n − 4 n-4 n−4 次,显然是不优的。因而考虑最多往后延申 1 − 7 1-7 1−7 个字符组成一组的方案。
考虑以下若干种转移:
因而对于这五种情况进行转移即可。总复杂度 O ( n ) \mathcal O(n) O(n)。
#include
using namespace std;
const int N = 1000000, inf = 0x3f3f3f3f;
char s[N + 5];
int f[N + 5];
int main()
{
memset(f, 0x3f, sizeof(f));
int t, n;
scanf("%d", &t);
while(t--)
{
scanf("%s", s + 1);
int n = strlen(s + 1);
f[0] = 0;
for (int i = 1; i <= n;i++)
f[i] = inf;
vector<int> cnt(26, 0);
for (int i = 1; i <= n;i++)
{
f[i] = min(f[i], f[i - 1] + 1);
bool pair = 0;
if (i + 1 <= n)
{
f[i + 1] = min(f[i + 1], f[i - 1] + 2);
cnt[s[i + 1] - 97]++;
if (cnt[s[i + 1] - 97] >= 2)
pair = 1;
}
if (i + 2 <= n)
{
if (s[i] == s[i + 1] || s[i + 1] == s[i + 2] || s[i] == s[i + 2])
f[i + 2] = min(f[i + 2], f[i - 1] + 1);
else
f[i + 2] = min(f[i + 2], f[i - 1] + 2);
cnt[s[i + 2] - 97]++;
if (cnt[s[i + 2] - 97] >= 2)
pair = 1;
}
if (i + 3 <= n)
{
int suc = 0;
if (s[i] == s[i + 3])
suc++;
if (s[i + 1] == s[i + 2])
suc++;
f[i + 3] = min(f[i + 3], f[i - 1] + 2 - suc);
cnt[s[i + 3] - 97]++;
if (cnt[s[i + 3] - 97] >= 2)
pair = 1;
}
for (int j = i + 4; j <= n && j <= i + 6;j++)//保留外侧的A..A
{
int add = (s[i] != s[j]);
if(!pair)
add++;
f[j] = min(f[j], f[i - 1] + j - i - 3 + add);
cnt[s[j] - 97]++;
if (cnt[s[j] - 97] >= 2)//统计了非AA的相同成对的,必然是夹在AA之间,可以保留
pair = 1;
}
for (int j = i + 1; j <= n && j <= i + 6; j++)
cnt[s[j] - 97]--;
pair = 0;
for (int j = i; j <= n && j <= i + 6; j++)//保留内侧的.BB.
{
cnt[s[j] - 97]++;
if (cnt[s[j] - 97] >= 2)//统计了可以成对的BB
pair = 1;
if (j >= i + 4 && pair)
f[j] = min(f[j], f[i - 1] + j - i - 1);
}
for (int j = i; j <= n && j <= i + 6;j++)
cnt[s[j] - 97]--;
}
printf("%d\n", f[n]);
}
return 0;
}
题意:给定长度为 n n n 的排列 { p n } \{p_n\} {pn},找到 { 1 , 2 , ⋯ , n } \{1,2,\cdots,n\} {1,2,⋯,n} 的子集 S S S 的个数,使得 ∣ S ∣ = k |S|=k ∣S∣=k 且 P ( S ) ∩ S = ϕ P(S) \cap S=\phi P(S)∩S=ϕ,其中 P ( S ) = { p i ∣ i ∈ S } P(S)=\{p_i|i \in S\} P(S)={pi∣i∈S}。 n ≤ 5 × 1 0 5 n \leq 5\times 10^5 n≤5×105。
解法:首先将排列分成若干个置换环,每个换上选一些不相邻的数字。对于一个大小为 m m m 的置换环,其上选择 x x x 个数字的方案为 g m , x = ( m − x + 1 k ) − ( m − x − 1 k − 2 ) g_{m,x}=\displaystyle {m-x+1 \choose k}-{m-x-1 \choose k-2} gm,x=(km−x+1)−(k−2m−x−1)。其含义为:首先从 1 , m 1,m 1,m 处断环,然后对于要选的数字,将其与下一个数字绑定;不选的数字仍然是一个,对于最后一个数字需要额外添加一个,因而是 m − x + 1 m-x+1 m−x+1 个数中选择 k k k 个;若同时选择 1 , m 1,m 1,m,则剩余集合大小为 m − x − 1 m-x-1 m−x−1,其中选 k − 2 k-2 k−2 个。
对于大小为 m m m 的置换环,写出其选择若干个数字的生成函数 f m ( x ) = ∑ i = 0 m g m , i x i f_m(x)=\displaystyle \sum_{i=0}^mg_{m,i}x^i fm(x)=i=0∑mgm,ixi,对于该排列的所有 l l l 个置换环 { m l } \{m_l\} {ml},最终答案为 ∏ i = 1 l f m i ( x ) \displaystyle \prod_{i=1}^l f_{m_i}(x) i=1∏lfmi(x)。使用分治乘法即可,时间复杂度 O ( n log 2 n ) \mathcal O(n \log ^2n) O(nlog2n)。
#include
#define fp(i, a, b) for(int i = (a), i##_ = (b) + 1; i < i##_; ++i)
#define fd(i, a, b) for(int i = (a), i##_ = (b) - 1; i > i##_; --i)
using namespace std;
using ll = int64_t;
const int N = 5e5 + 5, P = 998244353;
using Poly = vector<int>;
#define ADD(a, b) ((a) += (b), (a) >= P ? (a) -= P : 0)
#define SUB(a, b) ((a) -= (b), (a) < 0 ? (a) += P : 0)
#define MUL(a, b) (ll(a) * b % P)
int POW(ll a, int b = P - 2, ll x = 1) {
for (; b; b >>= 1, a = a * a % P)
if (b & 1) x = x * a % P;
return x;
}
namespace NTT {
const int g = 3;
Poly Omega(int L) {
int wn = POW(g, P / L);
Poly w(L); w[L >> 1] = 1;
fp(i, L / 2 + 1, L - 1) w[i] = MUL(w[i - 1], wn);
fd(i, L / 2 - 1, 1) w[i] = w[i << 1];
return w;
}
auto W = Omega(1 << 19);
void DIF(int *a, int n) {
for (int k = n >> 1; k; k >>= 1)
for (int i = 0, y; i < n; i += k << 1)
for (int j = 0; j < k; ++j)
y = a[i + j + k], a[i + j + k] = MUL(a[i + j] - y + P, W[k + j]), ADD(a[i + j], y);
}
void IDIT(int *a, int n) {
for (int k = 1; k < n; k <<= 1)
for (int i = 0, x, y; i < n; i += k << 1)
for (int j = 0; j < k; ++j)
x = a[i + j], y = MUL(a[i + j + k], W[k + j]),
a[i + j + k] = x < y ? x - y + P : x - y, ADD(a[i + j], y);
const int Inv = P - (P - 1) / n;
fp(i, 0, n - 1) a[i] = MUL(a[i], Inv);
reverse(a + 1, a + n);
}
}
namespace Polynomial {
int norm(int n) { return 1 << (__lg(n - 1) + 1); }
void DFT(Poly &a) { NTT::DIF(a.data(), a.size()); }
void IDFT(Poly &a) { NTT::IDIT(a.data(), a.size()); }
Poly &dot(Poly &a, Poly &b) {
fp(i, 0, a.size() - 1) a[i] = MUL(a[i], b[i]);
return a;
}
Poly operator*(Poly a, Poly b) {
int n = a.size() + b.size() - 1, L = norm(n);
a.resize(L), b.resize(L);
DFT(a), DFT(b), dot(a, b), IDFT(a);
return a.resize(n), a;
}
}
using namespace Polynomial;
int n, k, a[N]; Poly fac, ifac;
int C(int n, int m) { return (ll) fac[n] * ifac[m] % P * ifac[n - m] % P; }
void Solve() {
scanf("%d%d", &n, &k);
fp(i, 1, n) scanf("%d", a + i);
Poly vis(n + 1);
vector<Poly> p;
fp(i, 1, n) if (!vis[i]) {
int c = 0;
for (int j = i; !vis[j]; j = a[j]) ++c, vis[j] = 1;
Poly b(c);
b[0] = 1;
fp(j, 1, c / 2) b[j] = (C(c - j - 1, j - 1) + C(c - j, j)) % P;
p.push_back(b);
}
function<Poly(int, int)> calc = [&](int L, int R) {
if (L + 1 == R) return p[L];
int m = (L + R) >> 1;
return calc(L, m) * calc(m, R);
};
Poly f = calc(0, p.size());
printf("%d\n", f.size() > k ? f[k] : 0);
}
int main() {
fac.resize(N), ifac = fac, fac[0] = 1;
fp(i, 1, N - 1) fac[i] = MUL(fac[i - 1], i);
ifac.back() = POW(fac.back());
fd(i, N - 1, 1) ifac[i-1]= MUL(ifac[i], i);
int t = 1;
scanf("%d", &t);
while(t--) Solve();
return 0;
}
题意:维护一个字符串 S S S,初始给定,有如下操作:
∣ S ∣ ≤ 1 × 1 0 5 |S| \leq 1\times 10^5 ∣S∣≤1×105,操作次数 q ≤ 1 × 1 0 5 q \leq 1\times 10^5 q≤1×105, ∑ ∣ T ∣ ≤ 5 × 1 0 6 \sum |T| \leq 5\times 10^6 ∑∣T∣≤5×106。强制在线。
解法:一个较暴力的做法为,建立 S S S 的 SAM 和对应的 hash,每隔一定的时间重新更新 S S S 及其对应的数据结构。对于添加字符和删除,仅在 hash 上进行。首先查询 SAM 上出现次数,再查询 T T T 在前面删除部分出现和后面添加部分出现的次数,暴力枚举起始和终止点。
考虑更新时间间隔为 t t t,则总更新次数为 q t \dfrac{q}{t} tq。对于间隔内的每次查询,其最长删除字符长度为 t t t,添加字符次数也为 t t t,则每次暴力查询的复杂度为 O ( t ) O(t) O(t)。在 SAM 上查询复杂度为 ∑ ∣ T ∣ \sum |T| ∑∣T∣,因而总复杂度为 q t ∣ S ∣ + q t + ∑ ∣ T ∣ \dfrac{q}{t}|S|+qt+\sum |T| tq∣S∣+qt+∑∣T∣,因而取 t = ∣ S ∣ t=\sqrt{|S|} t=∣S∣ 即可,总复杂度为 O ( q ∣ S ∣ + ∑ ∣ T ∣ ) \mathcal O\left(q\sqrt{|S|}+\sum |T|\right) O(q∣S∣+∑∣T∣)。卡常。
#include
using namespace std;
class SAM
{
const int shift = 97;
struct node
{
int ch[26];
int len;
int father;
long long cnt;
node()
{
memset(ch, 0, sizeof(ch));
len = father = cnt = 0;
}
} NIL;
vector<node> t;
int last, ind;
void insert(int c)
{
int p = last;
int np = last = ++ind;
t.push_back(NIL);
t[np].len = t[p].len + 1;
t[np].cnt = 1;
for (; p && !t[p].ch[c]; p = t[p].father)
t[p].ch[c] = np;
if(!p)
t[np].father = 1;
else
{
int q = t[p].ch[c];
if (t[p].len + 1 == t[q].len)
t[np].father = q;
else
{
int nq = ++ind;
t.push_back(t[q]);
t[nq].cnt = 0;
t[nq].len = t[p].len + 1;
t[q].father = t[np].father = nq;
for (; p && t[p].ch[c] == q; p = t[p].father)
t[p].ch[c] = nq;
}
}
}
public:
void set(string s)
{
t.clear();
last = ind = 1;
t.push_back(NIL);
t.push_back(NIL);
if(s.empty())
return;
for (int i = 0;i < s.length();i++)
insert(s[i] - shift);
vector<vector<int> > graph(t.size());
for (int i = 2; i <= ind;i++)
graph[t[i].father].push_back(i);
function<void(int)> dfs = [&](int place)
{
assert(place < t.size());
for (auto i : graph[place])
{
dfs(i);
t[place].cnt += t[i].cnt;
}
};
dfs(1);
}
long long query(string &s)
{
int place = 1;
for (auto i : s)
place = t[place].ch[i - shift];
return t[place].cnt;
}
};
const long long mod = 998244353, base = 31;
long long power(long long a, long long x)
{
long long ans = 1;
while (x)
{
if (x & 1)
ans = ans * a % mod;
a = a * a % mod;
x >>= 1;
}
return ans;
}
long long inv(long long a)
{
return power(a, mod - 2);
}
const int N = 500000;
long long th[N + 5], invth[N + 5];
class myhash
{
int st, n, orin;
vector<long long> pre;
long long get_hash(int l, int r)
{
long long ans = pre[r];
if (l)
ans = (ans - pre[l - 1] + mod) % mod * invth[l] % mod;
return ans;
}
public:
myhash(string s)
{
this->set(s);
}
void set(string s)
{
pre.clear();
st = 0;
this->n = this->orin = s.length();
pre.resize(n);
for (int i = 0; i < n;i++)
{
pre[i] = (s[i] - 97) * th[i] % mod;
if(i)
pre[i] = (pre[i - 1] + pre[i]) % mod;
}
}
void add(char ch)
{
long long now = (ch - 97) * th[n] % mod;
if (pre.empty())
pre.push_back(now);
else
pre.push_back((pre.back() + now) % mod);
n++;
}
void del()
{
st++;
}
int query(string &t)
{
int lens = n - st, lent = t.length();
if (t.length() > lens)
return 0;
long long hasht = 0;
for (int i = 0; i < lent;i++)
hasht = (hasht + (t[i] - 97) * th[i]) % mod;
int ans = 0;
for (int i = 0; i < st;i++)
{
int r = i + lent - 1;
if (r < n && get_hash(i, r) == hasht)
ans--;
}
for (int j = orin; j < n;j++)
{
int l = j - lent + 1;
if (l >= 0 && get_hash(l, j) == hasht)
ans++;
}
return ans;
}
};
int main()
{
th[0] = 1;
for (int i = 1; i <= N;i++)
th[i] = th[i - 1] * base % mod;
invth[N] = inv(th[N]);
for (int i = N - 1; i >= 0;i--)
invth[i] = invth[i + 1] * base % mod;
cin.tie(0)->sync_with_stdio(0);
cin.exceptions(cin.failbit);
cin.tie(NULL);
cout.tie(NULL);
int caset;
cin >> caset;
string s, t, buf;
int q;
int cnt = 0;
while(caset--)
{
cin >> s >> q;
int block = 4000, st = 0;
SAM solve;
solve.set(s);
myhash h(s);
for (int o = 1, reset = 0, op, lastans = 0; o <= q; o++, reset++)
{
cin >> op;
if (op == 1)
{
cin >> buf;
char now = ((buf[0] - 97) ^ lastans) % 26 + 97;
h.add(now);
s += now;
}
else if (op == 2)
h.del();
else
{
cnt++;
cin >> t;
for (auto &i : t)
i = ((i - 97) ^ lastans) % 26 + 97;
int ans = solve.query(t) + h.query(t);
cout << ans << "\n";
lastans = ans;
}
if (reset == block)
{
if (st >= s.length())
s = "";
else
s = s.substr(st);
solve.set(s);
reset = 0;
st = 0;
}
}
}
return 0;
}
题意:有 m m m 个队列,有 n n n 个人来购物结账,到达时间和结账时长为 ( a i , s i ) (a_i,s_i) (ai,si)。每次一个人到达会找一个队列最短且编号最小的队列站着,问最少要多长时间所有人结账完毕。 n , m ≤ 2 × 1 0 5 n,m \leq 2\times 10^5 n,m≤2×105。
解法:维护一个 set
表示每个队列现在排队人数,然后把每个人来和离开事件根据时间从小到大压入优先队列。当一个人到达时找 set
中队列最短的队伍编号,然后对应修改;离开时也去 set
中修改。时间复杂度 O ( n log m ) \mathcal O(n \log m) O(nlogm)。也可以使用线段树维护。
#include
using namespace std;
class segment_tree
{
vector<long long> t;
int n;
void update(int place, int left, int right, int start, int x)
{
if (left == right)
{
t[place] += x;
return;
}
int mid = (left + right) >> 1;
if (start <= mid)
update(place << 1, left, mid, start, x);
else
update(place << 1 | 1, mid + 1, right, start, x);
t[place] = min(t[place << 1], t[place << 1 | 1]);
}
int query(int place, int left, int right)
{
if (left == right)
return left;
int mid = (left + right) >> 1;
if (t[place << 1] <= t[place << 1 | 1])
return query(place << 1, left, mid);
else
return query(place << 1 | 1, mid + 1, right);
}
public:
segment_tree(int n)
{
t.resize(4 * n + 1);
this->n = n;
}
void update(int place, int x)
{
update(1, 1, n, place, x);
}
int get_min()
{
return query(1, 1, n);
}
};
struct operation
{
long long t;
int id;
int x;
operation(long long _t, int _id, int _x)
{
t = _t;
id = _id;
x = _x;
}
bool operator<(const operation &b)const
{
if (t != b.t)
return t > b.t;
else
return x > b.x;
}
};
struct people
{
int id;
long long arr;
long long spend;
bool operator<(const people &b)const
{
return arr < b.arr;
}
people(int _id, long long _arr, long long _spend)
{
id = _id;
arr = _arr;
spend = _spend;
}
};
int main()
{
int caset, n, m;
long long a, s;
scanf("%d", &caset);
while(caset--)
{
scanf("%d%d", &n, &m);
vector<people> que;
vector<long long> leave(n);
priority_queue<operation> q;
for (int i = 0; i < n;i++)
{
scanf("%lld%lld", &a, &s);
que.emplace_back(i, a, s);
q.emplace(a, i, 1);
}
vector<long long> last(m + 1, 0);
vector<int> pos(n);
segment_tree t(m);
long long ans = 0;
while(!q.empty())
{
auto tp = q.top();
q.pop();
if (tp.x == -1)
{
ans = max(ans, tp.t);
t.update(pos[tp.id], -1);
continue;
}
int id = t.get_min();
pos[tp.id] = id;
long long ed = max(last[id], que[tp.id].arr) + que[tp.id].spend;
last[id] = ed;
t.update(id, 1);
q.emplace(ed, tp.id, -1);
}
printf("%lld\n", ans);
}
return 0;
}