https://www.luogu.org/problemnew/show/P3213
首先要预处理出10^6以内的勾股数对。
我们有:(m^2-n^2)^2+(2mn)^2=(m^2+n^2)^2
枚举m,n就行了。
然后发现连出的图很稀疏,是一个森林上加几条非树边。
那么就对于每个连通块2^num枚举非树边较深的端点是否选,树形DP f[i][0/1]表示i号点是否选,子树的方案数。
#include<cstdio> #include<cstring> #include<cmath> #include<algorithm> #include<vector> using namespace std; const int N=1000005,md=1000000007; vector<int> vct[N]; int n,i,j,k,l,a[N],head[N],adj[N],nxt[N],g[N],h[N],num,f[N][2],ans=1,w,v[N],cnt,dzx[N]; bool b[N]; inline void Read(int &x) { char c; while((c=getchar())<'0'||c>'9'); x=c-'0'; while((c=getchar())>='0'&&c<='9') x=x*10+c-'0'; } int gcd(int a,int b) { if(!b) return a; return gcd(b,a%b); } inline void addedge(int u,int v) { if(!a[u]||!a[v]||gcd(u,v)!=1) return; adj[++l]=v; nxt[l]=head[u]; head[u]=l; adj[++l]=u; nxt[l]=head[v]; head[v]=l; //printf("%d %d\n",u,v); } void dfs(int x,int dad) { v[x]=++cnt; for(int y=head[x];y;y=nxt[y]) if(adj[y]!=dad) { if(!v[adj[y]]) { b[y]=true; dfs(adj[y],x); } else if(v[adj[y]]>v[x])//注意一条边不能被统计两次 { ++num; g[num]=x,h[num]=adj[y]; vct[x].push_back(adj[y]); } } } int pw(int a,int b) { int rtn=1; while(b) { if(b&1) rtn=1ll*rtn*a%md; a=1ll*a*a%md; b>>=1; } return rtn; } void work(int x) { int y; f[x][0]=1,f[x][1]=(pw(2,a[x])-1+md)%md; for(y=head[x];y;y=nxt[y]) if(b[y]) { work(adj[y]); f[x][0]=1ll*f[x][0]*(f[adj[y]][0]+f[adj[y]][1])%md; f[x][1]=1ll*f[x][1]*f[adj[y]][0]%md; } if(dzx[x]==1) f[x][0]=0; else if(dzx[x]==-1) f[x][1]=0; for(y=vct[x].size()-1;y>=0;--y) if(dzx[vct[x][y]]==1) break; if(y>=0) f[x][1]=0; } int main() { Read(n); for(i=1;i<=n;++i) { Read(j); a[j]++; } for(i=1;i<=1000000;++i) for(j=i+1;2ll*i*j<=1000000;++j) if(1ll*j*j-1ll*i*i<=1000000) addedge(1ll*j*j-1ll*i*i,2ll*i*j); for(i=1;i<=1000000;++i) if(a[i]&&!v[i]) { num=0; dfs(i,-1); w=0; for(j=0;j<(1<<num);++j) { for(k=1;k<=num;++k) if((1<<(k-1))&j) dzx[h[k]]=1; else dzx[h[k]]=-1; work(i); w=(1ll*w+f[i][0]+f[i][1])%md; } ans=1ll*ans*w%md; } printf("%d",(ans-1+md)%md); return 0; }