zoukankan      html  css  js  c++  java
  • 高斯消元总结

    https://zybuluo.com/ysner/note/1106886

    本质

    模拟加减消元,先消成上三角再代入求解。
    据说是小学内容???

    列方程

    有这个量的影响就赋系数。

    实现(加减方程组)

    • step 1:交换(将当前列系数绝对值最大的放到当前行
    • step 2:将每行第一系数化为1
    • step 3:消成倒三角(在下面的方程中减去上面的方程)
    • step 4:回代求解
    il void Gauss()
    {
      fp(i,1,n)//枚举列
        {
          re int now=i;
          fp(j,i+1,n)//枚举行
        if(fabs(a[j][i])+eps>fabs(a[now][i])) now=j;
          if(now^i) swap(a[i],a[now]);
          fp(j,i+1,n)
        fq(k,n+1,i)
        a[j][k]-=a[i][k]*a[j][i]/a[i][i];
        }
      fq(i,n,1)
        {
          ans[i]=a[i][n+1];
          fq(j,n,i+1) ans[i]-=ans[j]*a[i][j];
          ans[i]/=a[i][i];
        }
    }
    

    最关键的就是(a[j][k]-=a[i][k]*a[j][i]/a[i][i])了。
    此时上面所有方程第一系数在(/a[i][i])后已经被视为(1)
    想想平时怎么解方程的。
    先把上面方程乘上当前方程第一系数(即(*a[j][i])),再同位项相减即可。

    实现(异或方程组)

    解这种方程不难,和解加减方程差不多,原理是不断消去倒三角左边的系数。一开始消下面的系数,往回代时消上面的。
    除了continue,代码思路一致。

    il void Gauss()
    {
      fp(i,1,n)
        {
          re int now=i;
          fp(j,i+1,n) if(a[j][i]>a[now][i]) now=j;
          if(now^i) swap(a[now],a[i]);
          if(!a[i][i]) continue;
          fp(j,i+1,n) if(a[j][i]) a[j]^=a[i];
        }
      fq(i,n,1)
        fq(j,i-1,1)
        if(a[j][i]) a[j]^=a[i];
    }
    

    特殊情况

    • 无解:第一系数为0,常数非0
    • 无数解:第一系数为0,常数为0
      在开头交换后判一下系数最大值是否为0,有一个就记(flag=1),回代时方程形式为(x=b),判b是否为0即可。
      (注意先判无解,判完后若(flag=1),就是无穷解)

    例题

    T1 Luogu3389【模板】高斯消元法

    见上

    T2 hihoCoder1196 高斯消元法二

    据说异或方程组高斯消元和普通高斯消元一模一样,就是把减改成异或。
    若只有01建议用bitset压常数

    bitset<50>a[50];
    il void Gauss()
    {
      fp(i,1,30)
        {
          re int now=i;
          fp(j,i+1,30)
    	if(a[j][i]>a[now][i]) now=j;
          if(now^i) swap(a[now],a[i]);
          fp(j,i+1,30) if(a[j][i]) a[j]^=a[i];
        }
      fq(i,30,1)
        fq(j,i-1,1)
        if(a[j][i]) a[j]^=a[i];
    }
    int main()
    {
      fp(i,1,5) scanf("%s",s[i]+1);
      fp(i,1,5)
        fp(j,1,6)
        {
          re int p=pos(i,j),l=pos(i,j-1),r=pos(i,j+1),u=pos(i-1,j),d=pos(i+1,j);
          a[p][p]=1;a[p][31]=(s[i][j]-'0')^1;
          if(i>1) a[p][u]=1;if(i<5) a[p][d]=1;if(j>1) a[p][l]=1;if(j<6) a[p][r]=1;
        }
      Gauss();
      return 0;
    }
    

    T3Luogu2962 [USACO09NOV]灯Lights

    给定一个无向联通图,开始点值全为0。你可以选择将一个点和与其相邻的点同时取反,问最少进行多少次这样的操作可以使点值全为1。
    

    显然高斯消元解异或方程组。
    而且由于方程内值只有(0/1),可以开(bitset)使复杂度降到(O(frac{n^3}{64}))
    注意到这种方程解起来很容易得到形如(0x=0)的表达式(由于保证有解,不考虑(0x=b))。此时我们要枚举这个(x),才能继续往回代。

    bitset<50>a[50];
    int n,m,ans=1e9,tag[50];
    il void Gauss()
    {
      fp(i,1,n)
        {
          re int now=i;
          fp(j,i+1,n) if(a[j][i]>a[now][i]) now=j;
          if(now^i) swap(a[now],a[i]);
          if(!a[i][i]) continue;
          fp(j,i+1,n) if(a[j][i]) a[j]^=a[i];
        }
    }
    il void dfs(re int x,re int tot)
    {
      if(tot>=ans) return;
      if(!x) {ans=tot;return;}
      if(a[x][x])
        {
          re int t=a[x][n+1];
          fp(i,x+1,n) if(a[x][i]) t^=tag[i];
          tag[x]=t;
          dfs(x-1,tot+t);
        }
      else
        {
          tag[x]=0;dfs(x-1,tot);
          tag[x]=1;dfs(x-1,tot+1);
        }
    }
    int main()
    {
      n=gi();m=gi();
      fp(i,1,m)
        {
          re int u=gi(),v=gi();
          a[u][v]=a[v][u]=1;
        }
      fp(i,1,n) a[i][i]=a[i][n+1]=1;
      Gauss();
      dfs(n,0);
      printf("%d
    ",ans);
      return 0;
    }
    

    T4Luogu2447 [SDOI2010]外星千足虫

    给m个方程,最多n个未知数,解出方程并询问这最少需要要前几个方程组。
    

    太板子了。
    注意至少要(n)个方程。

    il int Gauss(re int m)
    {
      fp(i,1,m) fp(j,1,n) a[i]=aa[i];
      fp(i,1,n)
        {
          re int now=i;
          fp(j,i+1,m) if(a[j][i]>a[now][i]) now=j;
          if(now^i) swap(a[now],a[i]);
          if(a[i][i]==0) return 0;
          fp(j,i+1,m) if(a[j][i]) a[j]^=a[i];
        }
      fq(i,m,1)
        fq(j,i-1,1)
        if(a[j][i]) a[j]^=a[i];
      if(mm==m) fp(i,1,n) ans[i]=a[i][n+1];
      return 1;
    }
    int main()
    {
      n=gi();mm=m=gi();
      fp(i,1,m)
        {
          scanf("%s",s+1);re int len=strlen(s+1);
          fp(j,1,len)
          if(s[j]=='1') a[i][j]=aa[i][j]=1;
          a[i][n+1]=aa[i][n+1]=gi();
        }
      if(!Gauss(m)) {puts("Cannot Determine");return 0;}
      re int l=n,r=m;
      while(l<r)
        {
          re int mid=l+r>>1;
          if(Gauss(mid)) r=mid;
          else l=mid+1;
        }
      printf("%d
    ",l);
      fp(i,1,n)
        if(ans[i]&1) puts("?y7M#");
        else puts("Earth");
      return 0;
    }
    

    T5Luogu2973 [USACO10HOL]赶小猪

    一个无向图,节点1有一个炸弹,在每个单位时间内,有p/q的概率在这个节点炸掉,有1-p/q的概率随机选择一条出去的路到其他的节点上。问最终炸弹在每个节点上爆炸的概率。
    

    看到题就想起了[HNOI2013]游走
    好像思路方程都是一样的咦。
    不过高斯消元解的应该是炸弹到某点的概率,炸弹爆炸概率最后再乘。

    il void Gauss()
    {
      fp(i,1,n)//枚举列数
        {
          re int now=i;
          fp(j,i+1,n)//枚举行
        if(fabs(a[j][i])+eps>fabs(a[now][i])) now=j;
          if(now^i) swap(a[i],a[now]);
          fp(j,i+1,n)
        fq(k,n+1,i)
        a[j][k]-=a[i][k]*a[j][i]/a[i][i];
        }
      fq(i,n,1)
        {
          ans[i]=a[i][n+1];
          fq(j,n,i+1) ans[i]-=ans[j]*a[i][j];
          ans[i]/=a[i][i];
        }
    }
    int main()
    {
      memset(h,-1,sizeof(h));
      n=gi();m=gi();p=gi();q=gi();pi=(double)1.0*p/q;
      fp(i,1,m)
        {
          re int u=gi(),v=gi();
          add(u,v);
        }
      a[1][n+1]=1;
      fp(u,1,n)
        {
          a[u][u]=1;
          for(re int i=h[u];i+1;i=e[i].next)
        {
          re int v=e[i].to;
          a[u][v]=-1.0*(1-pi)/in[v];
        }
        }
      Gauss();
      fp(i,1,n) printf("%.9lf
    ",ans[i]*pi);
      return 0;
    }
    

    T6 bzoj3270博物馆

    给定一个无向联通图,两个人分别在(a),(b)两点。在每一轮决策中,他们有(pi)的概率选择不动,有(1-pi)的概率等概率到任意一相邻房间。问在每个房间相遇的概率。
    这题挺新奇的是吧。。。
    要求每个房间的概率,我们就要求出每个状态的概率(即(a)在哪个点,(b)在哪个点)。
    所以我们把一个状态的概率设为未知数。
    方程?
    (P_{a在i,b在j}-sum P_{ain i的邻点,bin i的邻点状态转移至此的概率}=0)
    懒得详写分类讨论)
    注意不要从已相遇状态转移过来就成。

    void Gauss()
    {
        fp(i,1,tot)
        {
            re int now=i;
            fp(j,i+1,tot) if(fabs(a[j][i])+eps>fabs(a[now][i])) now=j;
            if(now^i) swap(a[now],a[i]);
            fp(j,i+1,tot)
            fq(k,tot+1,i)
              a[j][k]-=a[i][k]*a[j][i]/a[i][i];
        }
        fq(i,tot,1)
        {
            fq(j,tot,i+1) a[i][tot+1]-=a[j][tot+1]*a[i][j];
            a[i][tot+1]/=a[i][i];
        }
    }
    int main()
    {
      n=gi();m=gi();A=gi();B=gi();
      fp(i,1,m)
      {
          re int u=gi(),v=gi();
          d[u][v]=d[v][u]=1;++in[u];++in[v];
      }
      fp(i,1,n) scanf("%lf",&p[i]),d[i][i]=1;
      fp(i,1,n) fp(j,1,n) id[i][j]=++tot;
      a[id[A][B]][tot+1]=1;
      fp(i,1,n)
        fp(j,1,n)
        {
          a[id[i][j]][id[i][j]]=1;
          fp(ii,1,n)
            fp(jj,1,n)
              if(d[ii][i]&&d[jj][j]&&ii!=jj)//ii!=jj
              {
                  re double p1,p2;
                  if(i==ii) p1=p[ii];else p1=(1-p[ii])/in[ii];
                  if(j==jj) p2=p[jj];else p2=(1-p[jj])/in[jj];
                  a[id[i][j]][id[ii][jj]]+=-p1*p2;//+=???
              }
        }
      Gauss();
      fp(i,1,n) printf("%.6lf ",a[id[i][i]][tot+1]);
      return 0;
    }
    

    T7 Luogu3164 [CQOI2014]和谐矩阵

       我们称一个由0和1组成的矩阵是和谐的,当且仅当每个元素都有偶数个相邻的1。一个元素相邻的元素包括它本身,及他上下左右的4个元素(如果存在)。给定矩阵的行数和列数,请计算并输出一个和谐的矩阵。注意:所有元素为0的矩阵是不允许的。
    

    直接上方程啊。
    啥?输出全为(0)?把方程里的自由元赋为(1)即可.

    int main()
    {
        n=gi();m=gi();tot=n*m;
        fp(i,1,n)
        fp(j,1,m)
            {
                re int p=pos(i,j),l=pos(i,j-1),r=pos(i,j+1),u=pos(i-1,j),d=pos(i+1,j);
                a[p][p]=1;
                if (i>1) a[p][u]=1;
                if (j>1) a[p][l]=1;
                if (i<n) a[p][d]=1;
                if (j<m) a[p][r]=1;
            }
        fp(i,1,tot)
        {
            if (!a[i][i])
            {
                fp(j,i+1,tot)
                    if (a[j][i]) {swap(a[i],a[j]);break;}
            }
              fp(j,i+1,tot)
                if (a[j][i]) a[j]^=a[i];
        }
        fq(i,tot,1)
        {
            ans[i]=a[i][tot+1];
            fq(j,tot,i+1)
                if (a[i][j]) ans[i]^=ans[j];
            if (!a[i][i]) ans[i]=1;
        }
        for (int i=1;i<=n;++i,puts(""))
            fp(j,1,m)
                printf("%d ",ans[pos(i,j)]);
        return 0;
    }
    

    T8 Luogu3265 [JLOI2015]装备购买

    (n)个装备,每个装备(m)个属性,每个装备还有个价格。如果手里有的装备的每一项属性为它们分配系数(实数)后可以相加得到某件装备,则不必要买这件装备。求最多装备下的最小花费。

    看到这道题可以想起线性基的性质:其中没有一个元素能被其它元素异或和求得。
    那这题也类似?可以拿线性基的(insert)的方法套?
    线性基里存(m)维,拿一个表达式进去时,若该表达式对应维系数为0就换维,如果该维没访问过就把这个式子整个塞进去,否则就把这个式子当前维消掉(高斯消元)。
    于是,式子只有两个结果,一个是被塞进去,另一个是被消成蛋。
    (竟然卡精度)

    il int Insert(re int x)
    {
        fp(i,1,m)
        {
            if(fabs(a[x].s[i])<=eps) continue;
            if(vis[i]) fq(j,m,i) a[x].s[j]-=p[i][j]*a[x].s[i]/p[i][i];
            else {vis[i]=1;fp(j,i,m) p[i][j]=a[x].s[j];return 1;}
        }
        return 0;
    }
    int main()
    {
        n=gi();m=gi();
        fp(i,1,n) fp(j,1,m) a[i].s[j]=gi();
        fp(i,1,n) a[i].c=gi();
        sort(a+1,a+1+n);
        //fp(i,1,n) printf("%d %d %d
    ",a[i].s[1],a[i].s[2],a[i].s[3]);
        fp(i,1,n)
        if(Insert(i)) ++tot,ans+=a[i].c;
        printf("%d %d
    ",tot,ans);
        return 0;
    }
    
  • 相关阅读:
    JDBC
    初识JAVA
    初入门 HTML
    jsp数据交互(一).3
    jsp数据交互(一).2
    jsp数据交互(一).1
    jsp的简介(1)
    Android实现数据存储技术
    Android数据存储五种方式总结
    SD卡操作
  • 原文地址:https://www.cnblogs.com/yanshannan/p/8805546.html
Copyright © 2011-2022 走看看