1138 代码等式
时间限制:500MS 内存限制:65536K
提交次数:59 通过次数:21
题型: 编程题 语言: 无限制
Description
一个代码等式就是形如x1x2...xi=y1y2...yj,这里xi和yj是二进制的数字(0或1)或者是一个变量(如英语中的小写字母)。每一个变量都是一个有固定长度的二进制代码。例如:
a,b,c,d,e是变且它们的长度分别是4,2,4,4,2。考虑等式:1bad1=acbe,这个等式共有16组解。现要求任给一个等式,计算一共有多少组解。
(变量最多26个,长度和不超过10000)
输入格式
第一行数N为变量个数;
第二行N个数,为每个变量的位数
第三行为一个等式
输出格式
输出解的个数,无解输出0
输入样例
5
4 2 4 4 2
1bad1=acbe
输出样例
16
题意:
给你一个等式,等式两端是01串,也包含小写字母。
小写字母可能代表多个位。
求满足等式的解有多少个。
解题思路:
首先,每个字母代表多个位,要找到一种唯一而方便的映射,方便后续操作。下面是映射到一段连续的数字上:
用数组len存下每个字母的长度(len[0]表示‘a’,len[1]表示‘b’,以此类推)。
同时用数组begin记下每个字母的起始位置。(0,1的位置用来表示0和1。)显然字母‘a’的起始位置为2,即begin[0]=2;字母‘b’的起始位置begin[1]=begin[0]+len[0]。。。
即begin[i]=begin[i-1]+len[i-1](i>0)
这样就把字母映射到了连续的数字上。
然后把等式分成两边,按照上面的方法,拆除一位一位的,每位用一个数字来表示,如len[0]=4,len[1]=2,则a可用数字2,3,4,5表示,b可用数字6,7表示。此时对于等式“a=1b0”可以拆成2,3,4,5=1,6,7,0。
拆开后,首先判断两边的位数是否相等,不等的话等式肯定不成立。
接下来,两边在同一位置的数表示他们相等,可以放到同一个集合。这里可以采用并查集这种数据结构,详细的介绍请自行搜索。
在并查集中,两个元素在同一集合中,当且仅当他们有相同的根。在一开始初始化的时候,每个元素都是单独的一个集合,他们的根是本身。用数组Father表示就是Fathe[i]=i。
当发现i和j在同一位置时,即i和j相等,他们所在的集合也相等,可用f1、f2表示i、j的根,如果f1==f2说明他们已在同一集合中,不等的话执行Father[f1]=f2或Father[f2]=f1可将两个集合合并。
根据这个,相信你能想到,找出某个元素的根的做法。
当元素很多时,需要进行路径压缩,减少寻根的步骤,不过本题不需要,具体做法请自行思考。
最后,所有的数字都会被分成几个集合,集合内的数字都是相等。除了那两个包含0和1的集合外,其余的集合数为t,他们都有两种可能(0或1),则答案为2^t。
需要注意的是,必须先判断0和1是否在同一个集合中,在的话,等式肯定不成立。
另外,2^t用int会溢出而wa,可转成long long。
#include <cstdio>
#include <cstring>
#include <cmath>
#include <iostream>
const int N=10000+10;
char tmp[N];
int Left[N],Right[N],Father[N];
using namespace std;
int getfather(int v,int &step)
{
step=1;
while(Father[v]!=v)
{v=Father[v];step++;}
return v;
}
int conbine(int v,int w)
{
int s1,s2,f1,f2;
f1=getfather(v,s1);
f2=getfather(w,s2);
if(f1==f2)
{return 0;}
if(s1<=s2)
{
Father[f1]=f2;
}
else
{
Father[f2]=f1;
}
return 1;
}
const int M=3020; //用3020是因为2^10000结果是3000多位
int res[M],t[M];
void add(int a[],int b[])
{
int e;
e=0;
memset(t,0,sizeof(t));
for(int i=0;i<M;i++)
{
t[i]=a[i]*2+e;
if(t[i]>9)
{
t[i]-=10;
e=1;
}
else
e=0;
}
for(int i=0;i<M;i++)
b[i]=t[i];
}
void _pow(int n)
{
if(!n)
{
printf("0\n");
return;
}
memset(res,0,sizeof(res));
res[0]=1;
for(int i=0;i<n;i++)
{
add(res,res);
}
int i;
for(i=M-1;i>0;i--)
if(res[i])
break;
// printf("%d\n",i);
for(;i>=0;i--)
printf("%d",res[i]);
printf("\n");
}
int main()
{
int n,total,len[30],begin[30];
scanf("%d",&n);
begin[0]=2;
total=0;
for(int i=0;i<n;i++)
{
scanf("%d",&len[i]);
total+=len[i];
if(i)
begin[i]=begin[i-1]+len[i-1];
}
for(int i=0;i<total+2;i++)
Father[i]=i;
scanf("%s",tmp);
int part=strstr(tmp,"=")-tmp;
int left_cnt=0;
for(int i=0;i<part;i++)
{
if(tmp[i]=='0'||tmp[i]=='1')
{
Left[left_cnt]=tmp[i]-'0';
left_cnt++;
continue;
}
int t=tmp[i]-'a';
for(int k=0;k<len[t];k++)
{
Left[left_cnt]=begin[t]+k;
left_cnt++;
}
}
int right_cnt=0;
for(int i=part+1;tmp[i];i++)
{
if(tmp[i]=='0'||tmp[i]=='1')
{
Right[right_cnt]=tmp[i]-'0';
right_cnt++;
continue;
}
int t=tmp[i]-'a';
for(int k=0;k<len[t];k++)
{
Right[right_cnt]=begin[t]+k;
right_cnt++;
}
}
/*
for(int i=0;i<left_cnt;i++)
printf("%d ",Left[i]);
printf("\n");
for(int i=0;i<right_cnt;i++)
printf("%d ",Right[i]);
printf("\n");
*/
if(left_cnt!=right_cnt)
{
printf("0\n");
return 0;
}
for(int i=0;i<left_cnt;i++)
{
if(conbine(Left[i],Right[i]))
total--; //total表示不包含0和1的集合数,当两个集合合并时,显然总的集合数减一。
}
//printf("total:%d\n",total);
if(!conbine(0,1))
printf("0\n");
else
cout<<(long long )pow(2.0,total)<<endl;
// _pow(total);
//一开始用数组模拟,过了之换成long long发现也可以过。
}
============
以前写的、、