AtCoder Grand Contest 040 簡要題解

從這裏開始

  A < B < E < D < C = F,心情簡單.jpg。

Problem A ><

  把峯谷都設成 0。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 5e5 + 5;

int n;
char s[N];
int L[N], R[N];

int main() {
	scanf("%s", s + 1);
	n = strlen(s + 1) + 1;
	for (int i = 1; i <= n; i++)
		L[i] = (s[i - 1] == '<') * (L[i - 1] + 1);
	for (int i = n; i; i--)
		R[i] = (s[i] == '>') * (R[i + 1] + 1);
	long long ans = 0;
	for (int i = 1; i <= n; i++) {
		ans += max(L[i], R[i]);
//		cerr << max(L[i], R[i]) << ' ';
	}
	printf("%lld\n", ans);
	return 0;
}

Problem B Two Contests

  考慮如果兩個區間有包含,要麼它們放在一起,要麼長的那一個單獨在一場考試。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 1e5 + 5;

const int inf = (signed) (~0u >> 2);

typedef class Segment {
	public:
		int l, r;

		Segment() {	}
		Segment(int l, int r) : l(l), r(r) {	}

		Segment operator & (Segment b) const {
			return Segment(max(l, b.l), min(r, b.r));
		}
		int length() {
			return max(r - l + 1, 0);
		}
		boolean operator < (Segment b) const {
			return r < b.r;
		}
} Segment;

int n;
Segment s[N], sl[N], sr[N];

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d%d", &s[i].l, &s[i].r);
	}
	sort(s + 1, s + n + 1);
	int ans = 0;
	sl[0] = Segment(0, inf);
	for (int i = 1; i <= n; i++)
		sl[i] = sl[i - 1] & s[i];
	sr[n + 1] = Segment(0, inf);
	for (int i = n; i; i--)
		sr[i] = sr[i + 1] & s[i];
	for (int i = 1; i < n; i++)
		ans = max(ans, sl[i].length() + sr[i + 1].length());
	for (int i = 1; i <= n; i++)
		ans = max(ans, s[i].length() + (sl[i - 1] & sr[i + 1]).length());
	printf("%d\n", ans);
	return 0;
}

Problem C Neither AB nor BA

  考慮把奇數位置上的 A 變成 B,B 變成 A,那麼現在變成只有 AA 和 BB 不能消除。

  考慮能夠消除所有字符的必要條件是 A 或者 B 的數量不超過 $n / 2$。不難發現這個條件也是充分的。

  如果不存在 C,那麼直接消除 AB 即可。

  考慮如果 C 只與 A 或者 B 相鄰,那麼先把 AB 相互消除,最後和 C 消除,如果還剩一些 C,那麼把 C 兩兩消除。因爲 A,B 的數量都不超過 $n / 2$,所以它們相互消除後剩下的字符數量不會超過 C 的數量。

  否則如果 A,B 的數量一樣多,那麼至少有 2 個 C,消除 1 個 AC 和 1 個 BC,否則把 C 和較多一個字符消除。顯然這樣仍然滿足條件,可以遞歸到 $n$ 更小的問題。

  然後直接計數就行了。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

#define ll long long

void exgcd(int a, int b, int& x, int& y) {
	if (!b) {
		x = 1, y = 0;
	} else {
		exgcd(b, a % b, y, x);
		y -= (a / b) * x;
	}
}

int inv(int a, int n) {
	int x, y;
	exgcd(a, n, x, y);
	return (x < 0) ? (x + n) : (x);
}

const int Mod = 998244353;

template <const int Mod = :: Mod>
class Z {
	public:
		int v;

		Z() : v(0) {	}
		Z(int x) : v(x){	}
		Z(ll x) : v(x % Mod) {	}

