烷烃同分异构体个数的计算——BZOJ 4271:Chemistry
源于一节化学课的思想跳跃
化学课终于教到有机了,第一次知道什么是甲烷
看到烷烃的结构,尼玛这不就是棵树嘛
化学老师也是闲,叫我们来推推戊烷己烷的同分异构体个数
这对一个有OI基础的人来说基本不难
然后就顺势想到了如何编程求烷烃的同分异构体个数
心想着:这毕竟是一棵树啊,树的话OI就能考。。。
激动万分
然而想了想发现自己连口胡算法都不会。。。
为自己的智商感动得泪流满面
下课上网找题解,抱着不大的希望在BZOJ搜索了下Chemistry,就找到原题了
泪流满面,然而才8人A,其中大部分都是打表的。。。
找到ACM原题也没能找到题解。。。我擦勒
问了下Claris,知道了貌似要枚举树重心去DP一发。。。听都没听过的新思路
维基搜了下,没找到公式,OEIS的列表倒是有一点用(别跟我说公式其实就藏在OEIS下面一堆英文中QwQ
还找到了范浩强神犇的Blog,解法也是与重心有关,然而题解的网站挂了。。。
我擦勒什么题解都没有,而且听到算法梗概就觉得已经超出了我的思考能力了
泪流满面
然后隔没多久,接触到了有根树无根树的计数方法
我擦勒无根树计数不就是和求烷烃的数量啊
想了想,度数限制不大于4。。。感动
接下来就是一系列不服输的行为了
照着公式乱啃找计数和去重原理,自己琢磨出度数限制的方案
两节理综荒废,最后推出了一个什么鬼的递推公式。。。
上机实验,发现各种错误,在草稿纸上改公式啊改公式
我还把35种壬烷都画了出来。。。
然后弄出了个$O(n^3)$的算法,顺利过掉ACM的$N\le50$规模
BZOJ的$N\le500$由于高精度和算法复杂度太大,最慢9s
睡觉的时候才发现自己硬是把$O(n^2)$的算法写成$O(n^3)$
智商感人
思路就是用背包DP计算满足度数限制(每个点的度数不大于4)的有根树的数量
然后去重计算满足度数限制的无根树的数量
原理类似普通有根树和无根树的计数原理,建议结合看
#include <cstdio> #include <algorithm> #define rep(i, l, r) for(int i=l; i<=r; i++) #define down(i, l, r) for(int i=l; i>=r; i--) #define clr(x, c) memset(x, c, sizeof(x)) #define travel(x) for(edge *p=fir[x]; p; p=p->n) #define pb push_back #define ll long long #define ull unsigned long long #define maxn 501 #define maxl 29 #define mx 100000000 #define inf 0x7fffffff using namespace std; inline int read() { int x=0, f=1; char ch=getchar(); while (ch<'0' || ch>'9') {if (ch=='-') f=-1; ch=getchar();} while ('0'<=ch && ch<='9') x=x*10+ch-'0', ch=getchar(); return x*f; } struct node{int v[maxl], l;} f[maxn], k[maxn], h[maxn], g[5][maxn], sum, I, Nul, m[5]; int n, T; ll tmpc[maxl]; inline void Add(node &a, node b) { rep(i, 0, b.l-1) a.v[i]+=b.v[i]; a.l=max(a.l,b.l); rep(i, 0, a.l-1) if (a.v[i]>=mx) a.v[i+1]+=a.v[i]/mx, a.v[i]%=mx; while (a.v[a.l]!=0) { if (a.v[a.l]>=mx) a.v[a.l+1]+=a.v[a.l]/mx, a.v[a.l]%=mx; a.l++; } } inline void Sub(node &a, node b) { rep(i, 0, a.l-1) a.v[i]-=b.v[i]; rep(i, 0, a.l-1) while (a.v[i]<0) a.v[i+1]--, a.v[i]+=mx; while (a.v[a.l-1]==0 && a.l>1) a.l--; } inline void Add(node &a) { a.v[0]++; rep(i, 0, a.l-1) if (a.v[i]>=mx) a.v[i+1]+=a.v[i]/mx, a.v[i]%=mx; else return; while (a.v[a.l]!=0) { if (a.v[a.l]>=mx) a.v[a.l+1]+=a.v[a.l]/mx, a.v[a.l]%=mx; a.l++; } } inline node Sub(node a) { a.v[0]--; rep(i, 0, a.l-1) if (a.v[i]<0) a.v[i+1]--, a.v[i]+=mx; else return a; while (a.v[a.l-1]==0 && a.l>1) a.l--; return a; } inline node Div(node a, int x) { int tmp=0; down(i, a.l-1, 0) tmp=tmp*mx+a.v[i], a.v[i]=tmp/x, tmp%=x; while (a.v[a.l-1]==0 && a.l>1) a.l--; return a; } node operator * (node a, node b) { node c=Nul; c.l=a.l+b.l-1; //clr(tmpc,0); rep(i, 0, c.l) tmpc[i]=0; rep(i, 0, a.l-1) rep(j, 0, b.l-1) tmpc[i+j]+=1LL*a.v[i]*b.v[j]; rep(i, 0, c.l-1) if (tmpc[i]>=mx) tmpc[i+1]+=tmpc[i]/mx, c.v[i]=tmpc[i]%mx; else c.v[i]=tmpc[i]; while (tmpc[c.l]!=0) { if (tmpc[c.l]>=mx) tmpc[c.l+1]=tmpc[c.l]/mx, c.v[c.l]=tmpc[c.l]%mx; else tmpc[c.l+1]=0, c.v[c.l]=tmpc[c.l]; c.l++; } return c; } node C(node x, int y) { node now=I; rep(i, 1, y) now=now*x, Add(x); rep(i, 1, y) now=Div(now,i); return now; } inline void Put(node &a) { printf("%d", a.v[a.l-1]); down(i, a.l-2, 0) printf("%08d", a.v[i]); } int main() { n=read(); I.l=1; I.v[0]=1; f[1]=k[1]=h[1]=I; rep(o, 0, 4) g[o][o]=I; rep(a, 2, n) { //printf("%d %d\n", a, T); sum=Nul; rep(o, 1, 3) Add(sum,g[o][a-1]); f[a]=sum; Add(sum,g[4][a-1]); rep(c, 1, 4) if (a*c<=n) m[c]=C(f[a],c); down(o, 4, 1) rep(c, 1, o) rep(i, a*c+o-c, n-1) if (g[o-c][i-a*c].l!=0) { node &A=g[o][i], B=g[o-c][i-a*c]*m[c]; rep(i, 0, B.l-1) A.v[i]+=B.v[i]; A.l=max(A.l,B.l); rep(i, 0, A.l-1) if (A.v[i]>=mx) A.v[i+1]+=A.v[i]/mx, A.v[i]%=mx; while (A.v[A.l]!=0) { if (A.v[A.l]>=mx) A.v[A.l+1]+=A.v[A.l]/mx, A.v[A.l]%=mx; A.l++; } } k[a]=sum; rep(i, 1, (a-1)/2) if (i!=a-i) Sub(sum,f[i]*f[a-i]); if (a%2==0) Sub(sum,Div(f[a/2]*Sub(f[a/2]),2)); h[a]=sum; } Put(h[n]); return 0; }
有老司机知道枚举重心DP的做法么。。。现在依旧不知道QwQ
update 16.01.10:
后来在书中翻到了一句话:
假如两棵无根树同构,那么以各自的重心作为根的有根树也同构
也许这就是正解了。。。
有根树背包DP的时候只要使得子树大小小于$\frac n2$就行了
748ms AC
Feb 10, 2016 03:43:13 PM
大神,求问在哪里学习的有根数计数方法
我再网上看到一篇,貌似运用了高数知识TAT
Feb 10, 2016 05:55:16 PM
就背包一发就可以了啊。。。
貌似记得是自己YY出来的。。。
嗯记得有根树计数是有公式的,你要我可以找找
Feb 10, 2016 08:55:12 PM
%%%
能不能大致说说做法捏?
Feb 12, 2016 10:11:36 AM
有根树的话就背包f(i,j)表示结点数为i且森林数为j的方案数是多少(不计顺序)
然后我们要求结点数为a的有根树数目,就从f(a-1,k)得到,k任取
Mar 04, 2016 08:21:32 PM
%%%,时隔9个月大神写的东西终于让我再去切了这道题。
“假如两棵无根树同构,那么以各自的重心作为根的有根树也同构”,受益匪浅。
Mar 05, 2016 02:22:53 PM
啊我貌似是从哪本书看来的这句话(撕烤)
Mar 21, 2016 08:41:57 AM
感觉之前在某GD最坑爹学校集训写的某道名字是化学的题一模一样= =做法差不多= =
子树大小不超过1/2不应该是最小表示法的问题吗,否则就可以旋转变得相同
然后树的重心也是一个DP
因为树的重心的性质,可以logn枚举
然后最小表示法表示子树
这样就是n^3logn了= =
并不会n^3做法