洛谷 P4106 / bzoj 3614 [ HEOI 2014 ] 逻辑翻译 —— 思路+递归
题目:https://www.luogu.org/problemnew/show/P4106
https://www.lydsy.com/JudgeOnline/problem.php?id=3614
从很小的情况考虑,看题面上的样例:
x1=+1 x2=+1 0
x1=+1 x2=-1 1
x1=-1 x2=+1 2
x1=-1 x2=-1 3
手算的做法应该是设原式为 a0 + a1x1 + a2x2 + a3x1x2
通过加减,把关于 x2,x1 的项逐渐消去,得到常数项后再回代,逐个得出答案;
然后我们发现加减的两个式子的位置也是有规律的,如果把-1看作0,+1看作1,式子的系数从小到大排序:
x1=-1 x2=-1 3 —— 1
x1=-1 x2=+1 2 —— 2
x1=+1 x2=-1 1 —— 3
x1=+1 x2=+1 0 —— 4
12相加得到 x1 = -1 时无 x2 的值(+a0),34相加得到 x1 = 1 时无 x2 的值(+a0);
21相减得到 x2 = 1 时无 x1 的值(-a3),43相减得到 x2 = 1 时无 x1 的值(+a3);
两个相加的结果再相加得出a0,相减得出a1,两个相减的结果同理;
可以发现,这样做其实就是把问题的规模变成了一半,可以递归求解;
但空间很小,为了不递归,可以仿照FFT,FWT等,通过位置安排来直接循环做;
实际上,最初的相邻两位置加减,让 xn 的有无与该位置末位的 0/1 对应;
以此类推,如果倍增加减,则每个 xi 的有无就和该位置的第 i 位的 0/1 对应;
“xi 的有无”意思是这个值中关于 xi 的项还是否存在;
所以最后答案的 xi 情况就和其位置上的 0/1 分布情况一样,像FWT一样跳着加减就能得到;
过程中不要除以2,最后输出时把 2^n 放在分母上;
递归输出即可按照字典序,详见代码囧
代码如下:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; typedef double db; int const xn=(1<<20); int n,lim,f[xn]; char dc[25]; int gcd(int a,int b){return b?gcd(b,a%b):a;} void get() { scanf("%s",dc); int t=0; db ff; for(int j=0;j<n;j++){t<<=1; if(dc[j]=='+')t|=1;} scanf("%lf",&ff); if(ff>0)f[t]=(int)(ff*100+0.5); else f[t]=(int)(ff*100-0.5);//! } void print(int x,int s) { if(x==0)return; if(x<0)putchar('-'),x=-x;// int t=(100<<n),g=gcd(x,t);//t:100*(/2)^n if(g==t)printf("%d",x/g); else printf("%d/%d",x/g,t/g); if(!s){puts(""); return;} putchar(' '); for(int i=n-1,nw=1;i>=0;i--,nw++)if(s&(1<<i))printf("x%d",nw); puts(""); } void work(int nw,int s) { int t=((s<<1|1)<<(n-nw)); print(f[t],t); if(nw==n)return; work(nw+1,(s<<1)|1); work(nw+1,(s<<1)); } int main() { scanf("%d",&n); lim=(1<<n); for(int i=1;i<=lim;i++)get(); for(int i=0;i<n;i++) { int t=(1<<i),d=(t<<1); for(int j=0;j<lim;j+=d) for(int k=0;k<t;k++) { int x=f[j+k],y=f[j+t+k]; f[j+k]=x+y; f[j+t+k]=y-x; } } print(f[0],0); work(1,0); return 0; }