		friend Z operator + (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
		}
		friend Z operator - (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
		}
		friend Z operator * (const Z& a, const Z& b) {
			return Z(a.v * 1ll * b.v);
		}
		friend Z operator ~(const Z& a) {
			return inv(a.v, Mod);
		}
		friend Z operator - (const Z& a) {
			return Z(0) - a;
		}
		Z& operator += (Z b) {
			return *this = *this + b;
		}
		Z& operator -= (Z b) {
			return *this = *this - b;
		}
		Z& operator *= (Z b) {
			return *this = *this * b;
		}
		friend boolean operator == (const Z& a, const Z& b) {
			return a.v == b.v;
		} 
};

Z<> qpow(Z<> a, int p) {
	Z<> rt = Z<>(1), pa = a;
	for ( ; p; p >>= 1, pa = pa * pa) {
		if (p & 1) {
			rt = rt * pa;
		}
	}
	return rt;
}

typedef Z<> Zi;

const int N = 1e7 + 7;

int n;
Zi fac[N], _fac[N], pw2[N];

Zi comb(int n, int m) {
	return (n < m) ? (0) : (fac[n] * _fac[m] * _fac[n - m]);
}

int main() {
	scanf("%d", &n);
	fac[0] = 1;
	for (int i = 1; i <= n; i++)
		fac[i] = fac[i - 1] * i;
	_fac[n] = ~fac[n];
	for (int i = n; i; i--)
		_fac[i - 1] = _fac[i] * i;
	pw2[0] = 1;
	for (int i = 1; i <= n + 1; i++)
		pw2[i] = pw2[i - 1] + pw2[i - 1];
	Zi ans = qpow(3, n);
	for (int i = (n >> 1) + 1; i <= n; i++)
		ans -= comb(n, i) * pw2[n - i + 1];
	printf("%d\n", ans.v);
	return 0;
}

Problem D Balance Beam

  一道貪心題就有 100 種假貪心。

  假設我們已經定好了順序,設 $B_i - A_i$ 的前綴 max 爲 $mx$,顯然答案可以這樣計算

for (int i = 1; i <= n; i++) {
  if (sum >= B[i]) {
    sum -= B[i];
    ans += 1;
  } else {
    ans += 1.0 * sum / B[i];
    break;
  }
}

  考慮枚舉進入 else 語句的 $i$。計算貢獻可以分爲 3 部分:$[1, i), (i, pmx], (pmx, n]$,其中 $pmx$ 表示前綴 max 的位置。先考慮把所有 $A_i < B_i$ 大於 0 的平衡木加入 $i$ 之後 $pmx$ 之前,然後考慮把一些移動到 i 之前,不難發現移動的代價爲 $\max{A_i, B_i}$。排序後二分一下最多能選到哪即可。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

#define ll long long
#define ld long double

template <typename T>
boolean vmax(T& a, T b) {
	return (a < b) ? (a = b, true) : (false);
}

const int N = 1e5 + 5;
const ll llf = 1e17;
const int inf = (signed) (~0u >> 2);

ll gcd(ll a, ll b) {
	return (b) ? (gcd(b, a % b)) : (a);
}

typedef class Fraction {
	public:
		ll a, b;

		Fraction() : a(0), b(1) {	}
		Fraction(ll a, ll b) : a(a), b(b) {	}

		boolean operator < (Fraction B) const {
			return ((ld) a) * B.b - ((ld) b) * B.a < 0;
		}
		boolean operator > (Fraction B) const {
			return ((ld) a) * B.b - ((ld) b) * B.a > 0;
		}
} Fraction;

#define pii pair<int, int>

typedef class Item {
	public:
		int a, b, id;

		Item() {	}
		Item(int a, int b, int id) : a(a), b(b), id(id) {	}

		int maxval() const {
			return max(a, b);
		}
		boolean operator < (Item i) const {
			return maxval() < i.maxval();
		}
} Item;

int n;
vector<Item> E;
ll _presum[N], *presum;

