Contest Info
[Practice Link](https://ac.nowcoder.com/acm/contest/881#question)
Solved | A | B | C | D | E | F | G | H | I | J |
---|---|---|---|---|---|---|---|---|---|---|
9/10 | O | Ø | Ø | Ø | Ø | O | - | Ø | Ø | O |
- O 在比賽中通過
- Ø 賽后通過
- ! 嘗試了但是失敗了
- - 沒有嘗試
Solutions
A.Equivalent Prefixes
題意:
定理兩個序列等價,當且僅當:
- 兩個序列的長度相同
- \(RMQ(u, l, r) = RMQ(v, l, r)\)
\(RMQ(w, l, r)\)的定義是\([l, r]\)區間內最小數的下標
現在給出兩個序列\(a_i, b_i\),保證每個序列有\(n\)個互不相同的數,問找一下最大的\(p\),使得\(\{a_1, \cdots, a_p\}\)與\(\{b_1, \cdots, b_p\}\)是等價的。
思路:
首先相當於找一個最長前綴,他們是等價的。
顯然這個具有單調性。我們可以去二分長度,然后就變成了一個判定問題。
根據定義,我們要判定的是兩個序列的任意一個子區間的\(RMQ\)都相同,那么反過來考慮。
我們考慮一個數的管轄范圍為管轄它左邊離它最近的大於它的數之間的所有數,以及右邊離他最近的大於它的數之間的所有數。
那么這個管轄范圍內的任意一個子區間的\(RMQ\)都是這個數的下標。
那么也就是說,只要兩個序列中,每個數的管轄范圍是相同的,那么這兩個序列就是等價的。
求管轄范圍可以用單調棧或者笛卡爾樹。
代碼:
#include <bits/stdc++.h>
using namespace std;
#define N 100010
int n, a[N], b[N];
struct Cartesian_Tree {
struct node {
int id, val, fa;
int son[2];
node() {}
node (int id, int val, int fa) : id(id), val(val), fa(fa) {
son[0] = son[1] = 0;
}
}t[N];
int root, l[N], r[N];
void init() {
t[0] = node(0, 0, 0);
}
void build(int n, int *a) {
for (int i = 1; i <= n; ++i) {
t[i] = node(i, a[i], 0);
}
for (int i = 1; i <= n; ++i) {
int k = i - 1;
while (t[k].val > t[i].val) {
k = t[k].fa;
}
t[i].son[0] = t[k].son[1];
t[k].son[1] = i;
t[i].fa = k;
t[t[i].son[0]].fa = i;
}
root = t[0].son[1];
}
int DFS(int u) {
if (!u) return 0;
l[t[u].id] = DFS(t[u].son[0]);
r[t[u].id] = DFS(t[u].son[1]);
return l[t[u].id] + r[t[u].id] + 1;
}
}t[2];
bool check(int x) {
t[0].init();
t[1].init();
t[0].build(x, a);
t[1].build(x, b);
t[0].DFS(t[0].root);
t[1].DFS(t[1].root);
for (int i = 1; i <= x; ++i) {
if (t[0].l[i] != t[1].l[i] || t[0].r[i] != t[1].r[i])
return 0;
}
return 1;
}
int main() {
while (scanf("%d", &n) != EOF) {
for (int i = 1; i <= n; ++i) {
scanf("%d", a + i);
}
for (int i = 1; i <= n; ++i) {
scanf("%d", b + i);
}
int l = 1, r = n, res = -1;
while (r - l >= 0) {
int mid = (l + r) >> 1;
if (check(mid)) {
res = mid;
l = mid + 1;
} else {
r = mid - 1;
}
}
printf("%d\n", res);
}
return 0;
}
B.Integration
題意:
給出:
詢問
思路:
考慮\(n = 1\)時:
有不定積分:
當\(x \rightarrow \infty\)時,就是\(\frac{\pi}{2a}\)。
那么考慮\(n > 1\)時,我們知道有積分的和等於和的積分,所以我們希望把乘積拆成加減形式,那么可以用拆項積分法,比方說:
\(n = 2\)時:
考慮:
那么有:
所以:
再推個幾項,就發現規律比較明顯,具體參考這里
那么有:
則:
然后轉化成積分的和即可。
其實我更喜歡下面這種方法:
考慮:
那么移項有:
那么顯然右邊的\(\prod\)項中有一項是\(a_i^2 + x^2\),那么考慮拆出來:
那么我們令\(x = a_i \cdot i\),那么\(x^2 = -a_i^2\),顯然有:
因為\(k\)是會等於\(i\)的。
那么就有:
所以:
代碼:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll P = (ll) 1e9 + 7;
#define N 1010
ll qpow(ll x, ll n) {
ll res = 1;
while (n) {
if (n & 1) {
res = res * x % P;
}
x = x * x % P;
n >>= 1;
}
return res;
}
int n;
ll arr[N];
int main() {
while (~scanf("%d", &n)) {
for (int i = 1; i <= n; ++i) {
scanf("%lld", arr + i);
}
ll ans = 0;
for (int i = 1; i <= n; ++i) {
ll tmp = 2ll * arr[i];
for (int j = 1; j <= n; ++j) {
if (i == j) {
continue;
}
tmp = tmp * (arr[j] * arr[j] % P - arr[i] * arr[i] % P + P) % P;
}
ans = (ans + qpow(tmp, P - 2)) % P;
}
printf("%lld\n", ans);
}
return 0;
}
C.Euclidean Distance
題意:
有一個\(n\)維實數域\(\mathbb{R}\)的向量\((a_1/m, a_2/m, \cdots, a_n/m)\),要求找到另一個點\(P = (p_1, p_2, \cdots, p_n)\)滿足以下要求:
- \(p_1, p_2, \cdots, p_n \in \mathbb{R}\)
- \(p_1, p_2 \cdots p_n \geq 0\)
- \(p_1 + p_2 + \cdots + p_n = 1\)
並且要使得
最小。
思路:
我們改變一下限制條件,我們將第三個條件改成:
那么對於答案式子,可以作如下改變:
我們考慮要求的答案式子相當於求什么,假設所有\(p_i = 0\)(顯然不符合條件,不管了,先這樣),那么就相當於將\(a_i\)畫在坐標軸上畫成矩形,求矩形的高度的平方,如下圖:
那么我們加入\(p_i\)能有什么用?
顯然加入\(p_i\)能夠降低一些矩形的高度,感性理解一下,肯定是盡量把高的矩形降低高度。
那么我們先降\(A_1\),使得它的高度與\(A_2\)相同。
然后呢?
然后其實可以把\(A_1, A_2\)合並起來看成一個矩形,那繼續做什么?當然是降低\(A_1, A_2\)這個合並體的高度了。
然后繼續做,我們發現有一步\(m\)不夠了,那怎么辦?
那肯定是均攤給高度相同並且最高的那個合並體,假設已經用了\(k\)了,那么還剩下\(m - k\),假設合並體的寬度(即個數)是\(i\)。
所以均攤給這其中每個獨立個體的高度貢獻就是
然后這個合並體的最終高度就是
然后加上剩下\(n - i\)個未變動矩形的貢獻即可。
注意貢獻中我們提取了一個\(\frac{1}{m^2}\),不要忘記。
代碼:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 10010
int n;
ll a[N], m;
struct frac{
ll x,y;
frac() {}
frac(ll x, ll y) : x(x), y(y) {}
ll gcd(ll a, ll b) {
return b ? gcd(b, a % b) : a;
}
frac operator+(const frac &u){
ll p, q;
p = x * u.y + y * u.x;
q = u.y * y;
ll d = gcd(p, q);
p /= d; q /= d;
return (frac){p, q};
}
frac operator-(const frac &u){
ll p, q;
p = x * u.y - y * u.x;
q = u.y * y;
ll d = gcd(p, q);
p /= d; q /= d;
return (frac){p, q};
}
frac operator*(const frac &u){
ll p, q;
p = u.x * x;
q = u.y * y;
ll d = gcd(p, q);
p /= d; q /= d;
return (frac){p, q};
}
frac operator/(const frac &u){
ll p, q;
p = u.y * x;
q = u.x * y;
ll d = gcd(p,q);
p /= d; q /= d;
return (frac){p,q};
}
void sqr() {
*this = (*this) * (*this);
}
void print(){
y == 1 ?
printf("%lld\n", x) :
printf("%lld/%lld\n", x, y);
}
};
int main() {
while (scanf("%d%lld", &n, &m) != EOF) {
for (int i = 1; i <= n; ++i) scanf("%lld", a + i);
sort(a + 1, a + 1 + n, [&](ll x, ll y){
return x > y;
});
ll k = m;
frac ans = frac(0, 1);
for (int i = 1; i <= n; ++i) {
if (i < n && (1ll * i * (a[i] - a[i + 1])) <= k) {
k -= 1ll * i * (a[i] - a[i + 1]);
} else {
ans = ans + frac(1ll * (i * a[i] - k) * (i * a[i] - k), 1ll * i * m * m);
for (int j = i + 1; j <= n; ++j) {
ans = ans + frac(1ll * a[j] * a[j], m * m);
}
ans.print();
break;
}
}
}
return 0;
}
D.Parity of Tuples
題意:
有\(n\)個\(m\)元組\((v_1, v_2, \cdots, v_n)\)其中\(v_i = (a_{i, 1}, \cdots, a_{i, m})\),定義\(count(x)\)為:
求\(\oplus_{x = 0}^{2^k - 1} (count(x) \cdot 3^x \bmod 10^9 + 7)\)
思路:
- 考慮一個長度為\(2\)的數組\(F\),元組\((a_1, \cdots, a_m)\)
- 對於所有元組\([m]\)的一個子集,將\(F[\oplus_{i \in S} a_i]\)加上\((-1)^{|S|}\),其中\(|S|\)表示子集的大小
- 將\(F\)做\(FWT_{xor}\),那么\(\displaystyle \frac{FWT(F)[x]}{2^m}\)就是\(count(x)\)的貢獻。
為啥做一遍\(FWT_{xor}\)就可以了呢,我也不太清楚。。
根據\(qls\)的說法,\(count(x)\)后面的\(\prod\)的項需要展開成\(2^m\)項,但是因為有這樣一個性質:
因為只考慮結果的奇偶性的話,\(a, b\)相同的位\(and\; x\)后一定會貢獻\(0\)。
可以推廣成:
因為顯然可以看成:
代碼:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 2000010
const ll p = 1e9 + 7;
int n, m, k;
int f[N];
void DFS(vector <int> &vec, int i, int x, int p) {
if (i < (int)vec.size()) {
DFS(vec, i + 1, x, p);
DFS(vec, i + 1, x ^ vec[i], -p);
} else {
f[x] += p;
}
}
void FWT(int *x, int len) {
for (int i = 2; i <= len; i <<= 1) {
int step = i >> 1;
for (int j = 0; j < len; j += i) {
for (int k = j; k < j + step; ++k) {
ll a = x[k], b = x[k + step];
x[k] = (a + b) % p;
x[k + step] = (a - b + p) % p;
}
}
}
}
ll qmod(ll base, ll n) {
ll res = 1;
while (n) {
if (n & 1) {
res = res * base % p;
}
base = base * base % p;
n >>= 1;
}
return res;
}
int main() {
while (scanf("%d%d%d", &n, &m, &k) != EOF) {
for (int i = 0; i < 1 << k; ++i) f[i] = 0;
for (int i = 1; i <= n; ++i) {
vector <int> a(m);
for (auto &it : a) scanf("%d", &it);
DFS(a, 0, 0, 1);
}
FWT(f, 1 << k);
ll res = 0;
ll three = 1;
ll inv = qmod(qmod(2, m), p - 2);
for (int i = 0; i < 1 << k; ++i) {
res ^= 1ll * f[i] * three % p * inv % p;
three = three * 3 % p;
}
printf("%lld\n", res);
}
return 0;
}
E.ABBA
題意:
要求構造一個長度為\(2(n + m)\)的字符串,使得有一種子序列拆分拆分出\(n\)個\(AB\)以及\(m\)個\(BA\)。
問方案數。
思路:
考慮\(f[x][y]\)表示有\(x\)個\(A\)以及\(y\)個\(B\)的合法前綴的方案數,那么考慮推到\(f[x + 1][y]\),首先要滿足:
- \(x + 1 \leq n + m, y \leq n + m\)
- \(x + 1 - n \leq y, y - m \leq x\),意思是\(x + 1\)個\(A\)中我全都給\(AB\)的\(A\)之后,那么剩下的\(A\)一定要可以跟前綴中的\(B\)配對,否則不合法,同理,對\(B\)的數量也如此判斷。
代碼:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 2050
const ll p = 1e9 + 7;
int n, m;
ll f[N][N];
void add(ll &x, ll y) {
x += y;
if (x >= p) x -= p;
}
int main() {
while (scanf("%d%d", &n, &m) != EOF) {
f[0][0] = 1;
for (int i = 0; i <= n + m; ++i) {
for (int j = 0; j <= n + m; ++j) {
if ((i + 1) <= n || (i + 1 - n) <= j) {
add(f[i + 1][j], f[i][j]);
}
if ((j + 1) <= m || (j + 1 - m) <= i) {
add(f[i][j + 1], f[i][j]);
}
}
}
printf("%lld\n", f[n + m][n + m]);
for (int i = 0; i <= n + m + 10; ++i) {
for (int j = 0; j <= n + m + 10; ++j) {
f[i][j] = 0;
}
}
}
return 0;
}
F.Random Point in Triangle
題意:
給出三個點\(A, B, C\),詢問在三角形中隨機選取一個點,然后會構成三個三角形\(S_{PAB}, S_{PBC}, S_{PCA}\),問:
思路:
答案是\(\displaystyle \frac{11}{2}\)倍三角形的面積,,不知道為啥。
代碼:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
struct node {
ll x, y;
void input() {
scanf("%lld %lld", &x, &y);
}
ll operator ^ (const node &other) const {
return x * other.y - y * other.x;
}
node operator - (const node &other) const {
return {x - other.x, y - other.y};
}
}p[5];
int main() {
while (~scanf("%lld %lld", &p[1].x, &p[1].y)) {
p[2].input();
p[3].input();
ll ans = abs((p[1] - p[2]) ^ (p[1] - p[3]));
ans *= 11;
printf("%lld\n", ans);
}
return 0;
}
H.XOR
題意:
有一個序列\(a_i\),詢問:
思路:
考慮期望的線性性,我們可以單獨計算一個數在多少個集合中出現過。
首先我們對整個序列求一個線性基\(R\),那么剩下的\(n - r\)個數,每個數出現的方案數是\(2^{n - r - 1}\)種。
因為考慮剩下的\(n - r\)個數,假設我先確定誰一定要選,那么剩下的\(n - r - 1\)個數中我不管怎么取,基\(R\)中都有一種組合方式能組合出它們的異或和。
並且是唯一組合的,因為是基中是線性無關的。
再考慮\(R\)中的每一個數的貢獻。
首先我們知道,我們知道了一個序列\(a_i\)的線性基的大小是\(r\),那么一個數要想有貢獻,它的貢獻必定是\(2^{n - r - 1}\)種。
那么有一種顯然的做法就是,枚舉基\(R\)中的每一個數\(R_i\),去掉這個數,剩下的數做線性基\(B\),如果\(R_i\)還能插入到\(B\)中,說明\(R_i\)不屬於線性基\(B\)構成的張成中,也就是說\(B\)中沒有一種組合能夠組合出\(R_i\),
那么此時\(R_i\)的方案數為\(0\),否則方案數就是\(2^{n - r - 1}\)。
有一種小優化是,先對剩下的\(n - r\)個數做一個線性基\(B\),然后枚舉\(R_i\)的時候,只要往\(B\)中插入\(R_j(j \neq i)\)即可。
因為這里數的大小是\(64\)位的,所以線性基的大小不會超過\(64\)
時間復雜度:\(\mathcal{O}(64n + 64^3)\)
代碼:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define N 100010
#define M 65
const ll p = 1e9 + 7;
int n;
ll a[N];
int vis[N];
void add(ll &x, ll y) {
x += y;
if (x >= p) x -= p;
}
struct LB {
ll d[M]; int id[M];
void init() {
memset(d, 0, sizeof d);
memset(id, -1, sizeof id);
}
bool insert(ll val, int _id = 1) {
for (int i = 63; i >= 0; --i) {
if (val & (1ll << i)) {
if (!d[i]) {
id[i] = _id;
d[i] = val;
break;
}
val ^= d[i];
}
}
return val > 0;
}
}A, B, BB;
int main() {
while (scanf("%d", &n) != EOF) {
for (int i = 1; i <= n; ++i) vis[i] = 0;
for (int i = 1; i <= n; ++i) {
scanf("%lld", a + i);
}
ll res = 0;
ll two = 5e8 + 4;
A.init();
int nullity = 0;
for (int i = 1; i <= n; ++i) {
if (!A.insert(a[i], i)) {
++nullity;
add(two, two);
}
}
add(res, two * nullity % p);
B.init();
for (int i = 0; i <= 63; ++i) {
if (~A.id[i]) {
vis[A.id[i]] = 1;
}
}
vector <ll> vec;
for (int i = 1; i <= n; ++i) {
if (!vis[i]) {
B.insert(a[i], i);
} else {
vec.push_back(a[i]);
}
}
int sze = (int)vec.size();
for (int i = 0; i < sze; ++i) {
BB = B;
for (int j = 0; j < sze; ++j) {
if (i != j) {
BB.insert(vec[j]);
}
}
if (!BB.insert(vec[i])) {
add(res, two);
}
}
printf("%lld\n", res);
}
return 0;
}
I.Points Division
題意:
在二維平面上有\(n\)個點\((x_i, y_i, a_i, b_i)\),要求將他們划分到集合\(A\)或者集合\(B\)中。
如果划分到集合\(A\),那么這個點的貢獻就是\(a_i\),否則貢獻就是\(b_i\)。
並且要滿足不存在一個\((i, j)\)使得\(i \in A, j \in B, x_i \geq x_j, y_i \leq y_j\)
思路:
考慮那個限制條件,那么在平面上必然存在一條非遞減的折線,使得折線左上角的點都是集合\(A\)的,右下角的點都是集合\(B\)的。
那么我們假設折線是貼着集合\(B\)上的點的,考慮\(dp[i]\)表示到了點\(i\),它的左邊的點的貢獻是多少。
那么有一種轉移方式,就是枚舉之前的一個點\(j\),使得\(x_j \leq x_k \leq x_i\):
- \(y_k \leq y_j\)都屬於集合\(B\)
- \(y_k \geq y_j\)都屬於集合\(A\)
的最大貢獻是多少。
那么顯然在轉移過程中,我們先對\(x\)軸從小到大排序,再對\(y\)軸從大到小排序。
那么我們轉移的時候其實枚舉\(y_j\)就可以了,因為相同的\(y_j\)而\(x_j\)不同,后面的點對他們產生的貢獻是一樣的,所以直接他們取\(max\)然后后面加貢獻就可以了。
為什么要對\(y\)軸從大到小排序,因為折線是非遞減的,我們轉移時要找的\(y_j \leq y_i\),所以我們強制讓\(y_i\)大的先做,這樣避免重復轉移狀態。
代碼:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define INF 0x3f3f3f3f3f3f3f3f
#define N 100010
int n, m;
ll H[N];
struct node {
ll x, y, a, b;
node() {}
void scan() {
scanf("%lld%lld%lld%lld", &x, &y, &a, &b);
H[++m] = y;
}
bool operator < (const node &other) const {
if (x != other.x) return x < other.x;
return y > other.y;
}
}a[N];
struct SEG {
struct node {
ll Max, lazy;
node() {
Max = lazy = 0;
}
void add(ll x) {
Max += x;
lazy += x;
}
node operator + (const node &other) const {
node res = node();
res.Max = max(Max, other.Max);
return res;
}
}t[N << 2];
void build(int id, int l, int r) {
t[id] = node();
if (l == r) return;
int mid = (l + r) >> 1;
build(id << 1, l, mid);
build(id << 1 | 1, mid + 1, r);
}
void pushdown(int id) {
ll &lazy = t[id].lazy;
if (!lazy) return;
t[id << 1].add(lazy);
t[id << 1 | 1].add(lazy);
lazy = 0;
}
void update(int id, int l, int r, int ql, int qr, ll x) {
if (l >= ql && r <= qr) {
t[id].add(x);
return;
}
int mid = (l + r) >> 1;
pushdown(id);
if (ql <= mid) update(id << 1, l, mid, ql, qr, x);
if (qr > mid) update(id << 1 | 1, mid + 1, r, ql, qr, x);
t[id] = t[id << 1] + t[id << 1 | 1];
}
void update2(int id, int l, int r, int pos, ll x) {
if (l == r) {
t[id].Max = max(t[id].Max, x);
return;
}
int mid = (l + r) >> 1;
pushdown(id);
if (pos <= mid) update2(id << 1, l, mid, pos, x);
else update2(id << 1 | 1, mid + 1, r, pos, x);
t[id] = t[id << 1] + t[id << 1 | 1];
}
ll query(int id, int l, int r, int ql, int qr) {
if (l >= ql && r <= qr) {
return t[id].Max;
}
int mid = (l + r) >> 1;
pushdown(id);
ll res = 0;
if (ql <= mid) res = max(res, query(id << 1, l, mid, ql, qr));
if (qr > mid) res = max(res, query(id << 1 | 1, mid + 1, r, ql, qr));
return res;
}
}seg;
int main() {
while (scanf("%d", &n) != EOF) {
m = 0;
H[++m] = -INF;
H[++m] = INF;
for (int i = 1; i <= n; ++i) {
a[i].scan();
}
sort(a + 1, a + 1 + n);
sort(H + 1, H + 1 + m);
m = unique(H + 1, H + 1 + m) - H - 1;
for (int i = 1; i <= n; ++i) {
a[i].y = lower_bound(H + 1, H + 1 + m, a[i].y) - H;
}
seg.build(1, 1, m);
for (int i = 1; i <= n; ++i) {
ll g = seg.query(1, 1, m, 1, a[i].y);
seg.update2(1, 1, m, a[i].y, g + a[i].b);
seg.update(1, 1, m, a[i].y + 1, m, a[i].b);
seg.update(1, 1, m, 1, a[i].y - 1, a[i].a);
}
printf("%lld\n", seg.t[1].Max);
}
return 0;
}
J.Fraction Comparision
題意:
判斷\(\displaystyle \frac{x}{a}\)與\(\displaystyle \frac{y}{b}\)的大小關系。
- \(0 \leq x, y \leq 10^{18}\)
- \(1 \leq a, b \leq 10^9\)
思路:
- 不能用浮點數判斷
- 考慮\(xb, ya\)會爆\(long\;long\),但是\(a, b\)范圍卻相對較小,所以可以求出\(\left\lfloor \frac{x}{a} \right\rfloor\)以及\(\left\lfloor \frac{y}{b} \right\rfloor\),這兩個東西先比較,然后再用\(x \% a, y \% b\)進行交叉相乘的比較。
代碼:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
ll x, y, a, b;
void out(ll A, ll B) {
if (A == B) {
puts("=");
} else if (A < B) {
puts("<");
} else {
puts(">");
}
}
int main() {
while (scanf("%lld%lld%lld%lld", &x, &a, &y, &b) != EOF) {
ll A = x / a; x %= a;
ll B = y / b; y %= b;
if (A != B) {
out(A, B);
} else {
out(x * b, y * a);
}
}
return 0;
}