【例题】【高斯消元(异或)】

1、
NKOJ1985 中央暖气
时间限制 : 10000 MS 空间限制 : 65536 KB

问题描述
冬天来了,但URAL大学的暖气系统还没启动。这个暖气系统包括许多阀门,只有所有阀门都打开了才能供应暖气。大学里有一些技术员,他们每人负责一个或多个阀门,有可能存在一个阀门由多个人负责的情况。当一个技术员接到启动暖气系统的指示后他就会把所有自己负责的阀门都转动一次。这意味着如果一个阀门原来是开的,他就把它关掉;如果原来是关的,就把它打开。任何一个技术员的工作都不可能让其他人代劳。
你的任务是决定哪些技术员应接到指示,从而使整个大学的暖气系统启动。注意学校里一共有N个技术员和N个阀门(1 <= N <= 250)。

输入格式
输入的第一行是一个数字N(1 <= N <= 250)。下面的N行是每一个技术员负责的阀门的列表。其中第i行包含第i个技术员所负责的阀门的编号。每一个阀门列表都以-1结束。

输出格式
输出满足条件的升序的技术员编号序列。如果有好几个序列满足要求,就输出最短的一个。如果无法找到方法把整个学校的暖气系统启动,就输出”No solution” .

样例输入
4
1 2 -1
2 3 4 -1
2 -1
4 -1

样例输出
1 2 3

来源 ural 1042

思路:
“任何一个技术员的工作都不可能让其他人代劳”,是有无解或有唯一解的意思…
所以遇到变元就可以判无解了

#include<cstdio>
#include<iostream>
using namespace std;
const int need=257;

int n;
int m[need][need+1];
int ans[need];

int gauss(int xx,int yy)
{
    int x,y,i,j,mx;
    for(x=y=1;x<=xx&&y<yy;x++,y++)
    {
        mx=x;
        for(i=x;i<=xx;i++) if(m[i][y]==1) {mx=i;break;}
        if(mx!=x)
         for(i=y;i<=yy;i++) swap(m[mx][i],m[x][i]);
        if(m[x][y]==0) {return -1;}//必定无解或唯一解 
        for(i=x+1;i<=xx;i++)
         if(m[i][y])
         for(j=y;j<=yy;j++) m[i][j]^=m[x][j];
    }
    //for(i=x;i<=xx;i++) if(m[i][yy]) return -1;
    int temp;
    for(i=xx;i>=1;i--)
    {
        temp=m[i][yy];
        for(j=i+1;j<yy;j++) temp^=(ans[j]&m[i][j]);
        ans[i]=temp;//m[i][i]必定为1 
    }
}

int main()
{
    cin>>n;
    for(int i=1,a;i<=n;i++)
    {
        cin>>a;
        for(;a!=-1;cin>>a) m[a][i]=1;
    }
    for(int i=1;i<=n;i++) m[i][n+1]=1;
    if(gauss(n,n+1)==-1) puts("No solution");
    else for(int i=1;i<=n;i++) if(ans[i]) cout<<i<<" ";
}

2、
NKOJ1986 开关问题
时间限制 : 10000 MS 空间限制 : 65536 KB

问题描述
有N个相同的开关,每个开关都与某些开关有着联系,每当你打开或者关闭某个开关的时候,其他的与此开关相关联的开关也会相应地发生变化,即这些相联系的开关的状态如果原来为开就变为关,如果为关就变为开。你的目标是经过若干次开关操作后使得最后N个开关达到一个特定的状态。对于任意一个开关,最多只能进行一次开关操作。你的任务是,计算有多少种可以达到指定状态的方法。(不计开关操作的顺序)

输入格式
输入第一行有一个数K,表示以下有K组测试数据。
每组测试数据的格式如下:
第一行 一个数N(0 < N < 29)
第二行 N个0或者1的数,表示开始时N个开关状态。
第三行 N个0或者1的数,表示操作结束后N个开关的状态。
接下来 每行两个数I J,表示如果操作第 I 个开关,第J个开关的状态也会变化。每组数据以 0 0 结束。

输出格式
如果有可行方法,输出总数,否则输出“Oh,it’s impossible~!!” 不包括引号

样例输入
2
3
0 0 0
1 1 1
1 2
1 3
2 1
2 3
3 1
3 2
0 0
3
0 0 0
1 0 1
1 2
2 1
0 0

样例输出
4
Oh,it’s impossible~!!

提示
第一组数据的说明:
一共以下四种方法:
操作开关1
操作开关2
操作开关3
操作开关1、2、3 (不记顺序)

来源 poj 1830

思路:统计变元个数即可,不必求出具体解

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
const int need=45;

int n;
int s[need];
int m[need][need+1];
int ans[need];