int main() {
	scanf("%d", &n);
	ll _sum = 0;
	for (int i = 1, a, b; i <= n; i++) {
		scanf("%d%d", &a, &b);
		E.emplace_back(a, b, i);
		_sum += max(b - a, 0);
	}
	sort(E.begin(), E.end());
	Fraction ans (0, 1), I (1, 1);
	presum = _presum + 1;
	presum[-1] = 0;
	for (int i = 0; i < n; i++)
		presum[i] = presum[i - 1] + E[i].maxval();
	for (int j = 0; j < n; j++) {
		ll sum = _sum, ts;
		Item& e = E[j];
		if (e.a > e.b)
			sum += e.b - e.a;
		int l = 0, r = n - 1, mid;
#define calc(p) (presum[p] - (p >= j) * e.maxval())
		while (l <= r) {
			mid = (l + r) >> 1;
			ts = calc(mid);
			if (ts <= sum) {
				l = mid + 1;
			} else {
				r = mid - 1;
			}
		}
		Fraction tmp (sum - calc(l - 1), e.b);
		tmp = min(tmp, I);
		tmp.a += tmp.b * (l - (l > j));
		ans = max(ans, tmp);
	}
	ll g = gcd(ans.a, ans.b *= n);
	ans.a /= g;
	ans.b /= g;
	printf("%lld %lld\n", ans.a, ans.b);
	return 0;
}

Problem E Prefix Suffix Addition

  顯然可以讓前綴加非 0 的位置不相交,後綴加非 0 的位置不相交,並且不會更劣。

  問題等價於把原序列拆成兩個序列 $\{x_i\}, \{y_i\}$,滿足

  • $x_0 = y_0 = x_{n + 1} = y_{n + 1} = 0$

  • $x_i \geqslant 0, y_i \geqslant 0, x_i + y_i = a_i$。

  要求最小化 $\sum [x_i > x_{i + 1}] + [y_i < y_{i + 1}]$。

  設 $f_{i, j}$ 表示考慮到序列的第 $i$ 位,$x_i$ 的值爲 $j$ 的最小代價。

  不難發現兩個性質:

  • $f_{i, j}$ 的極差小於等於 2
  • $f_{i, j}$ 單調不增

  然後記錄一下分界點,轉移二分一下就行了。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

const int N = 2e5 + 5;

typedef class dp_t {
	public:
		int v0, p1, p2, a;

		dp_t() {	}
		dp_t(int v0, int p1, int p2, int a) : v0(v0), p1(p1), p2(p2), a(a) {	}

		int eval(int x, int na) {
#define calc(xls, fold) ((fold) + (xls > x) + (a - xls < na - x))
			int rt = calc(0, v0);
			if (p1 <= a)
				rt = min(rt, calc(p1, v0 - 1));
			if (p2 <= a)
				rt = min(rt, calc(p2, v0 - 2));
			return rt;
		}
		dp_t trans(int na) {
			dp_t rt (0, 0, 0, na);
			rt.v0 = eval(0, na);
			int l = 0, r = na, mid;
			while (l <= r) {
				mid = (l + r) >> 1;
				if (eval(mid, na) == rt.v0) {
					l = mid + 1;
				} else {
					r = mid - 1;
				}
			}
			rt.p1 = l;
			r = na;
			while (l <= r) {
				mid = (l + r) >> 1;
				if (eval(mid, na) == rt.v0 - 1) {
					l = mid + 1;
				} else {
					r = mid - 1;
				}
			}
			rt.p2 = l;
			return rt;
		}
} dp_t;

int n;
int a[N];

int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		scanf("%d", a + i);
	}
	dp_t f (0, 1, 1, 0);
	for (int i = 1; i <= n; i++)
		f = f.trans(a[i]);
	f = f.trans(0);
	printf("%d\n", f.v0);
	return 0;
}

