1. 程式人生 > 實用技巧 >2020-12-2 考試總結

2020-12-2 考試總結

有史以來爆的最慘的一次,這個故事告訴我們一定要對拍!!!特別是自己沒有把握的題目!!!

T1 bricks

題目傳送門

Solution

直接三分即可,有平臺的話就說明是答案了。

但是考試寫的貪心不知道為什麼掛了,似乎mys學長跟我寫的思路一樣但是他過了,我保齡了。。。

T2 二分圖染色

題目傳送門

Description

給定一個完全二分圖,圖的左右兩邊的頂點數目相同。我們要把圖中的每條邊染成紅色、藍色、或者綠色,並使得任意兩條紅邊不共享端點、同時任意兩條藍邊也不共享端點。計算所有滿足條件的染色的方案數,並對 \(10^{9}+7\) 取模。

\(n\le 10^7\)

Solution

考試的時候推出來的式子似乎並沒有辦法優化到 \(\Theta(n)\)

,大概長成這個樣子:

\[\sum_{k=0}^{n}(-1)^k(\sum_{i=k}^{n}\binom{i}{k}\binom{n}{i}^2\times i!)(\sum_{i=0}^{n-k}\binom{n-k}{i}^2\times i!) \]

有優化方法的大佬可以幫一下我這個小蒟蒻。


我們可以發現,這個問題實際上就是在一個 \(n\times n\) 的棋盤,放“車”,使得不同顏色的車不攻擊,且一個點只能放一個,問合法方案數。

不難發現在只有一種顏色的情況下,答案就是:

\[f(n)=\sum_{i=0}^{n}\binom{n}{i}^2\times i! \]

其中 \(f(n)\)

表示棋盤大小為 \(n\) 的方案數。

但是現在有兩種顏色,所以我們需要考慮容斥。

我們可以列舉有多少個點是相同的,那麼答案就是:

\[\sum_{k=0}^{n}(-1)^k\binom{n}{k}^2\times k!\times f(n-k)^2 \]

於是問題就是如何求出 \(f(x)\)。不難想到遞推。

對於新增加的一行一列有 \(2n\) 種選法(選 \(2n-1\) 個格子以及不選),但是你發現這樣會算重,因為可能選到行和列上的點,兩者不影響就會算重。所以你可以列舉這個兩個點覆蓋範圍的交點,所以可以得到遞推式

\[f(n)=2n\times f(n-1)-(n-1)^2\times f(n-2) \]

時間複雜度 \(\Theta(n)\)

Code

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

#define Int register int
#define mod 1000000007
#define MAXN 10000005

//char buf[1<<21],*p1=buf,*p2=buf;
//#define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}

int n,ans,f[MAXN],fac[MAXN],ifac[MAXN];
int mul (int a,int b){return 1ll * a * b % mod;}
int dec (int a,int b){return a >= b ? a - b : a + mod - b;}
int add (int a,int b){return a + b >= mod ? a + b - mod : a + b;}
int qkpow (int a,int b){int res = 1;for (;b;b >>= 1,a = mul (a,a)) if (b & 1) res = mul (res,a);return res;}
int binom (int a,int b){return a >= b ? mul (fac[a],mul (ifac[b],ifac[a - b])) : 0;}
int sqr (int x){return mul (x,x);}

signed main(){
	read (n);
	fac[0] = 1;for (Int i = 1;i <= n;++ i) fac[i] = mul (fac[i - 1],i);
	ifac[n] = qkpow (fac[n],mod - 2);for (Int i = n;i;-- i) ifac[i - 1] = mul (ifac[i],i);
	f[0] = 1,f[1] = 2;for (Int i = 2;i <= n;++ i) f[i] = dec (mul (2 * i,f[i - 1]),mul (sqr (i - 1),f[i - 2]));
 	for (Int i = 0,tmp;i <= n;++ i)
		tmp = mul (sqr (f[n - i]),mul (fac[i],sqr (binom (n,i)))),
		ans = i & 1 ? dec (ans,tmp) : add (ans,tmp);
	write (ans),putchar ('\n');
	return 0;
}

T3 [eJOI2018]迴圈排序

題目傳送門

Description

