ARC185

georegyucjr發表於2024-10-14

沒看的會標記出來,會了沒寫的不會貼程式碼,過了會貼程式碼。

A

A statement

兩個人在博弈,每個人手上有\(1\sim n\)\(n\)張牌。每次每方都可以出一張牌,如果當前所有牌的sum為\(m\)的倍數,那麼出這牌的人輸。如果到最後還是沒有出現\(m\)的倍數過,那麼Alice勝。多測。

  • \(1\le T \le 10^5\)
  • \(1\le n, m \le 10^9\)

A sol

我沒想去證過,憑感覺和手模猜到的。感覺就是勝負只和Alice的最後一張牌有關係直接考慮Bob是否能夠透過最後一步使得為\(m\)的倍數(我也不是到我在寫什麼)。具體的可以去看一下官方editorial。

A code

code

main() ...

	int T; read(T); rep(_, 1, T) {
		ll n, m; read(n, m);
		constexpr auto o = [&](bool &&flg) { writeln(flg ? "Bob"s : "Alice"); };
		if (m > (1 + n) * n) 
			{o(0);continue;}
		ll x = (1 + n) * n % m;
		if (x == 0 || x > n) o(0);
		else o(1);
	}

B

B statement

給定一個序列,問是否能否透過若干次操作使得序列變得不減。操作的定義是選\(1\le i < j\le n\), \(\text{let} \ a_i=a_i+1,a_j=a_j-1\)。多測。

  • \(1 \leq T \leq 2 \times 10^5\)
  • \(2 \leq n \leq 2 \times 10^5\)
  • \(0 \leq a_i \leq 10^9\)
  • \(\sum \limits n \leq 2\times 10^5\)

B sol

首先我們發現我們如果是合法的都是可以變成前面sum%nsum/n,n-sum%n(sum+n-1)/n。比如\((4, 6)\),可以透過一次操作變成\((5, 5)\)

然後考慮變成如上形式。我們令我們要變成的第\(i\)位是\(b_i\),那麼如果\(\exist 1\le m\le n,\sum \limits_{i=1}^{m}b_i-a_i<0\),也就意味著前面減的比加的多了,顯然是不合法的,因此不合法。否則合法。

B code

完整程式碼

	int T; read(T); rep(_, 1 ,T) {
		int n; read(n); vector<int> a(n), b(n); forall(a, [&](auto &x) { read(x); }); 
		auto sum = accumulate(ALL(a), 0LL);
		int p = n + 1 - sum % n, dv = sum / n;
		rep0(i, 0, p - 1) b[i] = dv;
		rep0(i, p - 1, n) b[i] = dv + 1;
		dbg(b);
		ll tot = 0;
		bool flg = true;
		rep0(i, 0, n) {
			tot += b[i] - a[i];
			if (tot < 0) {
				flg = false;
				break;
			}
		}
		writeln(flg ? "Yes"s : "No"s);
	}

C

C statement

給你一個長度為\(n\)序列\(a\),詢問是否存在\(i, j, k\), 使得

  • \(1 < i < j < k \le n\)

  • \(a_i + a_j + a_k = m\)(\(m\)給定)

  • \(3 \leq n \leq 10^6\)

  • \(1 \leq x \leq 10^6\)

  • \(1 \leq a_i \leq X\)

C sol

這個題場上看了5min胡了,好像有點類似官方做法?

套路的我們想到列舉一個數是多少(比如列舉\(j\)),然後就轉化成了詢問是否存在一對\(i, k\),使得\(a_i + a_k = m - a_j\)

我們姑且不管他不能相等,發現就是\(x + y = z\)的形式,發現可以直接卷積。在發現後面的哪個也是卷積形式。一開始的想法是直接記錄一個數有沒有出現過,直接卷積。

但是發現一個問題,\(i\not = j \not = k\),也就是說只記錄是否出現是假的。於是直接考慮記錄出現次數,然後卷積,減去會有相同的數量,詳見程式碼。

提一嘴,我在每一次卷積後把\(>m\)的部分都erase掉了,不然極大機率會T。我扔掉還是跑了2991ms。可能跟我沒卡常有關?

C code

完整程式碼

核心程式碼:


