高斯消元練習題集

1.POJ-1222 EXTENDED LIGHTS OUT


題意
開關問題,給一個矩陣,每次翻轉,上下左右都會一起翻轉,問你翻轉哪些位置,可以把燈全部關上。

思路

異或運算下的高斯消元

典型的開關問題,和 POJ 1830 開關問題 是一樣的屬於 XOR 類型的消元。

/***************************
*author:ccf
*source:poj-1222- EXTENDED LIGHTS OUT 
*topic:高斯消元
****************************/
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;

const int N = 40;
const int inf = 50;
int cas,n,icas = 1;
int a[N][N];//增廣矩陣
int equ,var;//equ:行     var: 列 
int free_i[N],free_cnt;
int x[N],mp[N];//x[N]自由元數組 

//void debug() {
//	for(int i = 0 ; i < equ; i++) {
//		for(int j = 0; j <= var; j++)
//			printf("%d ",a[i][j]);
//		printf("\n");
//	}
//
//}
bool Gauss() {
	int k,col,max_i;//k是列 col是行 max_i是最大行
	//轉化爲階梯陣
	for(k = col = 0; k < equ && col < var; k++,col++) {
		//找到最大行
		max_i = k;
		for(int i = k + 1; i < equ; i++) {
			if(abs(a[i][col]) > abs(a[max_i][col])) max_i = i;
		}
		if(a[max_i][col] == 0) {
			free_i[col] = 1;
			x[free_cnt++] = col;
			k--;
			continue;
		}
		if(k != max_i) {
			for(int j = col; j <= var; j++) {
				swap(a[max_i][j],a[k][j]);
			}
		}
		//開始化簡階梯陣
		for(int i = 0; i < equ; i++) {
			if(a[i][col] && i != k)
				for(int j = col; j <= var; j++)
					a[i][j] ^= a[k][j];
		}
	}
	//debug();
	for(int i = k; i < equ; i++) 
		if(a[i][var]) return false;
	return true;
}
int main() {
	//freopen("date.in","r",stdin);
	equ = var = 30;
	scanf("%d",&cas);
	while(cas--) {
		int cnt = 0;
		memset(a,0,sizeof(a));
		memset(free_i,0,sizeof(free_i));
		memset(x,0,sizeof(x));
		memset(mp,0,sizeof(mp));
		for(int i = 0; i < equ; i++) {
			scanf("%d",&mp[i]);
			a[i][var] = mp[i];
		}
		//方格化成增廣矩陣
		for(int i = 0 ; i < equ; i++) {
			a[i][i] = 1;
			if(i < 24) a[i][i+6] = 1;
			if(i > 5) a[i][i-6] = 1;
			if(i % 6 != 0) a[i][i-1] = 1;
			if(i % 6 != 5) a[i][i+1] = 1;
		}

		if(!Gauss()) printf("no solution!\n");
		else {
			printf("PUZZLE #%d\n",icas++);
			for(int i = 1; i <= 30; i++) {
				printf("%d",a[i-1][var]);
				if(i % 6 == 0) printf("\n");
				else printf(" ");
			}
		}
	}
	return 0;
}

2.POJ-2947-Widget Factory

題意
有n種飾品,每種的加工時間爲3~9天,現在知道m條記錄,每條記錄形如:開始時間是周幾,終止時間是周幾,加工出來哪些飾品,各多少件。但是不知道持續了多少周。
求每種飾品的加工時間。

需要判斷無解和多解。
思路

高斯消元解同餘方程

在原本高斯約旦消元的基礎上,加上取模的運算,最後每一行得到一個
AxC(modp) Ax\equiv C(mod p) 形式的式子,使用擴展歐幾里得解同餘方程即可,也有直接模擬取餘操作的方法。

/***********************
*author:ccf
*source:poj-2974 Widget Factory
*topic:高斯消元解同餘方程
************************/
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long
using namespace std;

const int N = 600;
int cas,n,m,icas;
char start[5],fired[5];
int a[N][N],ans[N];//行列式  解集
int equ,var,free_cnt = 0;