給定一個長為 \(n\) 的數列 \(\{a_i\}\) ,你可以多次進行如下操作:
選定 \(k\) 個不同的下標 \(i_1, i_2, \cdots, i_k\)(其中 \(1 \le i_j \le n\)),然後將 \(a_{i_1}\) 移動到下標 \(i_2\) 處,將 \(a_{i_2}\) 移動到下標 \(i_3\) 處,……,將 \(a_{i_{k-1}}\) 移動到下標 \(i_{k}\) 處,將 \(a_{i_k}\) 移動到下標 \(i_1\) 處。

換言之,你可以按照如下的順序輪換元素:\(i_1 \rightarrow i_2 \rightarrow i_3 \rightarrow \cdots \rightarrow i_{k-1} \rightarrow i_k \rightarrow i_1\)

例如:\(n=4, \{a_i\}=\{ 10, 20, 30, 40\}, i_1=2, i_2=3, i_3=4\) ,則操作完成後的 \(a\) 數列變為 \(\{ 10, 40, 20, 30\}\)

你的任務是用操作次數最少的方法將整個數列排序成不降的。注意,所有操作中選定下標的個數總和不得超過 \(s\) 。如果不存在這樣的方法(無解),輸出 \(\texttt{-1}\)

\(n\le 2\times 10^5\)

Solution

先考慮全排列的情況,可以發現肯定就是每個點的值往它應該在的位置對應的值連邊,可以發現的是,肯定是一個又一個的環。然後如果你直接暴力刪環,是不行的。因為你可以把環連起來,然後把每個環的末端串起來再做一次就可以了。

考慮拓展到有重複元素的陣列,你直接每一個點的權值往這個位置應該對應的值連邊即可,然後跑歐拉回路,一個連通塊就是一個操作。考慮合併操作,不難發現你合併 \(s\) 個連通塊,那麼涉及到的點的數量就會增加 \(s\) 個,所以你可以合併的連通塊數量是一定的,你直接合並就行了。

時間複雜度 \(\Theta(n+m)\)

Code

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

#define Int register int
#define MAXN 200005

template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}

int n,s,un,tot,sum,a[MAXN],b[MAXN],tmp[MAXN];

struct node{
	int v,w;
};
vector <node> G[MAXN];

void Add_Edge (int u,int v,int w){
//	cout << u << " -> " << v << ": " << w << endl;
	G[u].push_back (node {v,w});
}
bool vis[MAXN];
vector <int> ans[MAXN];
void dfs (int u){
	vis[u] = 1;
	while (!G[u].empty()){
		int v = G[u].back().v,w = G[u].back().w;
		G[u].pop_back (),dfs (v),ans[tot].push_back (w);
	}
}

signed main(){
	read (n),read (s);
	for (Int i = 1;i <= n;++ i) read (a[i]),tmp[i] = a[i];
	sort (tmp + 1,tmp + n + 1);for (Int i = 1;i <= n;++ i) b[i] = (tmp[i] == tmp[i - 1] ? b[i - 1] : b[i - 1] + 1);
	un = unique (tmp + 1,tmp + n + 1) - tmp - 1; 
	for (Int i = 1;i <= n;++ i) a[i] = lower_bound (tmp + 1,tmp + un + 1,a[i]) - tmp;
	for (Int i = 1;i <= n;++ i) if (a[i] ^ b[i]) Add_Edge (a[i],b[i],i);tot = 1;
	for (Int i = 1;i <= un;++ i) if (!vis[i]){
		dfs (i);
		if (ans[tot].size()) sum += ans[tot].size(),tot ++;
	}
	-- tot;
	/*cout << tot << " " << sum << endl;
	for (Int i = 1;i <= tot;++ i){
		for (Int j = 0;j < ans[i].size();++ j) cout << ans[i][j] << " ";cout << endl;
		cout << " ----------- " << endl;
	}*/
	if (sum > s) return puts ("-1"),0;
	else if (s - sum <= 1 || tot <= 1){//不能合併環的情況 
		write (tot),putchar ('\n');
		for (Int i = 1;i <= tot;++ i){
			write (ans[i].size()),putchar ('\n');
			for (Int j = 0;j < ans[i].size();++ j) write (ans[i][j]),putchar (' ');
			putchar ('\n');
		}
		return 0;
	}
	else{
		int anst = tot - min (tot,s - sum) + 2;
		write (anst),putchar ('\n');
		for (Int i = 1;i <= anst - 2;++ i){
			write (ans[i].size()),putchar ('\n');
			for (Int j = 0;j < ans[i].size();++ j) write (ans[i][j]),putchar (' ');
			putchar ('\n');
		}
		vector <int> tmp[2];int lst = -1;
		for (Int i = tot;i >= anst - 1;-- i) tmp[0].push_back (ans[i][ans[i].size() - 2]),swap (ans[i].back(),lst);
		ans[tot].back() = lst;
		for (Int i = anst - 1;i <= tot;++ i)
			for (Int j = 0;j < ans[i].size();++ j)
				tmp[1].push_back (ans[i][j]);
		for (Int i = 0;i < 2;++ i){
			write (tmp[i].size()),putchar ('\n');
			for (Int j = 0;j < tmp[i].size();++ j) write (tmp[i][j]),putchar (' ');
			putchar ('\n');
		}
		return 0;
	}
	return 0;
}