int gauss(int xx,int yy)
{
    int x,y,i,j,mx;
    for(x=y=1;x<=xx&&y<yy;x++,y++)
    {
        mx=x;
        for(i=x;i<=xx;i++) if(m[i][y]==1) {mx=i;break;}
        if(mx!=x)
         for(i=y;i<=yy;i++) swap(m[mx][i],m[x][i]);
        if(m[x][y]==0) {x--;continue;}
        for(i=x+1;i<=xx;i++)
         if(m[i][y])
         for(j=y;j<=yy;j++) m[i][j]^=m[x][j];
    }
    for(i=x;i<=xx;i++) if(m[i][yy]) return -1;
    return xx-x+1;
}

int main()
{
    int t;cin>>t;
    int aa;
    int ta,tb;
while(t--)
{
    cin>>n;
    for(int i=1,j;i<=n;i++)
     for(j=1;j<=n+1;j++)
      m[i][j]=0;
    for(int i=1;i<=n;i++) cin>>s[i];
    for(int i=1;i<=n;i++) cin>>m[i][n+1],m[i][n+1]^=s[i];//开关状态与原来同还是反 
    for(int i=1;i<=n;i++) m[i][i]=1;
    while(true)
    {
        cin>>ta>>tb;
        if(ta==0&&tb==0) break;
        m[ta][tb]=1;
    }
    aa=gauss(n,n+1);
    if(aa==-1) puts("Oh,it's impossible~!!");
    else cout<<(1<<aa)<<endl;//自由变元只有两种取值 
}
}

NKOJ1987 手机游戏
时间限制 : 10000 MS 空间限制 : 65536 KB

问题描述
有一个有趣的手机游戏,有n*n个正方形的小按钮,有的按钮是黄色,有的按钮是白色。玩家的任务是通过点击按钮,让所有按钮都变成黄色,点按钮的次数越少,得分越高。
但是按钮有个奇怪的特点,当你点击了坐标为(x,y)的按钮后,坐标为(x,y),(x+1,y),(x-1,y),(x,y-1),(x,y+1)的五个按钮会同时改变自身的颜色,是白色的变成黄色,黄色的变成白色。完成游戏最少需要点击多少次按钮呢?请找出答案。

这里写图片描述

输入格式
第一行,一个整数n,表示有n*n个按钮。(1<=n<=40)
接下来是一个由小写字母’y’和字符’w’构成的n*n的矩阵,描述了游戏开始时的情景。’y’表示该按钮式黄色,’w’表示该按钮式白色。

输出格式
如果能够完成游戏,输出一个整数,表示所需最小点击次数。
如果无法完成游戏,输出”inf”

样例输入
样例输入1:
2
yw
ww

样例输入2:
5
wwwww
wwwww
wwwww
wwwww
wwwww

样例输出
样例输出1:
1

样例输出2:
15

来源 改编自poj 1681

思路:变元不能直接取为0,需深搜找最优解…
//深搜复杂度O(2^k),k为变元个数….

#include<cstdio>
#include<iostream>
using namespace std;
const int need=42;

int n,nn;
int m[need*need][need*need+1];
int ans[need*need];
int num[need][need];
char ma[need][need];
int kx[]={0,0,1,-1},ky[]={1,-1,0,0};
int free_x[need],tot; 
bool isfree[need];

int gauss(int xx,int yy)
{
    int x,y,i,j,mx;
    for(x=y=1;x<=xx&&y<yy;x++,y++)
    {
        mx=x;
        for(i=x;i<=xx;i++) if(m[i][y]==1) {mx=i;break;}
        if(mx!=x)
         for(i=y;i<=yy;i++) swap(m[mx][i],m[x][i]);
        if(m[x][y]==0) {isfree[y]=true;free_x[++tot]=y;x--;continue;}
        for(i=x+1;i<=xx;i++)
         if(m[i][y])
         for(j=y;j<=yy;j++) m[i][j]^=m[x][j];
    }
    for(i=x;i<=xx;i++) if(m[i][yy]) return -1;
    int temp,ans2=0;
    if(tot!=0)
    {
        ans2=1e9;
        int tans;
        int nn=(1<<tot)-1;
        for(int s=0,i,j,k;s<=nn;s++)
        {
            tans=0;
            for(j=0;j<tot;j++)
             if(s&(1<<j)) ans[free_x[j+1]]=1,tans++;
             else ans[free_x[j+1]]=0; 
            for(i=xx;i>=1;i--)
            {
                if(isfree[i]) continue;
                temp=m[i][yy];
                for(j=i+1;j<yy;j++) temp^=(ans[j]&m[i][j]);
                ans[i]=temp;
                if(ans[i]) tans++;
            }
            ans2=min(ans2,tans);
        }
        return ans2;
    }
    for(i=xx;i>=1;i--)
    {
        temp=m[i][yy];
        for(j=i+1;j<yy;j++) temp^=(ans[j]&m[i][j]);
        ans[i]=temp;//m[i][i]必定为1 
        if(ans[i]) ans2++;
    }
    return ans2;
}