void debug() {
	for(int i = 1; i <= equ; i++) {
		for(int j = 1; j <= var + 1; j++) {
			printf("%d ",a[i][j]);
		}
		printf("\n");
	}
	printf("\n");
}
int sti(char *s) {
	if(strcmp(s, "MON") == 0) return 1;
	else if(strcmp(s, "TUE") == 0) return 2;
	else if(strcmp(s, "WED") == 0) return 3;
	else if(strcmp(s, "THU") == 0) return 4;
	else if(strcmp(s, "FRI") == 0) return 5;
	else if(strcmp(s, "SAT") == 0) return 6;
	else if(strcmp(s, "SUN") == 0) return 7;
}
int gcd(int a, int b) {
	return b == 0 ? a : gcd(b,a%b);
}
int lcm(int a,int b) {
	return a / gcd(a,b) * b;
}
int Gauss() {
	int free_i[N],free_x[N],free_cnt = 0;
	int k,col,max_i;
	for(k = col = 1; k <= equ && col <= var; k++,col++) {
		max_i = k;
		//找到最大值
		for(int i = k + 1; i <= equ; i++) {
			if(abs(a[i][col]) > abs(a[max_i][col])) max_i = i;
		}
		//交換最大值
		if(k != max_i) {
			for(int j = k; j <= var + 1; j++) {
				swap(a[max_i][j],a[k][j]);
			}
		}
		//確定自由元
		if(a[k][col] == 0) {
			free_x[++free_cnt] = col;
			k--;
			continue;
		}
		//化簡行列式
		for(int i = 1; i <= equ; i++) {
			if(a[i][col] != 0 && i != k) {
				int LCM = lcm(abs(a[i][col]),abs(a[k][col]));
				int tmp1 = LCM/abs(a[i][col]),tmp2 = LCM/abs(a[k][col]);
				if(a[i][col] * a[k][col] < 0) tmp2 = -tmp2;
				for(int j= 1; j <= var + 1; j++) {
					a[i][j] = ( (a[i][j] * tmp1 - a[k][j] * tmp2) % 7 + 7) % 7;
				}
			}//debug();
		}
		
	}
	//無解
	for(int i = k ; i <= equ; i++)
		if(a[i][var+1]) return -1;
	//存在自由元 多個解
	if(k <= var) return var - k + 1;
	//只有一個解
	for(int i = var; i > 0; i--) {
		int tmp = a[i][var + 1];
		for(int j = i + 1; j <= var; j++) {
			if(a[i][j] != 0) tmp -= a[i][j] * ans[j];
			tmp = (tmp % 7 + 7) % 7;
		}
		while(tmp % a[i][i] != 0) tmp += 7;
		ans[i] = (tmp / a[i][i] )% 7;
	}
	return 0; 
}
int main() {
	freopen("data.in","r",stdin);
	while(scanf("%d %d",&n,&m) && n + m != 0) {
		memset(a,0,sizeof a);
		memset(ans,0,sizeof ans);
		equ = m;
		var = n;
		//化爲階梯陣
		for(int i = 1,k; i <= m; i++) {
			scanf("%d %s %s",&k,start,fired);
			//printf("%d %s %s\n",k,start,fired);
			a[i][n + 1] =((sti(fired) - sti(start) + 1)%7 + 7) % 7;
			for(int j = 1,tmp; j <= k; j++) {
				scanf("%d",&tmp);
				a[i][tmp]++;
				a[i][tmp] %= 7;
			}
		}
		//debug();
		int res = Gauss();
		if(res == -1) printf("Inconsistent data.\n");
		else if(res == 0) {
			for(int i = 1; i <= n; i++) {
				if(ans[i] < 3) ans[i] += 7;
				printf("%d",ans[i]);
				if(i == n) printf("\n");
				else printf(" ");
			}
		} else {
			printf("Multiple solutions.\n");
		}
	}
	return 0;
}

