洛谷 4106 / bzoj 3614 [HEOI2014]邏輯翻譯——思路+類似FWT
題目:https://www.luogu.org/problemnew/show/P4106
https://www.lydsy.com/JudgeOnline/problem.php?id=3614
可以先把給出的東西排序成這樣:
-1 -1 -1
-1 -1 1
-1 1 -1
-1 1 1
1 -1 -1
1 -1 1
1 1 -1
1 1 1
就是後面看成低位、前面看成高位,1看成1、-1看成0的二進位制的順序。
發現把第1行和第2行相加再除以2,得到的就是與 x3 無關的所有係數 a 在 x1 = -1 , x2 = -1 的情況下的值;
第2行減第1行再除以2,得到的就是與 x3 有關的所有係數 a 在 x3 = 1 , x1 = -1 , x2 = -1 的情況下的值;
把所有行兩個一組相加的答案放在一起考慮,就是所有與 x3 無關的係數在 x1 , x2 取 -1 , -1 ; -1 , 1 ; 1 , -1 ; 1 , 1 的情況下的值;相減的話就是 x1 , x2 取各種值,x3的值都是1的情況;這就是一個子問題了。
所有把所有行兩個一組相加除以2的值放在前半部分,兩個一組相減(下面減上面)除以2的值放在後半部分,大概就能做了。
最後第1行就是和所有 x 都無關的那個 a ,也就是常數項;第2行仔細考慮一下,是 x1 的係數。即,算出來的值在第 i 行的就是取 x 方案為 i ( i 就像狀壓了取哪些x乘起來的那一項)的項的係數。排序輸出即可。
注意分數中途爆 int 。
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define ll long long using namespace std; const int N=(1<<20)+5,M=25; int n,bin[M],r[N]; char ch[M]; int gcd(int a,int b){return b?gcd(b,a%b):a;} struct Node{ int x,y;View Codevoid yf() { if(!y||!x)return; if(y<0)y=-y,x=-x; int g=gcd(x>0?x:-x,y);x/=g;y/=g; } Node operator+ (const Node &b)const { Node c; c.y=(ll)y*b.y/gcd(y,b.y); c.x=(ll)x*c.y/y+(ll)b.x*c.y/b.y; c.yf(); return c; } Node operator- (const Node &b)const { Node u,v;u.x=x;u.y=y; v.x=b.x;v.y=-b.y; return u+v; } }a[2][N]; bool cmp(int a,int b) { for(int i=0;i<n;i++) { if(!a)return true; if(!b)return false; if((a&bin[i])&&(b&bin[i])) { a-=bin[i];b-=bin[i];continue; } if(!(a&bin[i])&&!(b&bin[i]))continue; if(a&bin[i])return true; if(b&bin[i])return false; } } void solve(int len) { for(int i=0;i<len;i++) if(i<r[i])swap(a[0][i],a[0][r[i]]); for(int R=len,u=1,v=0;R>1;R>>=1,u=!u,v=!v) { int tot=-1;//here for(int i=0;i<len;i+=R) { for(int j=0;j<R;j+=2) { a[u][++tot]=a[v][i+j]+a[v][i+j+1]; a[u][tot].y*=2; a[u][tot].yf(); } for(int j=0;j<R;j+=2) { a[u][++tot]=a[v][i+j+1]-a[v][i+j]; a[u][tot].y*=2; a[u][tot].yf(); } } } } int main() { scanf("%d",&n); bin[0]=1;for(int i=1;i<=n;i++)bin[i]=bin[i-1]<<1; for(int i=0;i<bin[n];i++)r[i]=(r[i>>1]>>1)+((i&1)?bin[n-1]:0); for(int i=0;i<bin[n];i++) { scanf("%s",ch); long long d=0; for(int j=0;j<n;j++) d|=(ch[j]=='+'?bin[j]:0); double tmp; scanf("%lf",&tmp); a[0][d].x=(int)(tmp*100+(tmp>0?0.5:-0.5)); a[0][d].y=100;///round! a[0][d].yf(); } solve(bin[n]);int fx=n&1; for(int i=0;i<bin[n];i++)r[i]=i; sort(r,r+bin[n],cmp); for(int i=0,u=r[0];i<bin[n];i++,u=r[i]) if(a[fx][u].x) { printf("%d",a[fx][u].x); if(a[fx][u].y>1)printf("/%d",a[fx][u].y); if(u) { putchar(' '); for(int j=0;j<n;j++) if(u&bin[j])printf("x%d",j+1); } puts(""); } return 0; }
但這樣空間在洛谷上能過, bzoj 上過不了。本來算下來就很大。
考慮不要把 a[ ] 開成滾動陣列了。比如不要把兩個一組相加的值放在前一半、相減的值放在後一半,而是把兩行 i 和 i+1 相加的值放在第 i 行,相減的值放在第 i+1 行;這樣就和 FWT 的模板長得更像,只開一個 a[ ] 而不用滾動也能應付過來。
考慮這樣算了一次之後,下一次是哪裡相加、相減。其實就相當於是原來的前一半,其間穿插上後一半的值;所以原來是前一半相鄰兩行再相加,現在就是隔一行相加;即原來是分治到前半部分和後半部分,現在是分治到奇數項和偶數項;這樣下去就是 i 和 i+4 匹配、i 和 i+8 匹配……套上 FWT 的那個迴圈就行了。
仔細想一想,發現這樣算出來,第1行是常數項,第2行是隻和 x3 有關的項……也就是角標的二進位制最低位是1表示有 x3 ,最高位是1表示有 x1 ……
如果一開始的排序是:
-1 -1 -1
1 -1 -1
-1 1 -1
1 1 -1
……
這樣的話算出來的結果就是角標二進位制最低位是1表示有x1……這樣的。
輸出可以寫 dfs ,先搜這一位填1的,再搜這一位填0的;搜下一位之前輸出一下,即每個方案在它填完最高位的1之後輸出,如果是填了0就不輸出,因為這個方案在最靠近的1被填了之後曾經輸出過。
#include<cstdio> #include<cstring> #include<algorithm> #include<cmath> #define ll long long using namespace std; const int N=(1<<20)+5,M=25; int n,bin[M]; char ch[M]; int gcd(int a,int b){return b?gcd(b,a%b):a;} struct Node{ int x,y; void yf() { if(!y||!x)return; if(y<0)y=-y,x=-x; int g=gcd(x>0?x:-x,y);x/=g;y/=g; } Node operator+ (const Node &b)const { Node c; c.y=(ll)y*b.y/gcd(y,b.y);//ll c.x=(ll)x*c.y/y+(ll)b.x*c.y/b.y; // return c; c.yf(); return c; } Node operator- (const Node &b)const { Node u,v;u.x=x;u.y=y; v.x=b.x;v.y=-b.y; return u+v; } void print(int id) { if(!x)return; yf(); if(y>1)printf("%d/%d",x,y); else printf("%d",x); if(id) { putchar(' '); for(int i=0;i<n;i++) if(id&bin[i])printf("x%d",i+1); } puts(""); } }a[N]; void dfs(int cr,int ml) { if(!cr||ml&bin[cr-1]) a[ml].print(ml); if(cr==n)return; dfs(cr+1,ml|bin[cr]); dfs(cr+1,ml); } int main() { scanf("%d",&n); int len=(1<<n); bin[0]=1;for(int i=1;i<=n;i++)bin[i]=bin[i-1]<<1; for(int i=0;i<len;i++) { scanf("%s",ch); int d=0; for(int j=0;j<n;j++) d|=(ch[j]=='+'?bin[j]:0); double tmp; scanf("%lf",&tmp); a[d].x=round(tmp*100); a[d].y=100; a[d].yf(); } for(int R=2;R<=len;R<<=1) { for(int i=0,m=R>>1;i<len;i+=R) for(int j=0;j<m;j++) { Node x=a[i+j],y=a[i+m+j]; a[i+j]=x+y; a[i+m+j]=y-x; a[i+j].y*=2; a[i+j].yf(); a[i+m+j].y*=2; a[i+m+j].yf(); } } dfs(0,0); return 0; }View Code