C:显然可以设f[i][S]为当前考虑到第i位,[i,i+k)的状态为S的最小能量消耗,这样直接dp是O(nC(k,x))的。考虑矩阵快速幂,构造min+转移矩阵即可,每次转移到下一个特殊点然后暴力处理掉该点的贡献。可以预处理2p次转移矩阵进一步加速。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> #include<map> using namespace std; #define ll long long #define N 80 #define inf 1000000000000000000ll char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,m,k,q,c[10],id[1<<8],t; map<int,int> d; struct data { int x,y; bool operator <(const data&a) const { return x<a.x; } }p[30]; struct matrix { int n;ll a[N][N]; matrix operator *(const matrix&b) const { matrix c;c.n=n;for (int i=0;i<n;i++) for (int j=0;j<b.n;j++) c.a[i][j]=inf; for (int i=0;i<n;i++) for (int j=0;j<b.n;j++) for (int k=0;k<b.n;k++) c.a[i][j]=min(c.a[i][j],a[i][k]+b.a[k][j]); return c; } }f,a,g; matrix power(matrix a,int k) { matrix c;c.n=a.n; for (int i=0;i<t;i++) for (int j=0;j<t;j++) c.a[i][j]=inf; for (int i=0;i<t;i++) c.a[i][i]=0; for (;k;k>>=1,a=a*a) if (k&1) c=c*a; return c; } signed main() { #ifndef ONLINE_JUDGE freopen("c.in","r",stdin); freopen("c.out","w",stdout); #endif n=read(),m=read(),k=read(),q=read(); for (int i=1;i<=m;i++) c[i]=read(); for (int i=1;i<=q;i++) p[i].x=read(),p[i].y=read(),d[p[i].x]=p[i].y; sort(p+1,p+q+1); memset(id,255,sizeof(id)); for (int i=0;i<(1<<m);i++) { int cnt=0,j=i;while (j) j^=j&-j,cnt++; if (cnt==n) id[i]=t++; } a.n=t; for (int i=0;i<t;i++) for (int j=0;j<t;j++) a.a[i][j]=inf; for (int i=0;i<(1<<m);i++) if (id[i]>=0) { if (i&1) { for (int x=1;x<=m;x++) if (!(i&(1<<x))) a.a[id[i]][id[(i|(1<<x))>>1]]=c[x]; } else a.a[id[i]][id[i>>1]]=0; } f.n=1;for (int i=0;i<t;i++) f.a[0][i]=inf;f.a[0][id[(1<<n)-1]]=0; int cur=1; for (int i=1;i<=q;i++) { if (p[i].x-10>=cur) f=f*power(a,p[i].x-10-cur),cur=p[i].x-10; int u=i;while (u<q&&p[u+1].x-p[u].x<=10) u++; while (cur<p[u].x&&cur<k-n+1) { g.n=1;for (int x=0;x<t;x++) g.a[0][x]=inf; for (int j=0;j<(1<<m);j++) if (id[j]>=0) { if (j&1) { for (int x=1;x<=m;x++) if (!(j&(1<<x))) g.a[0][id[(j|(1<<x))>>1]]=min(g.a[0][id[(j|(1<<x))>>1]],f.a[0][id[j]]+c[x]+d[cur+x]); } else g.a[0][id[j>>1]]=min(g.a[0][id[j>>1]],f.a[0][id[j]]); } f=g; cur++; } i=u; } f=f*power(a,k-n-cur+1); cout<<f.a[0][id[(1<<n)-1]]; return 0; //NOTICE LONG LONG!!!!! }
D:首先考虑如果我们钦定了其中k条边一定在树中,有多少种方案。可以把每个连通分量缩点,将其权值定义为其大小,将一条边的权值定义为其两端的点权值之积,将一棵树的权值定义为所有边权值之积,显然这样缩点后所有树的权值之和,就是钦定这些边后原树的数量。
注意到上述树权值的定义等价于∏每个点的权值度数。既然出现了度数,考虑与度数关系密切的prufer序列。我们知道prufer序列中每个点的出现次数=其度数-1,所以对于某一种prufer序列,其对应的树的权值是所有点权值之积*prufer序列每个点权值之积。由于我们要求所有树的权值之和,而所有树对应着所有的prufer序列,由乘法分配律可得,这个东西就是所有点权值之积*n点数-2,其中n是原树点的个数。这样就可以知道钦定了边的方案数了。
然后考虑对所有钦定k条边的情况求和,这样容斥一发就能求出恰有k条边的方案数。显然我们只需要求出所有方案的所有点权值之积的和,可以做一个树上二维背包,即f[i][j][k]为i子树钦定了j条边,根所在连通块大小为k时,子树内所有内连通块大小之积的和。由于对子树大小取min,复杂度是O(n4)。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> #include<cassert> using namespace std; #define ll long long #define N 110 #define P 1000000007 char getc(){char c=getchar();while ((c<'A'||c>'Z')&&(c<'a'||c>'z')&&(c<'0'||c>'9')) c=getchar();return c;} int gcd(int n,int m){return m==0?n:gcd(m,n%m);} int read() { int x=0,f=1;char c=getchar(); while (c<'0'||c>'9') {if (c=='-') f=-1;c=getchar();} while (c>='0'&&c<='9') x=(x<<1)+(x<<3)+(c^48),c=getchar(); return x*f; } int n,k,p[N],f[N][N][N],g[N][N],h[N][N],C[N][N],size[N],inv[N],t,root=1; struct data{int to,nxt; }edge[N<<1]; void addedge(int x,int y){t++;edge[t].to=y,edge[t].nxt=p[x],p[x]=t;} int ksm(int a,int k) { int s=1; for (;k;k>>=1,a=1ll*a*a%P) if (k&1) s=1ll*s*a%P; return s; } void inc(int &x,int y){x+=y;if (x>=P) x-=P;} void dfs(int k,int from) { size[k]=1;f[k][0][1]=1; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) { int x=edge[i].to; dfs(x,k); for (int u=0;u<size[k]+size[x];u++) for (int s=0;s<=u+1;s++) g[u][s]=f[k][u][s],f[k][u][s]=0; for (int u=0;u<size[k]+size[x];u++) for (int s=1;s<=u+1;s++) for (int v=max(0,u-size[k]);v<=min(u,size[x]);v++) { inc(f[k][u][s],1ll*g[u-v][s]*h[x][v]%P); if (u>v) for (int t=max(1,s-(u+1));t<=min(s,v+1);t++) inc(f[k][u][s],1ll*g[u-v-1][s-t]*inv[s-t]%P*f[x][v][t]%P*inv[t]%P*s%P); } size[k]+=size[x]; } for (int i=0;i<size[k];i++) for (int j=1;j<=i+1;j++) inc(h[k][i],f[k][i][j]); } int calc(int k) { int ans=0; for (int i=k;i<n;i++) { int x=h[root][i]; if (i==n-1) x=1;else x=1ll*x*ksm(n,n-2-i)%P; x=1ll*x*C[i][k]%P; if (i-k&1) ans=(ans-x+P)%P;else ans=(ans+x)%P; } return ans; } int main() { n=read(); for (int i=1;i<n;i++) { int x=read(),y=read(); addedge(x,y),addedge(y,x); } C[0][0]=1; for (int i=1;i<=n;i++) { C[i][0]=C[i][i]=1; for (int j=1;j<i;j++) C[i][j]=(C[i-1][j-1]+C[i-1][j])%P; } for (int i=1;i<=n;i++) inv[i]=ksm(i,P-2); dfs(root,root); for (int i=0;i<n;i++) printf("%d ",calc(i)); return 0; }