3.POJ-1681-Painter’s Problem

題意

異或運算下的高斯消元

給方格染色,原本是白的,可以染成黃色,每次染一個格子,上下左右的方格就都會翻轉,問全染成黃色的最小染色次數是多少?

思路

仍屬於開關問題,XOR 操作下的高斯消元。

/*******************
*author:ccf
*source:poj 1681 - Painter's Problem
*topic:高斯消元 
*******************/ 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long 
using namespace std;

const int N = 251;
int cas,n;
int a[N][N], mp[N];
int equ,var,ans = 0;

void debug(){
	for(int i= 1 ; i <= n*n; i++){
		for(int j = 1; j <= n*n + 1; j++)
			printf("%d ",a[i][j]);
		printf("\n");
	}
	printf("\n");
}

bool Gauss(){
	int free_x[N],free_cnt = 0;
	int k,col,max_i;
	for(k = col = 1; k <= equ && col <= var; k++,col++){
		//找最大值
		max_i = k;
		for(int i = k + 1; i <= equ; i++){
			if(a[max_i][col] < a[i][col]) max_i = i;
		} 
		if(k != max_i){
			for(int j = col; j <= var + 1; j++) 
				swap(a[max_i][j],a[k][j]);
		}
		//獲取自由元 
		if(a[k][col] == 0){
			free_x[++free_cnt] = col;
			k--;
			continue;
		}
		//化簡階梯陣
		for(int i = 1; i <= equ; i++){
			if(i != k && a[i][col]){
				for(int j = col; j <= var + 1; j++)
					a[i][j] ^= a[k][j];
			}
		} 
		//debug(); 
	} 
	//存在 0 = 1 的情況,無解
	for(int i = k; i <=equ ; i++)
		if(a[i][var + 1]) return false;
	//debug();
	for(int i = 1; i <= equ; i++)
		if(a[i][var + 1] ) ans++; 
	return true;
}
int main(){
	//freopen("date.in","r",stdin);
	scanf("%d",&cas);
	while(cas--){
		int cnt = 0;
		char c;
		ans = 0;
		memset(a,0,sizeof a);
		scanf("%d",&n);
		equ = var = n*n;
		for(int i = 1; i <= n; i++){
			getchar();
			for(int j = 1; j <= n; j++){
				scanf("%c",&c);
				if(c == 'y') mp[++cnt] = 1;
				else mp[++cnt] = 0;
			}
		}
		//方格轉化爲增廣矩陣 
		for(int i = 1; i <= cnt; i++){
			a[i][i] = 1;
			a[i][var + 1] = 1 ^ mp[i];
			if(i > n) a[i][i-n] = 1;
			if(i < n * (n-1) + 1) a[i][i+n] = 1;
			if(i % n != 0) a[i][i+1] = 1;
			if(i % n != 1) a[i][i-1] = 1;
		} 
		//debug(); 
		if(!Gauss()) printf("inf\n");
		else{
			printf("%d\n",ans);
		}
	}		
	return 0;
}
/*
1
3
yyy
yyy
yyy
*/

4.POJ-2065-SETI

題意
t_70)
思路

高斯消元解同餘方程組

和POJ-2947-Widget Factory 是一樣的,高斯消元時帶上取模運算,最後使用擴展歐幾里得算法進行同餘方程的求解。

/***********************
*author:ccf
*source:POJ-2065-SETI 
*topic:高斯消元解同餘方程 
************************/
#include <cstdio>
#include <algorithm>
#include <cstring>
#include <cmath>
#define ll long long
using namespace std;

const int N = 107;
int equ,var,cas,p;
int a[N][N],ans[N];
char info[N];

