2019牛客暑期多校訓練營(第一場)

[TOC]node

Contest Info

<hr> [Practice Link](https://ac.nowcoder.com/acm/contest/881#question)c++

Solved A B C D E F G H I J
9/10 O Ø Ø Ø Ø O - Ø Ø O
  • O 在比賽中經過
  • Ø 賽後經過
  • ! 嘗試了可是失敗了
  • - 沒有嘗試

Solutions

<hr>數組

A.Equivalent Prefixes

題意: 定理兩個序列等價,當且僅當:dom

  • 兩個序列的長度相同
  • $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}$是等價的。ide

思路: 首先至關於找一個最長前綴,他們是等價的。 顯然這個具備單調性。咱們能夠去二分長度,而後就變成了一個斷定問題。 根據定義,咱們要斷定的是兩個序列的任意一個子區間的$RMQ$都相同,那麼反過來考慮。 咱們考慮一個數的管轄範圍爲管轄它左邊離它最近的大於它的數之間的全部數,以及右邊離他最近的大於它的數之間的全部數。 那麼這個管轄範圍內的任意一個子區間的$RMQ$都是這個數的下標。 那麼也就是說,只要兩個序列中,每一個數的管轄範圍是相同的,那麼這兩個序列就是等價的。 求管轄範圍能夠用單調棧或者笛卡爾樹。優化

代碼:ui

#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

題意: 給出: $$ \begin{eqnarray*} \int_{0}^{\infty} \frac{1}{1 + x^2} dx = \frac{\pi}{2} \end{eqnarray*} $$ 詢問 $$ \begin{eqnarray*} \frac{1}{\pi} \int_{0}^{\infty} \frac{1}{\prod\nolimits_{i = 1}^n (a_i^2 + x^2)} dx \end{eqnarray*} $$this

