CodeCraft-19 and Codeforces Round #537 Div. 2
D:即有不超过52种物品,求容量为n/2的有序01背包方案数。容易想到设f[i][j]为前i种物品已用容量为j的方案数,有f[i][j]=f[i-1][j-a[i]]*C(n/2-j+a[i],a[i])+f[i-1][j]*C(n/2-s[i-1]+j,a[i])。显然本质不同询问只有O(k2)种,暴力就是O(n·k3)的。
考虑优化,询问相当于是把两个物品从背包中拿出,合并两物品后再放入背包。只要线性完成拿出物品的操作就可以优化到O(n·k2)。然而上面的式子并不能完成还原,因为后一部分的系数可能为0。但注意到进行背包的无序分配后,物品排列的方案数是固定的。所以之前的dp式子改为普通的01背包计数就可以还原了,最后再乘上排列方案数。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; #define ll long long #define N 100010 #define P 1000000007 #define M 55 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,q,f[N],g[N],a[M],sum[M],fac[N],inv[N],ans[M][M],tot; char s[N]; int trans(char a){if (a<='Z') return a-'A'+1;else return a-'a'+27;} int C(int n,int m){return 1ll*fac[n]*inv[m]%P*inv[n-m]%P;} int INV_C(int n,int m){return 1ll*inv[n]*fac[m]%P*fac[n-m]%P;} void inc(int &x,int y){x+=y;if (x>=P) x-=P;} void ins(int u,int sum,int *f) { if (!u) return; for (int j=min(sum,n);j>=0;j--) if (j>=u) inc(f[j],f[j-u]); } void del(int u,int sum) { if (!u) return; for (int j=0;j<=min(sum,n);j++) if (j>=u) inc(g[j],P-g[j-u]); } int main() { #ifndef ONLINE_JUDGE freopen("d.in","r",stdin); freopen("d.out","w",stdout); const char LL[]="%I64d\n"; #endif scanf("%s",s+1); n=strlen(s+1);for (int i=1;i<=n;i++) a[trans(s[i])]++; fac[0]=1;for (int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%P; inv[0]=inv[1]=1;for (int i=2;i<=n;i++) inv[i]=P-1ll*(P/i)*inv[P%i]%P; for (int i=2;i<=n;i++) inv[i]=1ll*inv[i-1]*inv[i]%P; m=52;n>>=1; tot=1ll*fac[n]*fac[n]%P; for (int i=1;i<=m;i++) tot=1ll*tot*inv[a[i]]%P; for (int k=1;k<=m;k++) { memset(f,0,sizeof(f));f[0]=1; for (int i=1;i<=m;i++) sum[i]=sum[i-1]+(i!=k)*a[i]; for (int i=1;i<=m;i++) if (i!=k) ins(a[i],sum[i],f); for (int j=k+1;j<=m;j++) { for (int i=0;i<=n;i++) g[i]=f[i]; del(a[j],sum[m]); ins(a[j]+a[k],n*2,g); ans[k][j]=1ll*g[n]*C(a[j]+a[k],a[j])%P; ans[k][j]=1ll*fac[a[j]]*fac[a[k]]%P*inv[a[j]+a[k]]%P*ans[k][j]%P; } ins(a[k],n*2,f); ans[k][k]=f[n]; } q=read(); for (int i=1;i<=q;i++) { int x=trans(s[read()]),y=trans(s[read()]); if (x>y) swap(x,y); printf("%d\n",1ll*ans[x][y]*tot%P); } return 0; }
E:容易想到建出虚树,换根只要换个点开始dfs就行了。问题在于dp,传统树形dp的思路似乎很难做到O(nm),因为无法避开合并两个O(m)的状态(当然应该可以只是我不会)。考虑按dfs序dp,这样每一个点只要和他的祖先在不同集合即可,祖先两两之间显然也在不同集合,所以能放的集合个数是确定的,就非常显然了。
#include<iostream> #include<cstdio> #include<cmath> #include<cstdlib> #include<cstring> #include<algorithm> using namespace std; #define N 100010 #define P 1000000007 #define M 310 #define ll long long 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,q,p[N],a[N],fac[N],inv[N],dfn[N],size[N],fa[N][20],cnt,deep[N],t; 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;} void inc(int &x,int y){x+=y;if (x>=P) x-=P;} 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; } int Inv(int a){return ksm(a,P-2);} int C(int n,int m){return 1ll*fac[n]*inv[m]%P*inv[n-m]%P;} int lca(int x,int y) { if (deep[x]<deep[y]) swap(x,y); for (int j=19;~j;j--) if (deep[fa[x][j]]>=deep[y]) x=fa[x][j]; if (x==y) return x; for (int j=19;~j;j--) if (fa[x][j]!=fa[y][j]) x=fa[x][j],y=fa[y][j]; return fa[x][0]; } int calc(int j,int x,int y){if (x+y<j) return 0;return 1ll*C(x,x+y-j)*C(y,x+y-j)%P*fac[x+y-j]%P;} namespace virtual_tree { int n,p[N],t,T,stk[N],top,f[2][M],g[M],x[N],y[N],DFN[N],DEEP[N],cnt; bool flag[N]; struct data{int to,nxt;}edge[N<<1]; void addedge(int u,int v){t++;x[t]=u,y[t]=v;} void Addedge(int x,int y){T++;edge[T].to=y,edge[T].nxt=p[x],p[x]=T;} bool cmp(const int&a,const int &b) { return dfn[a]<dfn[b]; } bool cmp2(const int&a,const int &b) { return DFN[a]<DFN[b]; } void build(int root,int m) { bool f=0;cnt=0;n=m; for (int i=1;i<=n;i++) if (a[i]==root) {f=1;break;} if (!f) a[++n]=root; sort(a+1,a+n+1,cmp); stk[top=1]=1;t=0; for (int i=1+(a[1]==1);i<=n;i++) { int l=lca(a[i],stk[top]); if (stk[top]!=l) { while (top>1&&deep[stk[top-1]]>=deep[l]) addedge(stk[top-1],stk[top]),top--; if (stk[top]!=l) addedge(l,stk[top]); stk[top]=l; } stk[++top]=a[i]; } while (top>1) addedge(stk[top-1],stk[top]),top--; for (int i=1;i<=t;i++) p[x[i]]=p[y[i]]=0;T=0; for (int i=1;i<=t;i++) Addedge(x[i],y[i]),Addedge(y[i],x[i]); for (int i=1;i<=t;i++) flag[x[i]]=flag[y[i]]=0; for (int i=1;i<=n;i++) if (a[i]!=root||f) flag[a[i]]=1; DEEP[root]=0; } void dfs(int k,int from) { DFN[k]=++cnt; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) { DEEP[edge[i].to]=DEEP[k]+flag[k]; dfs(edge[i].to,k); } } void work(int root) { sort(a+1,a+n+1,cmp2); f[!flag[root]][0]=1;for (int j=1;j<=m;j++) f[!flag[root]][j]=0; for (int i=1+(!flag[root]);i<=n;i++) { f[i&1][0]=0; for (int j=1;j<=m;j++) { f[i&1][j]=f[i&1^1][j-1]; if (j>=DEEP[a[i]]) inc(f[i&1][j],1ll*f[i&1^1][j]*(j-DEEP[a[i]])%P); } } } } void dfs(int k,int from) { dfn[k]=++cnt;size[k]=1; for (int i=p[k];i;i=edge[i].nxt) if (edge[i].to!=from) { fa[edge[i].to][0]=k; deep[edge[i].to]=deep[k]+1; dfs(edge[i].to,k); size[k]+=size[edge[i].to]; } } signed main() { #ifndef ONLINE_JUDGE freopen("aaa.in","r",stdin); freopen("aaa.out","w",stdout); const char LL[]="%I64d\n"; #endif n=read(),q=read(); for (int i=1;i<n;i++) { int x=read(),y=read(); addedge(x,y),addedge(y,x); } fac[0]=1;for (int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%P; inv[0]=inv[1]=1;for (int i=2;i<=n;i++) inv[i]=P-1ll*(P/i)*inv[P%i]%P; for (int i=2;i<=n;i++) inv[i]=1ll*inv[i-1]*inv[i]%P; fa[1][0]=1;dfs(1,1); for (int j=1;j<20;j++) for (int i=1;i<=n;i++) fa[i][j]=fa[fa[i][j-1]][j-1]; for (int i=1;i<=q;i++) { int k=read();m=read();int root=read(); for (int j=1;j<=k;j++) a[j]=read(); virtual_tree::build(root,k); virtual_tree::dfs(root,root); virtual_tree::work(root); int ans=0; for (int j=0;j<=m;j++) inc(ans,virtual_tree::f[virtual_tree::n&1][j]); printf("%d\n",ans); } return 0; //NOTICE LONG LONG!!!!! }