「模擬賽」暑期集訓CSP提高模擬6(7.23)

_yuen發表於2024-07-25

\(140 pts,Rank 23\)

題目列表

A.花間叔祖
B.合併 r
C.回收波特
D.斗篷


花間叔祖

\(98pts\)

題意:

給定一個陣列,選擇一個大於等於 2 的模數,然後把陣列中的數變成 \(mod\) 該模數後的數。

只能操作一次,問操作後最少有幾種不同的數。

賽事分析:

開始 5 分鐘想到了算 \(a_i\) 中所有數的最小質因數,若不是 1,那麼答案為 1,否則為 2,覺得很對,測大樣例,WA,??自己覺得思路很對,由於大樣例鍋了很多次了,所以懷疑是不是大樣例的問題,就先跳了,等著看改不改大樣例。半個小時之後,回來,還沒改,這應該不是大樣例的問題了,於是拿大樣例除錯,果然發現了可以使答案為 1,想法假了!於是自己手模小樣例發現問題所在,列出式子,直接出來了。結果少考慮一個點,導致沒拿滿.

正解:

容易得到答案不是 1 就是 2,若存在一個模數使得 \(a_i(i\in[1,n])\) mod 該數都為 \(d\),那麼有 \(k_i*x+d=a_i\)\(x\) 為模數),那麼 \(a_i-a_j \ (a_i>a_j)\) 化簡可以得到 \(a_i-a_j=(k_i-k_j)*x\),那麼我們只要拿 \(a\) 陣列中的數兩兩作差得到 \(b\) 陣列判有無公因數即可。

賽時我拿 \(a\) 陣列排序後相鄰的兩個數相減沒考慮可能為 0 的情況所以掛了 2pts。

code:

#include<bits/stdc++.h>
#define mp make_pair
#define ll long long
using namespace std;

const int N = 2e5 + 10;

int n, a[N], Ygcd, cnt, Xgcd;
int b[N];

signed main(){
	// freopen("in.in", "r", stdin); freopen("out.out", "w", stdout);

	scanf("%d%d%d", &n, &a[1], &a[2]);
	
	Ygcd = __gcd(a[1], a[2]);
	for(int i=3; i<=n; i++){
		scanf("%d", &a[i]);
		Ygcd = __gcd(Ygcd, a[i]);
	}

	if(Ygcd != 1){
		puts("1"); return 0;
	}

	sort(a+1, a+1+n);
	for(int i=2; i<=n; i++){
		b[++cnt] = a[i] - a[1];
	}

	Xgcd = __gcd(b[1], b[2]);
	for(int i=2; i<=cnt; i++){
		Xgcd = __gcd(Xgcd, b[i]);
	}

	if(Xgcd != 1) puts("1");
	else puts("2");

	return 0;
}

合併 r

題意:

\(n\) 個神奇的 \(r\),每個 \(r\) 有一個美味值,美味值都能表示為 \(\frac{1}{2^i}\),其中 \(i∈N\)(自然數集)。

Delov 一口吃掉了美味值之和為 \(k\)\(n\)\(r\), 現在他想知道每個 \(r\) 的美味值,但是可能的結果太多了,於是他只想知道有多少種可能性,你只需要告訴他方案數  \(mod 998244353\) 的結果。

兩種方案不同,則存在 \(i\), 使得美味值為 \(\frac1{2^i}\)\(r\) 的個數在兩種方案中不同。

正解:

原題 \(ARC107D\)

\(f_{i, j}\) 表示 \(i\) 個數和為 \(j\) 的方案數

我們這樣考慮,\(\frac{1}{2^i} = 1 / 2 / 2 ....\)

就是說,當前如果湊成了 \(j\),那麼所有數除以二,就能湊成 \(j / 2\)

那麼我們只需兩種操作,加一與除二,加一代表多選了一個數(初始為 \(1\) ),除二代表將原方案選的數中二的冪次都加一

\(f_{i, j} = f_{i - 1, j - 1} + f_{i, j * 2}\)

初始化 \(f_{0,0} = 0\)

code:

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

const int N = 5e3 + 10;
const int p = 998244353;

int n, k;
int f[N][N];

