2024MX-MF-DAY1-text題解

Yantai_YZY發表於2024-08-06

T1

【題目描述】

\(n\) 個人按編號從 \(1\)\(n\) 坐成一圈,即第 \(i \in [1,n]\) 個人右邊是 \(i + 1\) ,第 \(n\) 個人右邊的人是 \(1\)

初始,每個人手上有 \(m\) 個球。隨後,\(n\) 個人按編號從小到大的順序依次執行如下操作:

  • 把自己手中的球分成數量相同且儘可能多的三份,扔掉剩餘的球,再把分得的三份分別給自己、左邊的人,以及右邊的人。

問所有人均操作完一次後,有球最多的人有幾個球。

【輸入格式】

從標準輸入讀入資料。

一行兩個整數 \(n\)\(m\)

【輸出格式】

輸出到標準輸出。

一行表示一個整數表示答案。

【資料範圍】

對於 \(50 \%\) 的資料: \(3 \le n,m \le 10\)
對於 \(70 \%\) 的資料: \(3 \le n,m \le 10^7\)
對於 \(100 \%\) 的資料: \(3 \le n,m \le 10^12\)

思路

賽時思路:

直接暴力模擬喜提 \(70pts\)

\(100pts\) 思路:

假設每個人手上的球顏色不同,那麼顯然每種顏色的球 最多延申(被傳遞) \(O(\log_3 m)\) 次(只能對(當前為 \(i\)\(O(i-\log_3 m)\sim O(i+\log_3 m)\) 產生影響),故從 \(O(\log m)\) 個人開始,手上的球的變化就是重複的了,模擬前 \(O(\log m)\) 個人即可。

點選檢視程式碼
#include <iostream>
using namespace std;
int main(){
	int n, m;
	cin >> n >> m;
	int x = m / 3, val = 0;
	for(int i = 2,last = m;i <= m;i++){
		int now = last / 3 + m;
		if(now == last || n == i){
			val = now;
			break;
		}
		last = now;
	}
	cout << x + (m + x) / 3 + (val + x) / 3 << '\n';
	return 0;
}

T2

折線(line)

【題目描述】

圖 1:

\[\huge{\text{xOy}} \]

如上,在平面直角座標系 \(\text{xOy}\) 中,有點 \(A_1(1, 0), A_2(1, 1), A_3(−1, 1), A_4(−1, −1), A_5(2, −1), \dots\),有 \(q\) 次詢問形如:

  1. 1 n,詢問點 \(A_n\) 的座標;
  2. 2 l r,詢問折線段 \(A_lA_{l+1}A_{l+2} . . . A_{r−1}A_{r}\) 的長度;

【輸入格式】
從標準輸入讀入資料。
第一行一個正整數 \(q\),表示詢問總數。
接下來 \(q\) 行,每行一個詢問形如 1 n2 l r

【輸出格式】
輸出到標準輸出。
對於每一個詢問,如果是 \(1\) 詢問,輸出一行兩個整數表示 \(A_n\)\(x\)\(y\) 座標,如果是 \(2\) 詢問,輸出一行一個整數表示折線長度。

【資料範圍】
對於 \(100\%\) 的資料,\(1\le n\le 10^9,1\le l < r\le 10^6,1\le q\le 10^5\)

思路

\(1\) 詢問分類討論,\(2\) 詢問不難發現到顯然線段長度是兩個等差數列求和直接預處理即可。

點選檢視程式碼
#include <iostream>
using namespace std;
long long x[1000000];
int main() {
    int n;
    cin >> n;
    for (int i = 1; i < 1000000; i++) {
        if (i % 2 == 1) {
            x[i] = x[i - 1] + (i + 1) / 2;
        } else {
            x[i] = x[i - 1] + i / 2 + 1;
        }
    }
    for (int i = 1; i <= n; i++) {
        int a, b, c;
        cin >> a;
        if (a == 1) {
            cin >> b;
        	if (b % 4 == 1) {
                cout << b / 4 + 1 << " " << (b / 4 + 1) * (-1) + 1 << endl;
            } else if (b % 4 == 2) {
                cout << b / 4 + 1 << " " << b / 4 + 1 << endl;
            } else if (b % 4 == 3) {
                cout << (b / 4 + 1) * (-1) << " " << b / 4 + 1 << endl;
            } else {
                cout << (b / 4) * (-1) << " " << (b / 4) * (-1) << endl;
            }
        } else {
            cin >> b >> c;
            cout << x[c - 1] - x[b - 1] << endl;
        }
    }
    return 0;
}

T3

STL

【題目描述】
\(n\) 個初始由空格隔開的字串 \(s_1, s_2,\dots, s_n\),每一個均為 \(\texttt{int}\)\(\texttt{vector}\)\(\texttt{pair}\) 之一。

現在他希望在字串之間以及最後一個字串之後的空格中添上 \(<\)\(>\)\(,\)(可以不填),使得現在整個字串可以代表一個 \(\text{C++14}\) 中的合法資料型別。
例如:
\(\texttt{int}\) 本身合法。

\(\texttt{vector int}\) 可以變成 vector 使之合法。

\(\texttt{pair int int}\) 可以變成 pair<int,int> 使之合法。

\(\texttt{pair vector pair int vector int pair vector int int}\) 可以變成
\(\texttt{pair<vector<pair<int,vector<int>>>,pair<vector<int>,int>>}\) 使之合法。

小 L 對此感到好奇,於是他給你 \(m\) 個操作,每個操作形如:
l r op:詢問 \([l, r]\) 是否可以透過執行上述操作得到一個合法的資料型別。若 \(op = 1\) 且有解,你還要給出一種方案。若有多解,你可以輸出任意一種。
【輸入格式】
從標準輸入讀入資料。
本題包含多組資料。
第一行兩個正整數 \(n, m\)
接下來一行 \(n\) 個用空格隔開的字串 \(s_1, s_2,\dots, s_n\)
接下來 \(m\) 行,每行三個整數 \(l, r, op\)
【輸出格式】
輸出到標準輸出。
對每個詢問,首先輸出一行 YesNo 表示解是否存在。若該詢問對應的 \(op = 1\),你還需要再輸出一行代表一組解,若有多解,你可以輸出任意一種。
【資料範圍】
對於 \(100\%\) 的資料,\(1\le n\),\(m\le 10^6\)\(s_i\)\(\texttt{int vector pair}\) 中的一種,\(1\le l\le r\le n\)\(op \in\{0, 1\}\)\(op = 1\) 且有解時的 \(\sum(r − l + 1)\le 10^6\)

思路

首先我們注意到這個 SPJ 是在詐騙。

注意到 \(\texttt{int}、\texttt{vector}、\texttt{pair}\) 的合法組合事實上是一個字尾表示式,其中 \(\texttt{int}\) 為運算元,\(\texttt{vector}\) 為一元運算子, \(\texttt{pair}\) 為二元運算子。於是合法時方案唯一。

那我們如何構造一個合法方案呢?

看到字尾表示式,我們考慮用一個棧來記錄資訊。棧中每個元素表示一個區間 \([L,R]\),表示區間 \([L,R]\) 是一個合法的字尾表示式。每次從右往左掃,遇到 \(\texttt{int}\) 直接將 \([i,i]\) 入棧;遇到 \(\texttt{vector}\) 將棧頂的彈出並放入 \([i,R]\),在 \(i,i+1\) 之間放入一個 <,再在 \(R,R+1\) 之間放入一個 >;遇到 \(\texttt{pair}\) 將棧頂的 \([i+1,R_1][R_1,R_2]\) 彈出並放入 \([i,R_2]\),在 \(i,i+1\) 之間放入一個 <,在 \([R_1,R_1+1]\) 之間放入一個 ,,最後在 \([R_2,R_2+1]\) 之間放入一個 > 即可。

以上任何一步需要彈棧時棧空或最終棧中元素個數不為 \(1\) 時無解。

那要是我們不需要構造方案呢?

現在依次考慮上述無解的兩個條件。

最終棧中元素個數:注意到每遇到一個 \(\texttt{int}\),棧中元素個數 \(+1\) ;每遇到一個 \(\texttt{vector}\),棧中元素個數不變;每遇到一個 \(\texttt{pair}\),棧中元素個數 \(-1\)

於是我們預處理一個每個位置對棧中元素個數貢獻的字首和 \(sum\),每次判斷 \(sum_r-sum_{l-1}\) 是否為 \(1\) 即可。

彈棧時棧不空:對於所有 \(\texttt{vector}\) 出現的位置,我們需要字尾和 \(\ge 1\);對於所有 \(\texttt{pair}\) 出現的位置,我們需要字尾和 \(\ge 2\)

當然,我們可以運用 ST 表等 RMQ 演算法求出區間字尾和最小值,但是超綱了。

對於 \(\texttt{vector}\) 的條件,我們需要知道 \([l-1,r-1]\) 中的 \(sum_i\) 中是否有 \(\ge sum_r\) 者——即 \(<r\) 的第一個
滿足 $ >sum_r$ 的位置是否不存在或 \(<l-1\);對於 \(\texttt{pair}\) 的條件,我們需要知道 \([l-1,r-1]\) 中的 \(sum_i\)
中是否有 $ >sum_r$ 者——即 \(<r\) 的第一個滿足 $ >sum_r$ 的位置是否不存在或 \(<l-1\)

分別排序後離線連結串列即可。時間複雜度為 \(O(n \log n + m)\)

點選檢視程式碼
#include<bits/stdc++.h>
using namespace std;
int n,m,c[1000005],pre[1000005],Log[1000005],st[22][1000005],l,r,op;char s[15];
struct Link
{
	int pre,nxt;char c;
}a[10000005];
int query(int l,int r)
{
	if(l>r)return 0x3f3f3f3f;
	int k=Log[r-l+1];
	return min(st[k][l],st[k][r-(1<<k)+1]);
}
int main()
{
	cin.tie(0)->sync_with_stdio(0);
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	{
		cin>>s;
		if(s[0]=='i')c[i]=1;
		else if(s[0]=='v')c[i]=2;
		else c[i]=3;
	}
	for(int i=1;i<=n;i++)pre[i]=pre[i-1]+(c[i]==1?-1:(c[i]==2?0:1));
	for(int i=1;i<=n;i++)st[0][i]=pre[i];
	for(int i=1;(1<<i)<=n;i++)
	{
		for(int j=1;j+(1<<i)-1<=n;j++)st[i][j]=min(st[i-1][j],st[i-1][j+(1<<(i-1))]);
	}
	for(int i=2;i<=n;i++)Log[i]=Log[i>>1]+1;
	while(m--)
	{
		cin>>l>>r>>op;
		if(pre[r]-pre[l-1]!=-1||query(l,r-1)-pre[l-1]<=-1)cout<<"No\n";
		else 
		{
			cout<<"Yes\n";
			if(op==1)
			{
				stack<pair<int,int> >st;
				int tot=0;
				for(int i=r;i>=l;i--)
				{
					if(c[i]==1)
					{
						a[++tot].c='i';a[++tot].c='n';a[++tot].c='t';
						for(int j=0;j<3;j++)a[tot-j].pre=tot-j-1,a[tot-j].nxt=tot-j+1;
						if(st.size())a[tot].nxt=st.top().first;
						else a[tot].nxt=0;
						a[tot-2].pre=0;
						st.push(make_pair(tot-2,tot));
					}
					else if(c[i]==2)
					{
						int l=st.top().first,r=st.top().second;st.pop();
						a[++tot].c='v';a[++tot].c='e';a[++tot].c='c';
						a[++tot].c='t';a[++tot].c='o';a[++tot].c='r';a[++tot].c='<';
						for(int j=0;j<7;j++)a[tot-j].pre=tot-j-1,a[tot-j].nxt=tot-j+1;
						a[tot-6].pre=0;a[tot].nxt=l;a[l].pre=tot;
						a[++tot].c='>';
						a[tot].pre=r;a[tot].nxt=a[r].nxt;a[r].nxt=tot;
						st.push(make_pair(tot-7,tot));
					}
					else 
					{
						int pl=st.top().first,pr=st.top().second;st.pop();
						int ql=st.top().first,qr=st.top().second;st.pop();
						a[++tot].c='p';a[++tot].c='a';a[++tot].c='i';a[++tot].c='r';a[++tot].c='<';
						for(int j=0;j<5;j++)a[tot-j].pre=tot-j-1,a[tot-j].nxt=tot-j+1;
						a[tot-4].pre=0;a[tot].nxt=pl;a[pl].pre=tot;
						a[++tot].c=',';
						a[tot].pre=pr;a[tot].nxt=ql;a[pr].nxt=tot;a[ql].pre=tot;
						a[++tot].c='>';
						a[tot].pre=qr;a[tot].nxt=a[qr].nxt;a[qr].nxt=tot;
						st.push(make_pair(tot-6,tot));
					}
				}
				for(int i=st.top().first;i;i=a[i].nxt)cout<<a[i].c;
				for(int i=1;i<=tot;i++)a[i].pre=a[i].nxt=a[i].c=0;
				cout<<'\n';
			}
		}
	}
	return 0;
}

T4

【題目描述】

現在有 \(n\) 個白色的球串成了一個圓環,編號依次為 \(1,2,3,\dots,n\),現在你需要把這些球都染黑,具體操作為:

1.等機率隨機地選擇一個之前從未選過的球(白球、黑球都可以)。

2.將該球和它相鄰的兩個球都染黑。

3.如果所有白球都染黑,那麼結束。

求出染黑所有白球的期望步數,答案對 \(998244353\) 取模。

【輸入格式】
一行一個正整數 \(n\),表示白球的個數。

【輸出格式】
一行一個數字,表示期望步數,對 \(998244353\) 取模。

【資料範圍與提示】
對於 \(100\%\) 的資料,\(3 < n < 5000\)

思路

\(100pts\) 需要高中乃至大學知識,我只會 \(40pts\) 的暴力搜尋。

點選檢視程式碼
#include <bits/stdc++.h>
using namespace std;
const int mod = 998244353;
int Pow(int a, int b) {
    int res = 1;
    while (b) {
        if (b & 1)
            res = 1LL * res * a % mod;
        a = 1LL * a * a % mod;
        b >>= 1;
    }
    return res;
}
int a[15], f[15];
int n;
int sum = 0;
signed main() {
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin >> n;
    for (int i = 1; i <= n; i++) f[i] = i;
    do {
        for (int i = 1; i <= n; i++) a[i] = 0;
        int cnt = 0;
        for (int i = 1; i <= n; i++) {
            int now = f[i];
            int pre = ((now == 1) ? n : (now - 1));
            int nxt = ((now == n) ? 1 : (now + 1));
            cnt += (!a[pre]);
            a[pre] = 1;
            cnt += (!a[now]);
            a[now] = 1;
            cnt += (!a[nxt]);
            a[nxt] = 1;
            if (cnt == n) {
                sum = (sum + i) % mod;
                break;
            }
        }
    } while (next_permutation(f + 1, f + n + 1));
    int s = 1;
    for (int i = 1; i <= n; i++) s = 1LL * s * i % mod;
    cout << 1LL * sum * Pow(s, mod - 2) % mod;
    return 0;
}