void debug() {
	for(int i = 1; i <=equ; i++) {
		for(int j = 1; j <= var + 1; j++)
			printf("%d ",a[i][j]);
		printf("\n");
	}
	printf("\n"); 
}
int gcd(int a,int b) {
	return b == 0 ? a : gcd(b,a%b);
}
int lcm(int a,int b) {
	return a / gcd(a,b) * b;
}
int exgcd(int a,int b,int &x,int &y){
	if(b == 0){
		x = 1;y = 0;
		return a;
	}
	int t = exgcd(b,a%b,x,y);
	int x0 = x, y0= y;
	x = y0;
	y = x0 - a / b * y0;
	return t;
}
int qpow(int a, int b,int mod) {
	int res = 1;
	while(b) {
		if(b & 1) {
			res = res * a % mod;
		}
		a = a * a % mod;
		b >>= 1;
	}
	return res;
}
int sti(char *s) {
	int len = strlen(s);
	for(int i = 0; i < len; i++) {
		if(s[i] == '*') a[i + 1][len + 1] = 0;
		else  a[i + 1][len + 1] = (s[i] - 'a' + 1) % p;
	}
	return len;
}
int Gauss() {
	int free_x[N],free_i[N],free_cnt = 0;
	int k,col,max_i;
	for(k = col = 1; k <= equ && col <= var; k++,col++) {
		max_i = k;
		//找出最大值並交換
		for(int i = k + 1; i <= equ; i++)
			if(abs(a[i][col]) > abs(a[max_i][col])) max_i = i;
		if(k != max_i) {
			for(int j = col; j <= var + 1; j++)
				swap(a[k][j],a[max_i][j]);
		}
		if(a[k][col] == 0) {
			free_x[++free_cnt] = col;
			k--;
			continue;
		}
		//化簡階梯陣
		for(int i = 1; i <= equ; i++) {
			if(i != k && a[i][col]) {
				int LCM = lcm(abs(a[i][col]),abs(a[k][col]));
				int tmp1 = LCM / abs(a[i][col]),tmp2 = LCM / abs(a[k][col]);
				if(a[i][col] * a[k][col] < 0) tmp2 = -tmp2;
				for(int j = 1; j <= var + 1; j++) {
					a[i][j] = ((a[i][j] * tmp1 - a[k][j] * tmp2) % p + p) % p;
				}
			}
			//debug(); 
		}
	}
	for(int i = k; i <= equ; i++)
		if(a[i][var + 1])return -1;
	return 0;
}
void solve(){
	int x,y;
	for(int i = 1; i <= var; i++){
		int A = a[i][i], B = p, C = a[i][var + 1];
		int g = exgcd(A,B,x,y);
		A /= g;
		B /= g;
		C /= g;
		int x0 = x * C;
		x0 = (x0 % B + B ) % B;
		ans[i] = x0; 
	}
	
}
int main() {
	//freopen("data.in","r",stdin);
	scanf("%d",&cas);
	while(cas--) {
		memset(a,0,sizeof a);
		memset(ans,0,sizeof ans);
		scanf("%d %s",&p,info);
		int len = sti(info);
		equ = var = len;
		for(int i = 1; i <= len; i++) {
			for(int j = 1; j <= len; j++) {
				a[i][j] = qpow(i,j-1,p);
			}
		}
		//debug();
		if(Gauss() != -1){
			solve();
			for(int i = 1; i <= len; i++)
				printf("%d ",ans[i]); 
		}else {
			printf("No Smoking!");
		}
		puts("");
	}

	return 0;
}

5.POJ-1753 - Flip Game

題意

給一個棋盤,可以黑色白色棋子相互翻轉,每次翻轉一個棋子,其上下左右的棋子也會翻轉。變成全白或全黑的時候結束,問結束的最小步數是多少?

思路

高斯消元 + 枚舉不定元

開關問題,但有一點不同的是:當存在不定變元的時候,我們要枚舉每個不定元的狀態來求出最小值,cntcnt 個不定元一共有2cnt2^{cnt}種狀態,用數組記錄不定元,使用二進制來枚舉可加快速度。

/*******************
*author:ccf
*source:poj 1753 -  Flip Game 
*topic:高斯消元 
*******************/ 
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#define ll long long 
using namespace std;

