【題解】P2962 [USACO09NOV]Lights G(線代,矩陣,高斯消元)
【題解】P2962 [USACO09NOV]Lights G
題目連結:P2962 [USACO09NOV]Lights G
思路分析:
首先根據題意可以對每一個節點建立一個異或方程組,顯然每個節點的狀態只由它自己和它相鄰節點的狀態決定。而我們最終要達到的狀態是 1,所以對於每個節點 \(i\) ,設它的相鄰節點的狀態為 \(a_1,a_2,a_3,…,a_k\),它自己的狀態為 \(b_i\),那麼 \(a_1 \oplus a_2 \oplus a_3 \oplus … \oplus a_k=b_i\) 。
我們可以通過高斯消元解異或方程組,然後解出來一個矩陣。
考慮這個矩陣所滿足的特徵:
-
這個矩陣的主對角線以下的所有位置均為 0,因為在高斯消元的時候肯定已經通過主對角線將其以下的不為 0 的值都消成 0 了。
-
主對角線上的位置有可能為 0,也可能為 1,因為有可能存在無解或多解的情況。
-
主對角線以上的位置可能為 0,也可能為 1。當第 \(i\) 列上的主對角線上的位置為 1 時,這一列上的其它位置一定為 0(顯然主對角線會把他們消為 0);當第 \(i\) 列上主對角線的位置的值是 0,這一列上才有其它位置上的值為 1。
所以可以知道,在經過高斯消元之後得到的矩陣是上三角矩陣。
這裡引入兩個概念:
-
主元:主對角線上位置上的值不為 0 的未知量。
-
自由元:主對角線位置上的值為 0 的未知量。
可以知道,主元的解是確定的,而自由元的解是不確定的。
因此,我們可以 dfs 暴力列舉所有自由元的解。
根據異或方程組可以知道,要確定每一個點的狀態,就要確定與它相連的點的狀態,又由於主對角線以下的點上的值一定為 0,所以對這個點的狀態可能產生影響的只有編號大於它本身的點。
所以我們可以從下往上搜:判斷 \(a_{i,i}\) 的值是否為 0,也就是判斷它是自由元還是主元:
-
如果是主元,那麼它的解已經可以確定,直接根據異或方程算出它對應的解並累加到答案中即可。
-
而對於自由元,列舉它的狀態是 0 還是 1,並 dfs 即可。
易錯點:
高斯消元第一層迴圈結束後 f=false 應該直接 continue 掉而不需要繼續搜尋。
程式碼實現:
//luoguP2962
#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
const int maxn=605;
int a[maxn][maxn],ans=1e18,c[maxn];
int n,m;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=x*10+ch-48;ch=getchar();}
return x*f;
}
bool gauss()
{
bool f;
for(int i=1;i<=n;i++)
{
f=false;
for(int j=i;j<=n;j++)
{
if(a[j][i])
{
f=true;
for(int k=1;k<=n+1;k++)swap(a[j][k],a[i][k]);
break;
}
}
if(f==false)continue;//注意這裡直接continue即可。
for(int j=1;j<=n;j++)
{
if(j==i||a[j][i]==0)continue;
for(int k=i+1;k<=n+1;k++)a[j][k]^=a[i][k];//
a[j][i]=0;
}
}
return f;
}
void dfs(int now,int pos)
{
if(now>=ans)return ;//最優性剪枝
if(pos==0){ans=now;return ;}//更新答案。
if(a[pos][pos])//主元
{
int k=a[pos][n+1];
for(int i=pos+1;i<=n;i++)if(a[pos][i])k^=c[i];//直接根據異或方程計算答案。
dfs(now+k,pos-1);
}
else //自由元,暴力列舉是 0 還是 1
{
dfs(now,pos-1);
c[pos]=1;
dfs(now+1,pos-1);
c[pos]=0;
}
}
signed main()
{
n=read();m=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
a[u][v]=a[v][u]=1;//注意要連兩條邊
}
//自己也會對自己的狀態產生影響;將最後一行設為亮著的;
for(int i=1;i<=n;i++)a[i][i]=a[i][n+1]=1;
bool flag=gauss();//判斷結果矩陣是否含有自由元(是否為唯一解)
if(flag)
{
int ans=0;
for(int i=1;i<=n;i++)ans+=a[i][n+1];//答案唯一直接累加求和
cout<<ans<<endl;
}
else dfs(0,n),cout<<ans<<endl;//否則dfs,注意要從後往前搜。
return 0;
}