1. 程式人生 > 其它 >【題解】P2962 [USACO09NOV]Lights G(線代,矩陣,高斯消元)

【題解】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\)

我們可以通過高斯消元解異或方程組,然後解出來一個矩陣。

考慮這個矩陣所滿足的特徵:

  1. 這個矩陣的主對角線以下的所有位置均為 0,因為在高斯消元的時候肯定已經通過主對角線將其以下的不為 0 的值都消成 0 了。

  2. 主對角線上的位置有可能為 0,也可能為 1,因為有可能存在無解或多解的情況。

  3. 主對角線以上的位置可能為 0,也可能為 1。當第 \(i\) 列上的主對角線上的位置為 1 時,這一列上的其它位置一定為 0(顯然主對角線會把他們消為 0);當第 \(i\) 列上主對角線的位置的值是 0,這一列上才有其它位置上的值為 1。

所以可以知道,在經過高斯消元之後得到的矩陣是上三角矩陣

這裡引入兩個概念:

  1. 主元:主對角線上位置上的值不為 0 的未知量。

  2. 自由元:主對角線位置上的值為 0 的未知量。

可以知道,主元的解是確定的,而自由元的解是不確定的。

因此,我們可以 dfs 暴力列舉所有自由元的解。

根據異或方程組可以知道,要確定每一個點的狀態,就要確定與它相連的點的狀態,又由於主對角線以下的點上的值一定為 0,所以對這個點的狀態可能產生影響的只有編號大於它本身的點。

所以我們可以從下往上搜:判斷 \(a_{i,i}\) 的值是否為 0,也就是判斷它是自由元還是主元:

  1. 如果是主元,那麼它的解已經可以確定,直接根據異或方程算出它對應的解並累加到答案中即可。

  2. 而對於自由元,列舉它的狀態是 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;
}