const int N = 20;
int cas,n = 4;
int a[N][N], mp[N];
int equ,var;

//void debug(){
//	for(int i= 1 ; i <= n*n; i++){
//		for(int j = 1; j <= n*n + 1; j++)
//			printf("%d ",a[i][j]);
//		printf("\n");
//	}
//	printf("\n");
//}

int Gauss(){
	int ans = N; 
	int free_x[N],free_cnt = 0;
	int free_num[N],free_i[N];
	int k,col,max_i;
	memset(free_num,0,sizeof(free_num));
	memset(free_x,0,sizeof(free_x));
	memset(free_i,0,sizeof(free_i));
	for(k = col = 1; k <= equ && col <= var; k++,col++){
		//找最大值
		max_i = k;
		for(int i = k + 1; i <= equ; i++){
			if(a[max_i][col] < a[i][col]) max_i = i;
		} 
		if(k != max_i){
			for(int j = col; j <= var + 1; j++) 
				swap(a[max_i][j],a[k][j]);
		}
		//獲取自由元 
		if(a[k][col] == 0){
			free_i[col] = 1;
			free_x[++free_cnt] = col;
			k--;
			continue;
		}
		//化簡階梯陣
		for(int i = 1; i <= equ; i++){
			if(i != k && a[i][col]){
				for(int j = col; j <= var + 1; j++)
					a[i][j] ^= a[k][j];
			}
		} 
		//debug(); 
	} 
	//存在 0 = 1 的情況,無解
	for(int i = k; i <=equ ; i++)
		if(a[i][var + 1]) return N;
	//debug();
	int limit =  1 << free_cnt;
	//for(int i = 1; i <= free_cnt; i++) printf("%d ",free_x[i]);
	for(int i = 0; i < limit; i++){
		int num = 0,m = i;
		memset(free_num,0,sizeof free_num);
		//使用二進制來開始枚舉自由元
		for(int j = 1; j <= free_cnt; j++){
			if(m & 1){
				free_num[free_x[j]] = 1;
				num++;
			}
			m /= 2;
		}
		//根據不定元確定主元 
		for(int j = k - 1; j >= 1; j--){
			if(!free_i[j]){
				int tmp = a[j][var+1];
				for(int l = j + 1; l <= var; l++)
					if(a[j][l]) tmp ^= free_num[l];
				free_num[j] = tmp;
				if(tmp) num++;
			}
		}
		//printf("num = %d\n",num);
		ans = min(ans,num);
	}
	
	return ans;
}
int main(){
		int cnt = 0;
		char c;
		memset(a,0,sizeof a);
		equ = var = 16;
		for(int i = 1; i <= n; i++){
			for(int j = 1; j <= n; j++){
				scanf("%c",&c);
				if(c == 'b') mp[++cnt] = 1;
				else mp[++cnt] = 0;
			}
			getchar();
		}
		//方格轉化爲增廣矩陣 
		for(int i = 1; i <= cnt; i++){
			a[i][i] = 1;
			a[i][var + 1] = 0 ^ mp[i];
			if(i > n) a[i][i-n] = 1;
			if(i < n * (n-1) + 1) a[i][i+n] = 1;
			if(i % n != 0) a[i][i+1] = 1;
			if(i % n != 1) a[i][i-1] = 1;
		} 
		//debug(); 
		int tmp = Gauss();
		memset(a,0,sizeof a);
		for(int i = 1; i <= cnt; i++){
			a[i][i] = 1;
			a[i][var + 1] = 1 ^ mp[i];
			if(i > n) a[i][i-n] = 1;
			if(i < n * (n-1) + 1) a[i][i+n] = 1;
			if(i % n != 0) a[i][i+1] = 1;
			if(i % n != 1) a[i][i-1] = 1;
		} 
		tmp = min(tmp,Gauss());
		if(tmp == N) printf("Impossible\n");
		else{
			printf("%d\n",tmp);
		}	
	return 0;
}
/*
bwwb
bbwb
bwwb
bwww
*/
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章