Problem F Two Pieces

  考慮用 $(x, d)$ 來表示一個狀態,其中較大數在 $x$,較小數和較大數的差爲 $d$。那麼可以通過一些限制轉化成求操作序列個數,有如下三種操作:

  • 將 $x, d$ 同時加上 1
  • 將 $d$ 減少 1,這個時候必須滿足 $d \geqslant 2$
  • 將 $d$ 設爲 0.

  你發現前面兩個操作"很 Catalan",考慮確定前兩個操作序列,然後插入第三種操作。

  不難發現第 1 種操作會恰好執行 $B$ 次,考慮枚舉第 $2$ 種操作的操作次數 $k$。然後能用折線法計算方案數。

  如果 $N = B + k$,這種情況很 trivial,可以直接做。否則考慮 $B + k < N$ 的情況。

  考慮如何插入第 3 種操作,注意到它插入後必須同時滿足兩個條件:

  • 縱座標到達 $B - A$。
  • 不會使第二種操作不合法。

  注意到它的影響相當於把一段後綴的 $d$ 減去其中第一個 $d$ 的值。所以爲了滿足第二個條件它必須是嚴格的後綴最小值,爲了滿足第一個條件它需要操作最後一個 $d = A - k$。

  滿足這之後,可以把操作 3 插入任意一個滿足 $d \leqslant A - k$ 的嚴格後綴最小值處。這個直接插板法算方案即可。

Code

#include <bits/stdc++.h>
using namespace std;
typedef bool boolean;

#define ll long long

void exgcd(int a, int b, int& x, int& y) {
	if (!b) {
		x = 1, y = 0;
	} else {
		exgcd(b, a % b, y, x);
		y -= (a / b) * x;
	}
}

int inv(int a, int n) {
	int x, y;
	exgcd(a, n, x, y);
	return (x < 0) ? (x + n) : (x);
}

const int Mod = 998244353;

template <const int Mod = :: Mod>
class Z {
	public:
		int v;

		Z() : v(0) {	}
		Z(int x) : v(x){	}
		Z(ll x) : v(x % Mod) {	}

		friend Z operator + (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v + b.v) >= Mod) ? (x - Mod) : (x));
		}
		friend Z operator - (const Z& a, const Z& b) {
			int x;
			return Z(((x = a.v - b.v) < 0) ? (x + Mod) : (x));
		}
		friend Z operator * (const Z& a, const Z& b) {
			return Z(a.v * 1ll * b.v);
		}
		friend Z operator ~(const Z& a) {
			return inv(a.v, Mod);
		}
		friend Z operator - (const Z& a) {
			return Z(0) - a;
		}
		Z& operator += (Z b) {
			return *this = *this + b;
		}
		Z& operator -= (Z b) {
			return *this = *this - b;
		}
		Z& operator *= (Z b) {
			return *this = *this * b;
		}
		friend boolean operator == (const Z& a, const Z& b) {
			return a.v == b.v;
		} 
};

Z<> qpow(Z<> a, int p) {
	Z<> rt = Z<>(1), pa = a;
	for ( ; p; p >>= 1, pa = pa * pa) {
		if (p & 1) {
			rt = rt * pa;
		}
	}
	return rt;
}

typedef Z<> Zi;

const int N = 2e7 + 7;

int n, A, B;
Zi fac[N], _fac[N];

void init(int n) {
	fac[0] = 1;
	for (int i = 1; i <= n; i++)
		fac[i] = fac[i - 1] * i;
	_fac[n] = ~fac[n];
	for (int i = n; i; i--)
		_fac[i - 1] = _fac[i] * i;
}
Zi comb(int n, int m) {
	return (n < m) ? (0) : (fac[n] * _fac[m] * _fac[n - m]);
}
Zi calc(int x, int y) {
	return comb(x + y, x) - comb(x + y, x + 1);
}

int main() {
	scanf("%d%d%d", &n, &A, &B);
	if (!B) {
		puts("1");
		return 0;
	} 
	init(n << 1);
	Zi ans = 0;
	for (int k = 0; k <= n - B && k <= A; k++) {
		if (B + k == n) {
			ans += (A == k) * calc(B - 1, A);
		} else {
			ans += calc(B - 1, k) * comb(n - B - k - 1 + A - k, A - k);
		}
	}
	printf("%d\n", ans.v);
	return 0;
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章