CF963-Div2-E. Xor-Grid Problem

superl61發表於2024-11-06

CF963-Div2-E. Xor-Grid Problem

題意

給定一個 \(n \times m\) 的矩陣 \(a\),有兩種操作:

  • 選擇一,把每個數變成所在所有數的異或之和。
  • 選擇一,把每個數變成所在所有數的異或之和。

求進行任意次操作後整個矩陣最小的美麗值。

思路

第一個發現:同一數異或兩次相當於沒有異或,基於這個性質,我們首先可以發現連續兩次對同一行(列)操作相當於沒有操作。

第二個發現:能替換的值其實是很有限的,因為它是由一行或一列所有值的異或一起決定的,我們會忍不住把所有可供替換的值規規整整地寫在對應行(列)前面,以樣例中的第 \(4\) 組資料為例,我們把異或值寫在第 \(0\) 行和第 \(0\) 列,可以得到:

\[\begin{matrix} 1 & 2 & 15 & 12 \\ 0 & 1 & 2 & 3 \\ 7 & 4 & 5 & 6 \\ 6 & 7 & 8 & 9 \\ \end{matrix} \]

你仔細觀察這個矩陣或者手動操作幾次,可以發現對某行(列)操作相當於和第 \(0\) 行(列)整體交換。

所以題目就轉化成了:給定一個 \((n + 1) \times (m + 1)\) 的矩陣 \(a\),你可以不斷地交換某一行和第 \(0\) 行,或者某一列和第 \(0\) 列,求最終矩陣(不包含第 \(0\) 行和第 \(0\) 列)最小的美麗值。

實現

交換次數是無限的,但矩陣的狀態是有限的。

樸素想法是列舉所有的狀態,全部求一遍美麗值。

肯定要列舉哪行哪列不選,還要列舉每行每列的排列方式,最後對整個矩陣求答案。所以直接做的話應該是 \(O(nm \times 2^{n + m} \times nm)\),即 \(O((nm)^22^{n + m})\)

首先可以發現其實行和列的貢獻是分別獨立的。以行為例,如果行的排列方式確定了,那豎著的貢獻就是固定的。這是由操作方式決定的,題目給出的兩種操作能保證初始同行(列)的元素到最後仍然同行(列),變的只是行之間和列之間的順序。複雜度變為 \(O((nm)^2(2^nm + 2^mn)\)

繼續最佳化答案統計。每次都重新求一遍整個矩形的貢獻實在是太不划算了。又想到對於一個選行(列)的集合 \(S\),可以預先把最優的排列方式求出來,最後直接查。考慮狀壓 dp。

\(f_{S, i, j}\) 表示選的集合為 \(S\),最後一\(i\),不選第 \(j\) ,最小美麗值。

\(g_{S, i, j}\) 表示選的集合為 \(S\),最後一\(i\),不選第 \(j\) ,最小美麗值。

有轉移

\[f_{S, i, j} = \min_{k \in S,k \ne i} f_{S \setminus \{i\}, k, j} + sum_{i, k} - \left| a_{i, j} - a_{k, j}\right| \]

其中 \(sum_{i, k}\) 是某兩行之間的總貢獻。

\(g_{S, i, j}\) 轉移同理。

最後答案就是

\[\min_{0 \le i \le n, 0 \le j \le m}(\min_{k \ne i}f_{U_1 \setminus \{i\}, k, j} + \min_{k \ne j}g_{U_2 \setminus \{j\}, k, i}) \]

\(U_1, U_2\) 分別是行和列的全集。

時間複雜度 \(O(2^nn^2m + 2^mm^2n)\)

注意 dp 三層迴圈的順序,先列舉 \(S\),再列舉 \(i\),因為 \(S\) 是從小的更新到大的,而 \(i\) 是每一輪都列舉全部。

#include<bits/stdc++.h>
#define F(i,l,r) for(int i(l);i<=(r);++i)
#define G(i,r,l) for(int i(r);i>=(l);--i)
using namespace std;
using ll = long long;
const int inf = 0x3f3f3f3f;
int f[66000][18][18], g[66000][18][18];
int s[18][18], ss[18][18], a[18][18];
int T, n, m;
void init(){
	cin >> n >> m;
	F(s, 0, (1 << (n + 1)) - 1) F(i, 0, n) F(j, 0, m) f[s][i][j] = inf;
	F(s, 0, (1 << (m + 1)) - 1) F(i, 0, m) F(j, 0, n) g[s][i][j] = inf;
	F(i, 0, n) F(j, 0, n) s[i][j] = 0;
	F(i, 0, m) F(j, 0, m) ss[i][j] = 0; 
	
	
	F(i, 1, n) F(j, 1, m) cin >> a[i][j];
	F(j, 1, m){
		a[0][j] = 0;
		F(i, 1, n) a[0][j] ^= a[i][j];
	}
	F(i, 0, n){
		a[i][0] = 0;
		F(j, 1, m) a[i][0] ^= a[i][j];
	} // ok
	F(i, 0, n) F(k, i + 1, n) {
		s[i][k] = 0;
		F(j, 0, m) s[i][k] += abs(a[i][j] - a[k][j]);	
		s[k][i] = s[i][k];
	}
	
	F(i, 0, m) F(k, i + 1, m) {
		ss[i][k] = 0;
		F(j, 0, n) ss[i][k] += abs(a[j][i] - a[j][k]);
		ss[k][i] = ss[i][k];
	}
	
}
int solve(){
	F(i, 0, n) F(j, 0, m) f[1 << i][i][j] = 0; // 只考慮行 
	F(j, 0, m) F(i, 0, n) g[1 << j][j][i] = 0; // 只考慮列 
	F(j, 0, m){
		F(S, 1, (1 << (n + 1)) - 1){
			F(i, 0, n){
				if(!((S >> i) & 1) || f[S][i][j] == inf) continue;
				F(k, 0, n){
					if(i == k || ((S >> k) & 1)) continue;
					f[S + (1 << k)][k][j] = min(f[S + (1 << k)][k][j], f[S][i][j] + s[i][k] - abs(a[i][j] - a[k][j]));
				}
			}
		}
	}
	
	F(j, 0, n){
		F(S, 1, (1 << (m + 1)) - 1){
			F(i, 0, m){
				if(!((S >> i) & 1) || g[S][i][j] == inf) continue;
				F(k, 0, m){
					if(i == k || ((S >> k) & 1)) continue;
					g[S + (1 << k)][k][j] = min(g[S + (1 << k)][k][j], g[S][i][j] + ss[i][k] - abs(a[j][i] - a[j][k]));
				}
			}
		}
	}
	int ans = inf, U1 = (1 << (n + 1)) - 1, U2 = (1 << (m + 1)) - 1;
	F(i, 0, n){
		F(j, 0, m){
			int ret1 = inf;
			F(k, 0, n) if(k != i) {
				ret1 = min(ret1, f[U1 - (1 << i)][k][j]);
			}
			int ret2 = inf;
			F(k, 0, m) if(k != j) {
				ret2 = min(ret2, g[U2 - (1 << j)][k][i]);	
			}
			ans = min(ans, ret1 + ret2);
		}
	}
	return ans;
}
signed main(){
	ios::sync_with_stdio(0); cin.tie(0); cout.tie(0);
	cin >> T;
	while(T --){
		init();
		cout << solve() << '\n';
	}
	return fflush(0), 0; 
}

相關文章