// head
# include <atcoder/all>

vector<ll> f;

int n, m;
vector<vector<int> > p;

ll C2(ll x) { return x * (x - 1) / 2; } 

signed main() {
  // ios_base ::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	read(n, m);
	f.resize(m + 1);
	p.resize(m + 1);
	rep(i, 1, n, x) read(x), f[x]++, p[x].eb(i);
	auto g = atcoder::convolution_ll(f, f);
	if (SZ(g) > m + 1) 
		g.erase(begin(g) + m + 1, end(g));
	rep(x, 0, m / 2) if (SZ(p[x])) g[x * 2] -= SZ(p[x]), chkmax(g[x * 2], 0);
	auto h = atcoder::convolution_ll(g, f);
	if (SZ(h) > m + 1) 
		h.erase(begin(h) + m + 1, end(h));	
	rep(x, 0, m / 3) if (SZ(p[x])) h[x * 3] -= g[x * 2] * 2;
	if (h[m] == 0) 
		return writeln("-1"s), 0;
	vector<int> cur(m + 1);
	vector<int> pos(3, 0);
	rep(i, 0, m)
		if (f[i] && g[m - i]) {
			pos[0] = ++cur[i];
			rep(j, 0, m - i) 
				if (f[j] && f[m - i - j]) {
					pos[1] = ++cur[j];
					pos[2] = ++cur[m - i - j];
					if (cur[i] <= SZ(p[i]) && cur[j] <= SZ(p[j]) && cur[m - i - j] <= SZ(p[m - i - j])) {
						vector<int> tans = {p[i][pos[0] - 1], p[j][pos[1] - 1], p[m - i - j][pos[2] - 1]};
						sort(ALL(tans));
						writeall(tans);
						return 0;
					} else {
						--cur[j], --cur[m - i - j];
					}
				}
			--cur[i];
		}
	writeln("-1"s);

#if defined(LOCAL) && !defined(CPH)
  std::cerr << "Spend Time : " << clock() * 1. / CLOCKS_PER_SEC * 1e3 << " ms \n";
#endif
  return 0;
}

D

D statement

一棵樹,根節點將n條長度為m的鏈連線起來,從根節點走,隨機會走到相鄰節點,求走遍所有點的機率。

  • \(1 \leq n, m \leq 2 \times 10^5\)

D sol

開了個小掛,找了個經典結論。

hint1 發現n條長度為m的鏈是相同的,分開考慮
hint2 長度為m+1的鏈(算根節點)平均的往返次數為$m^2$
證明 轉化問題,變成給你一個$[0,n]$的數軸,在$[1, n - 1]$的時候你都可以隨意向左或右走,$0$的時候只能走右邊,$n$的時候不能往右。令$f_x$表示當前座標在x時的期望步數。不難發現$f_x=\frac{f_{x-1}+f_{x+1}}{2}+1$

考慮移向。\(f_{i+1}-f_i=f_{i}-f_{i-1}-2\)。令\(g_i=f_i-f_{i-1}\)。那麼就變成了\(g_{i+1}=g_{i}-2\)。特殊的,由於\(f_0=f_1+1\),所以\(g_1=1\)。則\(g_x=-2x+1\)。那麼\(f_i=f_{i-1}+g_i=f_{i}+1-2i\)

移向得\(f_{i-1}=f_{i}+2i-1\)。求和發現\(f_0=\sum \limits_{i=1}^{n}(2i-1)=n^2\)
\(\mathcal{Q.E.D}\)

考慮只有\(m=1\)的時候答案是什麼。第k次選顯然的有\(\frac{n-k+1}{n}\)的機率會選到一個從未選擇過的點,那麼反之期望次數就是\(\frac{n}{n-k+1}\)。那麼\(m=1\)時答案就是\(2\sum \limits_{i=1}^{n}\frac{n}{i}-1\),由於時往返,並且都走過就不用再走最後一步了,所以減1。自然的,最後的答案是\(m=1\)的答案\(\times m^2\),由hint2得。

綜上,答案是\(((2\sum \limits_{i=1}^{n}\frac{n}{i})-1)\times m^2\)

D code

完整程式碼