int main(){
	// freopen("in.in", "r", stdin); freopen("out.out", "w", stdout);
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	cin>>n>>k;

	f[0][0] = 1;
	for(int i=1; i<=n; i++){
		for(int j=i; j>=1; j--){
			f[i][j] = f[i-1][j-1];
			if(j * 2 <= i) f[i][j] += f[i][j*2];
			f[i][j] %= p;
		}
	}
	cout<<f[n][k];

	return 0;
}

回收波特:

題意:

Delov 不和 npy、 們玩了,這回他和他的波特們一起玩。

他和他的 \(n\) 個波特們在一條道路上的不同地點,不妨認為這是一條數軸,而 Delov 位於原點。不幸的是波特們都快沒電了,現在 Delov 要開始回收波特了。

Delov 只有一個控制所有波特的遙控器,所以每次他會給出一個引數 \(di\) ,波特就會向著目前 Delov 相對它所在的方向移動 \(di\) 的單位長度,恰好到達原點的波特會被回收不再移動。

在傳送了 \(m\) 條指令後波特們沒電了,現在 Delov 想知道每個波特是否被回收,如果被回收,他想知道該波特是在第幾個指令後被回收,便於充電;如果沒被回收,他想知道該波特最終處於的位置,便於手動回收。

你能幫幫他嗎?

賽時分析:

一開始一眼就覺得會了,二分思路,時間複雜度正確,然後大喊一句“正解”開打,打了一半二分發現假了。。。於是就先按暴力 \(30pts\) 打,在打假的二分上套了一層二分,最優情況 \(O(m \log^2n)\),最壞 \(O(mn\log\ n)\),打完怕以為時間內複雜度不穩定連該拿的暴力分都拿不到,於是加了個 if(n*m<=200000000)然後跟暴力程式碼,否則跟玄學複雜度程式碼,最終 \(32 pts\),賽後交上只有玄學複雜度程式碼 \(42pts\),。。。

正解:

(複製的下發題解思路較簡單)

官方題解寫的很棒,但是是英語,英語好的/使用翻譯的同學可以移步

粘個官方的圖

image

注意到值域有限,考慮對值域內所有數處理出答案

觀察一次操作過後,一部分超過原點,一部分到達原點,一部分沒過原點,

到達的不用管而超過的部分,後續操作和關於原點對稱的位置的操作一模一樣,於是可以合併

每次將正半軸負半軸較短的一截與另一邊合併起來

這樣每個點只會操作 \(o(1)\) 次,複雜度就可以保證了。

code:

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

const int N = 1e6 + 10;

int n, m, vis[N], ans[N];
int x[N], d[N];
std::vector<int>v[N], G;

void dfs(int x, int flag){
	for(auto y : v[x]){
		vis[y] = vis[x], ans[y] = -1.0 * flag * ans[x];
		dfs(y, flag);
	}
}

int main(){
	// freopen("in.in", "r", stdin); freopen("out.out", "w", stdout);
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);

	cin>>n>>m;
	for(int i=1; i<=n; i++) cin>>x[i];
	for(int i=1; i<=m; i++) cin>>d[i];


	int l = 1, r = N - 10, pos = 0, f = 1;//f=1:0 in the left
	for(int i=1; i<=m; i++){
		int D = d[i];
		pos += f * D; // now 0's position
		
		if(pos > r) f = -1;
		else if(pos < l) f = 1;
		else{
			vis[pos] = true, ans[pos] = i;
			G.push_back(pos);

			int lnum = pos - l, rnum = r - pos;
			if(lnum > rnum){
				for(int j=1; j<=r-pos; j++) //connect the side
					v[pos-j].push_back(pos+j);
				r = pos - 1, f = -1;
			}
			else{
				for(int j=1; j<=pos-l; j++)
					v[pos+j].push_back(pos-j);
				l = pos + 1, f = 1;
			}
		}
	}

	for(int i=l; i<=r; i++){
		ans[i] = i - pos;
		dfs(i, 1);
	}

	for(int i : G) dfs(i, -1);
	for(int i=1; i<=n; i++){
		if(vis[x[i]]) printf("Yes");
		else printf("No");
		printf(" %d\n", ans[x[i]]);
	}

	return 0;
}

斗篷

相關文章