【高斯消元】[NOIP2004]虫食算 —— 正解

题目描述:所谓虫食算,就是原先的算式中有一部分被虫子啃掉了,需要我们根据剩下的数字来判定被啃掉的字母。来看一个简单的例子:

+43a9865a045008468a663344445506978

其中a号代表被虫子啃掉的数字。
根据算式,我们很容易判断:第一行的两个数字分别是5和3,第二行的数字是5。
现在,我们对问题做两个限制:
首先,我们只考虑加法的虫食算。这里的加法是N进制加法,算式中三个数都有N位,允许有前导的0。
其次,虫子把所有的数都啃光了,我们只知道哪些数字是相同的,我们将相同的数字用相同的字母表示,不同的数字用不同的字母表示。
如果这个算式是N进制的,我们就取英文字母表午的前N个大写字母来表示这个算式中的0到N-1这N个不同的数字:但是这N个字母并不一定顺序地代表0到N-1)。
输入数据保证N个字母分别至少出现一次。    

+BADCCBDADCCC

上面的算式是一个4进制的算式。很显然,我们只要让ABCD分别代表0123,便可以让这个式子成立了。
你的任务是,对于给定的N进制加法算式,求出N个不同的字母分别代表的数字,使得该加法算式成立。输入数据保证有且仅有一组解。

样例输入:
5
ABCED
BDACE
EBBAA

样例输出:
1 0 3 4 2

【数据规模】

对于30%的数据,保证有N≤10;
对于50%的数据,保证有N≤15;
对于全部的数据,保证有N≤26。

下面题解开始
如果我们直接暴搜显然是不行的复杂度有O(nn)。。。。
那么我们可以发现因为有n列那么,那么我们可以发现令第一行第i个为xi,第二行第i个为yi, 第三行为zi

+xiyizi

然后对于每一列我们可以得到一个方程我们令di表示第i列向第i1列进位的数量di在0和1之间那么我们对于任意一列可以发现(这里的n表示进制和字母数量)xi+yi+di+1=zi+ndi化简可以得到xi+yizi=ndidi+1
那么我们现在列出来的矩阵就是长得这个样子(样例的矩阵)
10111101000011001001100115000015000015000015000015
矩阵右边部分的第i列表示的是我们的当前需要的di的数量,那么现在的我们需要通过枚举不同的di来获取不同的矩阵又因为我们的di只能是0或者1同时我们的d1(也就是最高位)显然没有进位那么我们需要枚举的di就有n1个那么复杂度就是O(2n1n3)那么显然不能胜任本题目看到我们的这个矩阵,仔细观察一下既然我们左边的所有系数项目已经知道了那么我们为什么不把它先给用高斯约当消元法消元之后再看我们另消元之后得到的每一个系数为Ki右边变成了Gi,j那么我们上面的矩阵就变成了
K100000K200000K300000K400000K5G1,1G2,1G3,1G4,1G5,1G1,2G2,2G3,2G4,2G5,2G1,3G2,3G3,3G4,3G5,3G1,4G2,4G3,4G4,4G5,4G1,5G2,5G3,5G4,5G5,5
这里显然右边的矩阵可以跟着左边的变化(我们右边的矩阵表示的就是每一个di需要的个数)而求得那么我们可以先预处理一下O(n3)的复杂度,此时我们只需要枚举每一个di这里的复杂度是O(2n1)那么因为要把每一个di带入那么我们带入矩阵中因为每一个系数Gi,j已经知道了那么么一个答案举个例子A=ni=1G1,i×diK1同理有B=ni=1G2,i×diK2那么我们可以用O(n2)的时间来带入那么我们总的复杂度为O(2n1n2)
这个时候只需要判断三个不合法的情况

  1. 当前的整数有没有被使用过
  2. 当前的数是不是个整数
  3. 当前的数字在不在给定的范围内

    因为大部分的情况下枚举完成带入时如果不合法那么第一个A很容易就无法被整除了所以实际的复杂度更加接近于O(2n1n)

开始写吧!!!

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cstdlib>
using namespace std;
typedef long long LL;
const int MAXN = 26;
int _abs(int u){return u>0?u:-u;}
int gcd(int a, int b){
    int c;
    while(b){
        c = a % b;
        a = b;
        b = c;
    }
    return a;
}
int Ma[MAXN+10][MAXN+10], g[MAXN+10][MAXN+10], d[MAXN+10], n, x[MAXN+10];
bool vis[MAXN+10];
bool check(){
    memset(vis, 0, sizeof vis);
    for(int i=1;i<=n;i++){
        int sum = 0;
        for(int j=2;j<=n;j++)
            sum += d[j] * g[i][j];
        if(sum % Ma[i][i] != 0) return false;
        sum /= Ma[i][i];
        if(sum < 0 || sum >= n) return false;
        if(vis[sum]) return false;
        vis[x[i] = sum] = true;
    }
    return true;
}
void dfs(int u){
    if(u == 1){
        if(check()){
            for(int i=1;i<n;i++)
                printf("%d ", x[i]);
            printf("%d\n", x[n]);
            exit(0);
        }
        return ;
    }
    d[u] = 0; dfs(u-1);
    d[u] = 1; dfs(u-1);
}
void solve(int n){
    int col, row;
    for(col=row=1;row<=n&&col<=n;row++,col++){
        int Maxp = row;
        for(int i=row+1;i<=n;i++)
            if(_abs(Ma[i][col]) > _abs(Ma[Maxp][col]))
                Maxp = i;
        if(Maxp != row){
                swap(Ma[Maxp], Ma[row]);
                swap(g[Maxp], g[row]);
        }
        for(int i=1;i<=n;i++){
            if(i != row && Ma[i][col] != 0){
                int lcm = Ma[i][col] * Ma[row][col] / gcd(Ma[i][col], Ma[row][col]);
                int d1 = lcm / Ma[i][col], d2 = lcm / Ma[row][col];
                for(int j=1;j<=n;j++){
                    Ma[i][j] = Ma[i][j] * d1 - Ma[row][j] * d2;
                    g[i][j] = g[i][j] * d1 - g[row][j] * d2;
                }
            }
        }
    }
}
char s1[MAXN+10], s2[MAXN+10], s3[MAXN+10];
int  main(){
    scanf("%d", &n);
    scanf("%s%s%s", s1+1, s2+1, s3+1);
    memset(Ma, 0, sizeof Ma);
    for(int i=1;i<=n;i++){
        ++Ma[i][s1[i]-'A'+1];
        ++Ma[i][s2[i]-'A'+1];
        --Ma[i][s3[i]-'A'+1];
        g[i][i] = n, g[i][i+1] = -1;
    }
    solve(n);
    dfs(n);

    return 0;
}

另有搜索算法请见搜索大法好

posted on 2016-01-30 15:55  JeremyGuo  阅读(623)  评论(1编辑  收藏  举报

导航