int main()
{
    cin>>n;
    nn=n*n;
    for(int i=1,j,tot=0;i<=n;i++)
     for(j=1;j<=n;j++)
      num[i][j]=++tot;
    for(int i=1;i<=nn;i++) m[i][nn+1]=1;

    for(int i=1;i<=n;i++) scanf("%s",&ma[i][1]);

    for(int i=1,j;i<=n;i++)
     for(j=1;j<=n;j++)
      if(ma[i][j]=='y') m[num[i][j]][nn+1]=0;

    for(int i=1,j,k,xx,yy;i<=n;i++)
     for(j=1;j<=n;j++)
     {
        m[num[i][j]][num[i][j]]=1;
        for(k=0;k<4;k++)
        {
            xx=kx[k]+i,yy=ky[k]+j;
            if(xx>=1&&xx<=n&&yy>=1&yy<=n) m[num[xx][yy]][num[i][j]]=1;
        }
     }
    int tt=gauss(nn,nn+1);
    if(tt==-1) cout<<"inf";
    else cout<<tt;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Gröbner基的特殊高斯消元算法通过特殊的高斯消元法来计算Gröbner基。这种算法可以处理规模很大的问题,因为它使用了一些技巧来减少计算量。 算法步骤: 1. 对于给定的理想$I$,构造一个包含$I$的理想$J$,使得$J$的Gröbner基可以用特殊高斯消元法计算出来。这个步骤通常使用Buchberger算法来完成。 2. 对于$J$的Gröbner基$G$的每个元素$g_i$,计算一个消元子$f_i$,使得$f_i$是$g_i$的最高项。 3. 对于每对消元子$f_i$和$f_j$,如果它们是可约的,那么使用Buchberger算法计算它们的最小公倍式,并用它来代替$f_i$和$f_j$。 4. 对于每个消元子$f_i$,计算一个被消元子$h_i$,使得$h_i$是$I$所有多项式不包含$f_i$的最高项。 5. 对于每对消元子$f_i$和被消元子$h_j$,如果它们是可约的,那么使用Buchberger算法计算它们的最小公倍式,并用它来代替$f_i$和$h_j$。 6. 重复步骤4和5,直到没有可约的消元子和被消元子。 7. 对于每个消元子$f_i$和被消元子$h_i$,计算它们的最小公倍式$u_i$。 8. 返回$u_1,\dots,u_m$,它们组成了$I$的Gröbner基。 C代码实现: 以下是一个简单的C代码实现,用于计算给定多项式的Gröbner基。这个代码只是一个示例,可能需要进行修改才能处理更复杂的问题。 ```c #include <stdio.h> #include <stdlib.h> typedef struct { int deg; // 多项式的次数 int *coeffs; // 多项式的系数 } poly_t; // 计算两个多项式的最小公倍式 poly_t *lcm(poly_t *f, poly_t *g) { // TODO: 实现计算最小公倍式的代码 } // 计算多项式的消元子 poly_t *lead_term(poly_t *f) { poly_t *lt = (poly_t *) malloc(sizeof(poly_t)); lt->deg = f->deg; lt->coeffs = (int *) calloc(lt->deg + 1, sizeof(int)); lt->coeffs[lt->deg] = 1; return lt; } // 计算多项式的被消元子 poly_t *elim_term(poly_t *f, poly_t **polys, int n) { poly_t *et = (poly_t *) malloc(sizeof(poly_t)); et->deg = 0; et->coeffs = (int *) calloc(1, sizeof(int)); for (int i = 0; i < n; i++) { if (polys[i] == f) continue; int deg = polys[i]->deg - f->deg; if (deg < 0) continue; int coeff = polys[i]->coeffs[polys[i]->deg]; if (coeff == 0) continue; if (deg > et->deg) { et->coeffs = (int *) realloc(et->coeffs, (deg + 1) * sizeof(int)); for (int j = et->deg + 1; j <= deg; j++) { et->coeffs[j] = 0; } et->deg = deg; } et->coeffs[deg] = coeff; } return et; } // 判断两个多项式是否可约 int is_reducible(poly_t *f, poly_t *g) { // TODO: 实现判断多项式是否可约的代码 } // 计算Gröbner基 poly_t **groebner(poly_t **polys, int n) { // TODO: 实现计算Gröbner基的代码 } int main() { // TODO: 编写测试代码 return 0; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值