T4 「BalkanOI 2018 Day2」Parentrises

題目傳送門

Description

「括號串」是一個僅由 () 構成的字串。如果在括號串中插入一些 1+ 可以將其轉化為正確的表示式,該字串就是一個「良括號串」。例如,(())(()) 是良括號串,而 )(( 不是。空字串可視為良括號串。(就是你們學 Catalan 數時學的那個啊)
將一個括號串(不是良括號串)的每個括號都塗成紅綠藍三種顏色之一,如果有一種方案同時滿足:

  • 忽略該串的所有藍色括號後它是良括號串
  • 忽略該串的所有紅色括號後它是良括號串;

該串就是 RGB 可讀的。

你會接到兩類任務之一。任務型別用一個整數 \(P\) 表示,\(P=1\)\(2\)

  • \(P=1\):你會接到 \(T\) 組詢問,每組詢問包含一個括號串,試問該串是否 RGB 可讀,如果是,請輸出一種染色方案,如果否請輸出 impossible
  • \(P=2\):你會接到 \(T\) 組詢問,每組詢問包含一個數 \(N\),試求:有多少個長度為 \(N\) 的 RGB 可讀的良括號串。輸出答案模 \((10^9+7)\) 的結果。

Solution

以下 \(l,r\) 分別表示左右括號的個數

這裡只討論 \(P=2\)

首先可以看出,一個括號串能夠滿足條件當且僅當所有字首中右括號數量小於等於左括號×2,所有後綴中,左括號個數小於等於右括號數×2。

然後你設 \(f_{i,j,k}\) 表示前面 \(i\) 個括號,有 \(j\) 個左括號,\(2r-l\) 的最大值為 \(k\)。那麼可以產生貢獻的情況當且僅當 \(2(i-j)-j-k\ge 0\)。轉移顯然。

Code

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

#define Int register int
#define mod 1000000007
#define MAXN 605

template <typename T> inline void read (T &t){t = 0;char c = getchar();int f = 1;while (c < '0' || c > '9'){if (c == '-') f = -f;c = getchar();}while (c >= '0' && c <= '9'){t = (t << 3) + (t << 1) + c - '0';c = getchar();} t *= f;}
template <typename T> inline void write (T x){if (x < 0){x = -x;putchar ('-');}if (x > 9) write (x / 10);putchar (x % 10 + '0');}

int ans[305],dp[2][305][MAXN]; 
void Add(int &a,int b){a = a + b >= mod ? a + b - mod : a + b;}

void init (){
	dp[0][0][0] = 1;
	for (Int i = 0;i <= 300;++ i){
		memset (dp[i + 1 & 1],0,sizeof (dp[i + 1 & 1]));
		for (Int j = 0;j <= i;++ j)
			for (Int k = 0;k <= 600;++ k)
				if (!dp[i & 1][j][k]) continue;
				else{
					int l = j,r = i - j,f = dp[i & 1][j][k];
					if (2 * r - l - k >= 0) Add (ans[i],dp[i & 1][j][k]);
					if (2 * l - (r + 1) >= 0) Add (dp[i + 1 & 1][j][max (k,(r + 1) * 2 - l)],f);
					Add (dp[i + 1 & 1][j + 1][max (k,r * 2 - (l + 1))],f);
				} 
	}
}

signed main(){
	init ();
	int P;read (P);
	if (P == 2){
		int t;read (t);
		while (t --> 0){
			int n;read (n);
			write (ans[n]),putchar ('\n');
		}
		return 0;
	}
	return 0;
}