# include <atcoder/modint>
using mint = atcoder :: modint998244353;
// in main
	int n, m;
	read(n, m);
	mint ans = 0;
	rep0(i, 0, n) 
		ans += mint(n - i).inv();
	writeln(((ans * n * 2 - 1) * m * m).val());

E

E statement

定義一個序列的價值\(val(B_1,B_2,...,B_{len})=\sum \limits_{i=1}^{len-1}gcd(B_i,B_{i+1})\)

對於\(m=1...n\),求\(1...m\)的所有子序列的價值的和。

  • \(1 \leq n \leq 5 \times 10^5\)
  • \(1 \leq a_i \leq 10^5\)

E sol

這個題賽後推式子沒退出來,還是cly告訴我尤拉反演,遂穿了。

考慮移動一位的影響,無論這一位是否選定\(1\sim i - 1\)的f都會\(\times 2\), 然後再加上第\(i\)位取的貢獻。考慮上一次取的是\(j\),貢獻位\(gcd(a_i,a_j)\times 2^j\)

thus

\[f_i = 2f_{i - 1} + \sum \limits_{j = 1}^{i - 1} 2^j gcd(a_i, a_j) \]

套路的會想到莫反,實際上這個題要用到尤拉反演。

反演的基本式是:\(\sum \limits_{d | n} \varphi(d) = n\)

這裡證略。考慮把gcd當作\(n\),展開:

\(f_i = 2f_{i - 1} + \sum \limits_{j = 1}^{i - 1} 2^j (\sum \limits_{d | gcd(a_i, a_j)} \varphi(d))\)

\(\ \ \ = 2f_{i - 1} + \sum \limits_{j = 1}^{i - 1} 2^j (\sum \limits_{d | a_i} \varphi(d)[d|a_j])\)

\(\ \ \ = 2f_{i - 1} + \sum \limits_{d | a_i} \varphi(d) \sum \limits_{j = 1}^{i - 1} 2^j [d|a_j]\)

後面這個東西是好維護的,在轉移\(f\)的時候維護一個\(g\)即可。詳見程式碼。

E code

好像哪個地方計了兩次還是怎麼樣,反正寫完發現都是兩倍懶得找原因了直接\(/2\)

完整程式碼


constexpr int N = 5e5 + 5;
constexpr int V = 1e5;

int n, a[N];

# include <atcoder/modint>
using mint = atcoder::modint998244353;

mint dp[N], num[N], pw2[N];
int p[N], prime[N], pcnt = 0, phi[N];
vector<int>muls[V + 5], divs[V + 5];

void init() {
	p[1] = 1; phi[1] = 1;
	rep(i, 2, V) {
		if (!p[i])p[i] = i, prime[++pcnt] = i, phi[i] = i - 1;
		for (int j = 1; j <= pcnt && i * prime[j] <= V; ++j) {
			p[i * prime[j]] = prime[j];
			if (i % prime[j] == 0) {
				phi[i * prime[j]] = phi[i] * prime[j];
				break;
			}
			phi[i * prime[j]] = phi[i] * (prime[j] - 1);
		}
	}
}

signed main() {
	// ios_base ::sync_with_stdio(false), cin.tie(nullptr), cout.tie(nullptr);
	init(); pw2[0] = 1; rep(i, 1, N - 5) pw2[i] = pw2[i - 1] + pw2[i - 1];
	read(n);
	rep(i, 1, V) rep(j, 1, V / i) muls[i].eb(i * j), divs[i * j].eb(i);
	rep(i, 1, n) read(a[i]);
	auto mx = *max_element(a + 1, a + n + 1);
	rep(i, 1, n) {
		dp[i] = 2 * dp[i - 1];
		if (i != 1) {
			for (auto fc : divs[a[i]])
				dp[i] += phi[fc] * num[fc];
		}
		if (i < n) {
			for (auto fc : divs[a[i]])
				num[fc] += pw2[i];
		}
	}
	rep(i, 1, n)writeln((dp[i] / 2).val());

#if defined(LOCAL) && !defined(CPH)
	std::cerr << "Spend Time : " << clock() * 1. / CLOCKS_PER_SEC * 1e3 << " ms \n";
#endif
	return 0;
}

相關文章