题意:给定一个长度为 n n n 的序列,对它进行重新排序使得它不存在等差子序列。 n ≤ 5 × 1 0 3 n \leq 5\times 10^3 n≤5×103。
解法:首先如果一个数字出现大于等于 3 3 3 次,则一定无解。
考虑一个等差子序列的二进制构成: x , y , z x,y,z x,y,z。首先都丢弃掉共同的最低位的 0 0 0,考虑公差为 1 1 1 的情况(公差更大的同理),则有 x = ( 01 ⋯ 01 ) x=(01\cdots 01) x=(01⋯01), y = ( 01 ⋯ 10 ) y=(01\cdots 10) y=(01⋯10), z = x = ( 01 ⋯ 11 ) z=x=(01\cdots 11) z=x=(01⋯11),前面完全相同。则只有让 y y y 排列在 x , z x,z x,z 之前或者之后才行。注意到 x , z x,z x,z 同奇偶而 y y y 不同,因而可以按照末尾对其排序,则可以顺利让 x , z x,z x,z 置于 y y y 前或后。同理对公差更大的序列也可以进行类似的操作。因而最后的答案就是对数字按照 b i t r e v \rm bitrev bitrev 的大小进行排序即可。总时间复杂度 O ( n log n ) \mathcal O(n \log n) O(nlogn)。
#include
using namespace std;
unsigned rev(unsigned x)
{
unsigned ans = 0;
for (int i = 31; i >= 0; i--, x >>= 1)
ans |= (x & 1) << i;
return ans;
}
int main()
{
int t, n;
scanf("%d", &t);
while(t--)
{
scanf("%d", &n);
map<int, int> times;
vector<pair<unsigned, unsigned>> a(n);
bool flag = 1;
for (int i = 0; i < n;i++)
{
scanf("%u", &a[i].second);
times[a[i].second]++;
a[i].first = rev(a[i].second);
}
for(auto i:times)
if(i.second >= 3)
{
flag = 0;
break;
}
if(!flag)
{
printf("NO\n");
continue;
}
printf("YES\n");
sort(a.begin(), a.end());
for (auto i : a)
printf("%d ", i.second);
printf("\n");
}
return 0;
}
题意:给定长度为 n n n 的排列 { p i } \{p_i\} {pi}, q q q 次询问 [ l , r ] [l,r] [l,r],问该区间需要经过多少次子区间旋转平移操作(即, a i a i + 1 ⋯ a j → a i + 1 ⋯ a j a i a_ia_{i+1} \cdots a_j \to a_{i+1} \cdots a_ja_i aiai+1⋯aj→ai+1⋯ajai 或 a i a i + 1 ⋯ a j → a j a i a i + 1 ⋯ a j − 1 a_ia_{i+1} \cdots a_j \to a_ja_ia_{i+1} \cdots a_{j-1} aiai+1⋯aj→ajaiai+1⋯aj−1),变成 B ( p [ l , r ] ) B(p[l,r]) B(p[l,r])。其中 B ( p [ x , y ] ) B(p[x,y]) B(p[x,y]) 操作如下:
int* B(int *p, int x, int y)
{
for(int i=x;i<y;i++)
if(p[i]>p[i+1])
swap(p[i],p[i+1]);
return p;
}
n , q ≤ 1 × 1 0 5 n,q \leq 1\times 10^5 n,q≤1×105。
解法:利用单调栈求出每个点后面第一个比它大的值在哪里,第 i i i 个数字记为 R i R_i Ri。若 r i = i + 1 r_i=i+1 ri=i+1,则必然不会交换;否则它就会有交换操作,且直接交换到 R p R_p Rp。对应于可执行操作,就是一次旋转平移。同时,若满足 i < j ≤ r j ≤ r i i < j \leq r_j \leq r_i i<j≤rj≤ri,则 j j j 不会被交换。考虑倍增, f i , j f_{i,j} fi,j 表示从第 i i i 个点开始经历 2 j 2^j 2j 次旋转平移操作所到达的点。若抵达最后一个跳跃点 p p p,且 R p ≥ r R_p \geq r Rp≥r,若 R p = p + 1 R_p=p+1 Rp=p+1 则不经历旋转平移,否则还需要一次旋转平移。
#include
using namespace std;
const int N = 100000;
int a[N + 5], r[N + 5], nx[N + 5], f[N + 5][20];
int main()
{
int t, n, q;
scanf("%d", &t);
while(t--)
{
scanf("%d%d", &n, &q);
for (int i = 1; i <= n;i++)
{
r[i] = n + 1;
for (int j = 0; j < 20;j++)
f[i][j] = 0;
}
for (int j = 0; j < 20;j++)
f[n + 1][j] = n + 1;
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
stack<int> s;
for (int i = n; i >= 1;i--)
{
while (!s.empty() && a[s.top()] < a[i])
s.pop();
if(!s.empty())
r[i] = s.top();
else
r[i] = n + 1;
s.push(i);
}
f[n + 1][0] = nx[n + 1] = n + 1;
for (int i = n; i >= 1;i--)
{
if(r[i] > i + 1)
{
nx[i] = i + 1;
f[i][0] = r[i];
}
else
{
f[i][0] = f[r[i]][0];
nx[i] = nx[i + 1];
}
}
for (int i = 1; i < 20; i++)
for (int j = 1; j <= n + 1; j++)
f[j][i] = f[f[j][i - 1]][i - 1];
while(q--)
{
int l, r;
scanf("%d%d", &l, &r);
int ans = 0, place = l;
for (int i = 19; i >= 0;i--)
if (f[place][i] <= r)
{
place = f[place][i];
ans += 1 << i;
}
printf("%d\n", ans + (nx[place] <= r));
}
}
return 0;
}
题意:给定长度为 n n n 的序列 { a n } \{a_n\} {an}, q q q 次询问,每次从 l l l 号节点出发,初始钱数为 x x x,走到 i + 1 i+1 i+1 节点的时候若 x + a i + 1 ≥ 0 x+a_{i+1} \geq 0 x+ai+1≥0 则 x ← x + a i + 1 x \leftarrow x+a_{i+1} x←x+ai+1,否则不变,问抵达 r r r 节点时的钱数。 n , q ≤ 5 × 1 0 5 n,q \leq 5\times 10^5 n,q≤5×105, ∑ ∣ a i ∣ ≤ 1 × 1 0 6 \sum|a_i| \leq 1\times 10^6 ∑∣ai∣≤1×106。
解法:考虑在第 i i i 个节点的时候,两次询问里面人的钱数相同,则他们两个可以认为是一个人。
首先离线,然后从左到右遍历序列的时候维护一个询问的并查集。对于 a i ≥ 0 a_i \geq 0 ai≥0 的情况,那么显然它不会合并询问;否则,对于钱数为 x x x 和钱数为 x − a i x-a_i x−ai 的询问,就需要合并成一个询问。
考虑加入询问:如果当前的钱数没人了,则新建一个点;否则合并入并查集中,同时利用并查集维护每个询问的起始钱数对应于现在的钱数。回答只需要在并查集中查询对应的钱数即可。总时间复杂度 O ( n + q ) \mathcal O(n+q) O(n+q)。
#include
using namespace std;
const int N = 1000000;
int p[3 * N + 5], pos[3 * N + 5];
class DSU
{
vector<int> father;
public:
DSU(int n)
{
father.resize(n);
for (int i = 0; i < n;i++)
father[i] = i;
}
int getfather(int x)
{
return father[x] == x ? x : father[x] = getfather(father[x]);
}
void merge(int v, int u)
{
u = getfather(u), v = getfather(v);
if (u != v)
father[u] = v;
}
};
int main()
{
int caset, n, q;
scanf("%d", &caset);
for (int o = 1; o <= caset;o++)
{
scanf("%d%d", &n, &q);
for (int i = 0; i <= 3 * N; i++)
{
p[i] = -1;
pos[i] = 0;
}
DSU t(q);
auto merge = [&](int u, int v)
{
assert(u <= 2 * N && u >= 0);
assert(v <= 2 * N && v >= 0);
if (p[u] == -1)
return;
if (p[v] == -1)
{
p[v] = p[u];
pos[p[v]] = v;
}
else
t.merge(p[v], p[u]);
p[u] = -1;
};
int start = N;
vector<int> a(n), ans(q), x(q);
vector<vector<int>> add(n), del(n);
for (int i = 0; i < n;i++)
scanf("%d", &a[i]);
for (int i = 0, l, r; i < q;i++)
{
scanf("%d%d%d", &l, &r, &x[i]);
add[l - 1].push_back(i);
del[r - 1].push_back(i);
}
for (int i = 0; i < n;i++)
{
if (a[i] < 0)
for (int j = start; j < start - a[i];j++)
merge(j, j - a[i]);
start -= a[i];
for (auto j : add[i])
{
int now = x[j] + start;
if(p[now] == -1)
{
p[now] = j;
pos[j] = now;
}
else
t.merge(p[now], j);
}
for (auto j : del[i])
ans[j] = pos[t.getfather(j)] - start;
}
for (auto i : ans)
printf("%d\n", i);
}
return 0;
}
题意:给定 n n n 个盒子的大小 { a i } \{a_i\} {ai},一个大小为 x x x 的盒子能放入大小为 y y y 的盒子中当且仅当 x + r ≤ y x+r \leq y x+r≤y。问将这 n n n 个盒子分成 k k k 组,每一组中小盒子都能完全放入本组任何比它大的盒子中的方案数。 n , k ≤ 5 × 1 0 3 n,k \leq 5\times 10^3 n,k≤5×103。
解法:首先按照大小对 a i a_i ai 排序。考虑 f i , j f_{i,j} fi,j 表示前 i i i 小的盒子分成 j j j 堆的方案数,则 f i , j ← f i − 1 , j − 1 + f i − 1 , j max ( 0 , j − c i ) f_{i,j} \leftarrow f_{i-1,j-1}+f_{i-1,j}\max(0,j-c_i) fi,j←fi−1,j−1+fi−1,jmax(0,j−ci),其中 c i c_i ci 表示前 i i i 个盒子中有多少个盒子不能放入第 i i i 个盒子。 f i − 1 , j max ( 0 , j − c i ) f_{i-1,j}\max(0,j-c_i) fi−1,jmax(0,j−ci) 的含义为,第 i i i 个盒子只能和前面所有的堆中,最大的盒子满足 a j + r ≤ a i a_j+r \leq a_i aj+r≤ai 放在一堆,对于不能被 a i a_i ai 放进去的盒子,它一定会被单独的列在别的堆中。预处理 c i c_i ci 后总时间复杂度 O ( n 2 ) \mathcal O(n^2) O(n2)。
#include
using namespace std;
const int mod = 998244353;
const int N = 5000;
int f[N + 5][N + 5], a[N + 5], l[N + 5];
int main()
{
int t, n, k, r;
scanf("%d", &t);
while(t--)
{
memset(f, 0, sizeof(f));
scanf("%d%d%d", &n, &k, &r);
for (int i = 1; i <= n;i++)
scanf("%d", &a[i]);
sort(a + 1, a + n + 1);
l[1] = 1;
for (int i = 2; i <= n;i++)
{
int st = l[i - 1];
while(st <= i && a[st] <= a[i] - r)
st++;
l[i] = st;
}
for (int i = 1; i <= n;i++)
f[i][i] = 1;
for (int i = 2; i <= n;i++)
for (int j = 1; j < i && j <= k;j++)
f[i][j] = (f[i - 1][j - 1] + 1ll * f[i - 1][j] * max(0, j - (i - l[i])) % mod) % mod;
printf("%d\n", f[n][k]);
}
return 0;
}
题意:有 n n n 个点的图,第 i i i 个节点和第 j j j 个节点之前有一条边权为 gcd ( i , j ) \gcd(i,j) gcd(i,j) 的边。 q q q 次询问从 x x x 到 y y y 节点的最短路长度和最短路条数。 n ≤ 1 × 1 0 7 n\leq 1\times 10^7 n≤1×107, q ≤ 5 × 1 0 4 q \leq 5\times 10^4 q≤5×104。
解法:若
gcd
(
x
,
y
)
=
1
\gcd(x,y)=1
gcd(x,y)=1,则最短路长度为
1
1
1,条数也为
1
1
1。否则一定可以通过
i
→
1
→
j
i \to 1 \to j
i→1→j 以长度为
2
2
2 抵达。考虑符合条件的最短路条数,必须满足
gcd
(
x
,
i
)
=
1
\gcd(x,i)=1
gcd(x,i)=1 且
gcd
(
y
,
i
)
=
1
\gcd(y,i)=1
gcd(y,i)=1,即是计算
∑
i
=
1
n
[
gcd
(
i
,
l
c
m
(
x
,
y
)
)
=
1
]
\displaystyle \sum_{i=1}^n [\gcd(i,{\rm lcm}(x,y))=1]
i=1∑n[gcd(i,lcm(x,y))=1]。设
u
=
l
c
m
(
x
,
y
)
u={\rm lcm}(x,y)
u=lcm(x,y),有:
∑
i
=
1
n
[
gcd
(
i
,
u
)
=
1
]
=
∑
i
=
1
n
∑
d
∣
u
μ
(
d
)
=
∑
d
∣
u
μ
(
d
)
∑
i
=
1
⌊
n
d
⌋
1
=
∑
d
∣
u
μ
(
d
)
⌊
n
d
⌋
n∑i=1[gcd(i,u)=1]=n∑i=1∑d|uμ(d)=∑d|uμ(d)⌊nd⌋∑i=11=∑d|uμ(d)⌊nd⌋
i=1∑n[gcd(i,u)=1]===i=1∑nd∣u∑μ(d)d∣u∑μ(d)i=1∑⌊dn⌋1d∣u∑μ(d)⌊dn⌋
因而现在只需要枚举
l
c
m
(
x
,
y
)
{\rm lcm}(x,y)
lcm(x,y) 比
n
n
n 小的因子。对
u
,
v
u,v
u,v 进行质因子分解,统计
u
u
u 的质因子,然后
2
k
2^k
2k 枚举所有质因子组合即可。由于
1
×
1
0
7
1\times 10^7
1×107 下至多只有
8
8
8 个质因子,
l
c
m
(
x
,
y
)
{\rm lcm}(x,y)
lcm(x,y)最多能包含的本质不同的质因子只有
12
12
12 个,因而完全可以通过。总复杂度
O
(
q
2
k
)
\mathcal O(q2^k)
O(q2k),其中
k
≤
12
k \leq 12
k≤12。
#include
using namespace std;
const int N = 1e7 + 5;
bool vis[N + 5];
int tot, prime[N + 5], mu[N + 5], minfactor[N + 5];
void sieve(int n)
{
mu[1] = 1;
for (int i = 2; i <= n;i++)
{
if(!vis[i])
{
minfactor[i] = i;
prime[++tot] = i;
mu[i] = -1;
}
for (int j = 1; j <= tot && (long long)prime[j] * i <= n;j++)
{
int num = prime[j] * i;
vis[num] = 1;
mu[num] = -mu[i];
minfactor[num] = minfactor[i];
if (i % prime[j] == 0)
{
mu[num] = 0;
break;
}
}
}
}
int q, n, m, u, v, ans;
vector<int> factor;
void fac(int x)
{
while (x > 1)
{
factor.push_back(minfactor[x]);
x /= minfactor[x];
}
}
void dfs(int step, long long val)
{
if (val > n)
return;
if (step == m)
{
ans += mu[val] * (n / val);
return;
}
dfs(step + 1, val * factor[step]);
dfs(step + 1, val);
}
int main()
{
sieve(N);
scanf("%d%d", &n, &q);
while(q--)
{
scanf("%d%d", &u, &v);
int d = __gcd(u, v);
if (d == 1)
{
printf("1 1\n");
continue;
}
printf("2 ");
factor.clear();
fac(u), fac(v);
sort(factor.begin(), factor.end());
factor.erase(unique(factor.begin(), factor.end()), factor.end());
m = factor.size();
ans = 0;
dfs(0, 1);
printf("%d\n", ans + (d == 2));
}
return 0;
}
题意:给定长度为 n n n 的序列 { a i } \{a_i\} {ai},每次等概率随机选择两个数字 a , b a,b a,b,将它们从序列中删除,并添加 a b + a + b ab+a+b ab+a+b,问最终得到数字的期望。 n ≤ 500 n \leq 500 n≤500。
解法:整个序列的 ∏ i = 1 n ( a i + 1 ) \displaystyle \prod_{i=1}^n (a_i+1) i=1∏n(ai+1) 在除了最后一次操作的时候是不会变的,最后一次操作会让它减少 1 1 1,因而输出 ∏ i = 1 n ( a i + 1 ) − 1 \displaystyle \prod_{i=1}^n (a_i+1)-1 i=1∏n(ai+1)−1 即可。
题意:给定质数 p p p,问 F p {\Bbb F}_p Fp 中有多少个集合 S S S 满足存在一个域上 n n n 次可约多项式 f ( x ) = g ( x ) k ( x ) f(x)=g(x)k(x) f(x)=g(x)k(x),且 S = T ∩ F p S=T \cap {\Bbb F}_p S=T∩Fp,其中 T T T 为模意义方程 f ( x ) g ( c x ) ≡ 0 ( m o d p ) f(x)g(cx) \equiv 0 \pmod p f(x)g(cx)≡0(modp) 的解集。 p ≤ 5 × 1 0 5 p\leq 5\times 10^5 p≤5×105, n n n 无限制, c ∈ F p c \in {\Bbb F}_p c∈Fp。
解法:显然在 c c c 的作用下, F p {\Bbb F}_p Fp 被分成了 p − 1 o r d ( c ) \dfrac{p-1}{{\rm ord}(c)} ord(c)p−1 个长度为 o r d ( c ) {\rm ord}(c) ord(c) 的环,和 0 0 0 构成的自环。由于 c c c 的作用,选择了 x x x 就可以顺带选择 c − 1 x c^{-1}x c−1x(环上下一个点)。因而对于 h ( x ) h(x) h(x) 上连续 l l l 个零点,只需要 ⌈ l 2 ⌉ \left \lceil \dfrac{l}{2}\right \rceil ⌈2l⌉ 个 f ( x ) f(x) f(x) 零点即可——若需要连续的 x 1 , x 2 x_1,x_2 x1,x2,其中 x 1 = c x 2 m o d p x_1=cx_2 \bmod p x1=cx2modp,则可以让 x − x 1 ∣ g ( x ) x-x_1|g(x) x−x1∣g(x),这样 x 2 x_2 x2 就为 g ( c x ) g(cx) g(cx) 的根。因而该问题可以等价于环上选择若干个不相邻的点的方案数,为 ( n − s s ) + ( n − s − 1 s − 1 ) \displaystyle {n-s \choose s}+{n-s-1 \choose s-1} (sn−s)+(s−1n−s−1),其中 n n n 为环长, s s s 为 f ( x ) f(x) f(x) 的根(不相邻点数目)。最后还原到 h ( x ) h(x) h(x) 的根的选择,即是这 s s s 个后续节点(因为在选择不相邻点的时候,是将该点与下一个点绑定了)可选可不选。同时需要特判 n = 2 s n=2s n=2s 与 n = 2 s − 1 n=2s-1 n=2s−1 这两种情况:对于 n = 2 s n=2s n=2s,朴素选点只有两种选法,但是这两种选法下全选方案其实是一样的,需要减掉一种;对于 n = 2 s − 1 n=2s-1 n=2s−1,在环上选点方案为 0 0 0,但是其实有一种,且仅有一种: f ( x ) f(x) f(x) 选择 1 , 3 , 5 ⋯ , n 1,3,5\cdots,n 1,3,5⋯,n,对应于 h ( x ) h(x) h(x) 的全选。因而最后可以得到的 f ( x ) f(x) f(x) 中选择 s s s 个零点,对应于 h ( x ) h(x) h(x) 的零点方案数为 2 s ( ( n − s s ) + ( n − s − 1 s − 1 ) ) − [ n = 2 s ] + [ n + 1 = 2 s ] \displaystyle 2^s\left({n-s \choose s}+{n-s-1 \choose s-1}\right) -[n=2s]+[n+1=2s] 2s((sn−s)+(s−1n−s−1))−[n=2s]+[n+1=2s]。
考虑 k k k 个环,即是使用生成函数做 k k k 次幂即可。最后选择不超过 n n n 个 f ( x ) f(x) f(x) 的根,因为对于域上的多项式,任意次幂均有不可约多项式。此处需要它不能产生 F p {\Bbb F}_p Fp 上的根,那么如果仅选择了 m m m 个零点,只需要找一个 n − m n-m n−m 次的不可约多项式即可,此处 n − m ≥ 2 n-m \geq 2 n−m≥2,因为一次不可约多项式可以产生 F p \Bbb F_p Fp 上的根。
int get_ord(int p, int c)
{
int val = 1, ord = 0;
while (!ord || val != 1)
{
ord++;
val = 1ll * val * c % p;
}
return ord;
}
long long cal(int n, int s)
{
int ans = th[s] * (C(n - s, s) + C(n - s - 1, s - 1)) % P;
if (n + 1 == 2 * s)
inc(ans, 1);
if (n == 2 * s)
dec(ans, 1);
return ans;
}
int main()
{
th[0] = 1;
for (int i = 1; i < L;i++)
th[i] = th[i - 1] * 2 % P;
printf("%d %d\n", cal(4, 2), cal(7, 4));
int t, n, p, c;
scanf("%d", &t);
while (t--)
{
scanf("%d%d%d", &p, &c, &n);
int len = get_ord(p, c), cnt = (p - 1) / len;
Poly f(p + 1, 0);
for (int i = 0; i <= (len + 1) / 2;i++)
f[i] = cal(len, i);
Poly g = f.power(p + 1, cnt);
int ans = 0;
for (int i = 0; i <= min(p, n);i++)
{
inc(ans, g[i]);
if (i)
inc(ans, g[i - 1]);
}
if (n == 1)
dec(ans, 1);
printf("%d\n", ans);
}
return 0;
}