思路: 考慮$n = 1$時: $$ \begin{eqnarray*} \frac{1}{\pi} \int_{0}^{\infty} \frac{1}{a^2 + x^2} dx \end{eqnarray*} $$ 有不定積分: $$ \begin{eqnarray*} \int \frac{1}{a^2 + x^2} dx = \frac{1}{a} arctan(\frac{x}{a}) + C \end{eqnarray*} $$ 當$x \rightarrow \infty$時,就是$\frac{\pi}{2a}$。 那麼考慮$n > 1$時,咱們知道有積分的和等於和的積分,因此咱們但願把乘積拆成加減形式,那麼能夠用拆項積分法,比方說: $n = 2$時: $$ \begin{eqnarray*} \int \frac{1}{(a^2 + x^2)(b^2 + x^2)}dx \end{eqnarray*} $$ 考慮: $$ \begin{eqnarray*} &&\frac{1}{(a^2 + x^2)} \ &=& \frac{\alpha}{a^2 + x^2} + \frac{\beta}{b^2 + x^2} \ &=& \frac{\alpha(b^2 + x^2) + \beta(a^2 + x^2)}{(a^2 + x^2)(b^2 + x^2)} \ &=& \frac{b^2\alpha + a^2\beta + x^2(\alpha + \beta)}{(a^2 + x^2)(b^2 + x^2)} \end{eqnarray*} $$ 那麼有: $$ \begin{eqnarray*} \left{ \begin{array}{cccc} b^2\alpha + a^2\beta = 1 \ \alpha + \beta = 0 \end{array} \right. \end{eqnarray*} $$ 因此: $$ \begin{eqnarray*} \left{ \begin{array}{cccc} \alpha = \frac{1}{b^2 - a^2} \ \beta = \frac{1}{a^2 - b^2} \end{array} \right. \end{eqnarray*} $$ 再推個幾項,就發現規律比較明顯,具體參考這裏idea

那麼有: $$ c_i = \frac{1}{\prod\limits_{j \neq i} (a_j^2 - a_i^2)} $$ 則: $$ \frac{1}{\prod\limits_{j \neq i} (a_j^2 - a_i^2)} = \sum \frac{c_i}{a_i^2 + x^2} $$ 而後轉化成積分的和便可。spa

其實我更喜歡下面這種方法: 考慮: $$ \begin{eqnarray*} \frac{1}{\prod a_i^2 + x^2} = \sum \frac{c_i}{a_i^2 + x^2} \end{eqnarray*} $$ 那麼移項有: $$ \begin{eqnarray*} 1 = \sum \frac{c_i \prod (a_j^2 + x^2)}{a_i^2 + x^2} \end{eqnarray*} $$ 那麼顯然右邊的$\prod$項中有一項是$a_i^2 + x^2$,那麼考慮拆出來: $$ \begin{eqnarray*} 1 = c_i \prod\limits_{j \neq i}(a_j^2 +x^2) + \sum\limits_{j \neq i} \frac{c_j \prod (a_k^2 + x^2)}{a_j^2 + x^2} \end{eqnarray*} $$ 那麼咱們令$x = a_i \cdot i$,那麼$x^2 = -a_i^2$,顯然有: $$ \begin{eqnarray*} a_j^2 - a_i^2 &\neq& 0 \ \prod (a_k^2 - a_i^2) &=& 0 \end{eqnarray*} $$ 由於$k$是會等於$i$的。 那麼就有: $$ \begin{eqnarray*} 1 = c_i \prod\limits_{j \neq i} (a_j^2 - a_i^2) + 0 \end{eqnarray*} $$ 因此: $$ \begin{eqnarray*} c_i = \frac{1}{\prod\limits_{j \neq i} (a_j^2 - a_i^2)} \end{eqnarray*} $$

代碼:

#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$

而且要使得 $$ \begin{eqnarray*} ||A - P||2^2 = \sum\limits{i = 1}^n (a_i/m - p_i)^2 \end{eqnarray*} $$ 最小。

思路: 咱們改變一下限制條件,咱們將第三個條件改爲: $$ \begin{eqnarray*} p_1 + p_2 + \cdots + p_n = m \end{eqnarray*} $$ 那麼對於答案式子,能夠做以下改變: $$ \begin{eqnarray*} \sum\limits_{i = 1}^n (a_i/m - p_i)^2 = \frac{1}{m^2} \sum\limits_{i = 1}^n (a_i - p_i)^2 \end{eqnarray*} $$ 咱們考慮要求的答案式子至關於求什麼,假設全部$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$。 因此均攤給這其中每一個獨立個體的高度貢獻就是 $$ \frac{m - k}{i} $$ 而後這個合併體的最終高度就是 $$ a_i - \frac{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)$爲: $$ \begin{eqnarray*} count(x) = \frac{1}{2^m} \sum\limits_{i = 1}^n \prod\limits_{j = 1}^m (1 - (-1)^{|a_{i, j} ; \wedge x|}) \end{eqnarray*} $$ 求$\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; and ; x| + |b ; and ; x| = |(a;xor;b);and;x| (mod; 2) $$ 由於只考慮結果的奇偶性的話,$a, b$相同的位$and; x$後必定會貢獻$0$。 能夠推廣成: $$ |a; and ; x| + |b ; and ; x| + |c ; and ; x| = |(a;xor;b;xor;c);and;x| (mod; 2) $$ 由於顯然能夠當作: $$ |(a; xor;b) ; and; x| + |c ;and; x| = |(a;xor;b;xor;c);and;x| (mod; 2) $$

代碼:

#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}$,問: $$ \begin{eqnarray*} E = max{S_{PAB}, S_{PBC}, S_{PCA}} \end{eqnarray*} $$

思路: 答案是$\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$,詢問: $$ \begin{eqnarray*} \sum\limits_{S \subseteq A, \oplus_{x \in S} ;; x = 0} |S| \bmod 10^9 + 7 \end{eqnarray*} $$

思路: 考慮指望的線性性,咱們能夠單獨計算一個數在多少個集合中出現過。 首先咱們對整個序列求一個線性基$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;
}
相關文章
相關標籤/搜索