P3914 染色计数
题目描述
有一颗NN个节点的树,节点用1,2,\cdots,N1,2,?,N编号。你要给它染色,使得相邻节点的颜色不同。有MM种颜色,用1,2,\cdots,M1,2,?,M编号。每个节点可以染MM种颜色中的若干种,求不同染色方案的数量除以(10^9 + 7109+7)的余数。
输入输出格式
输入格式:
第1 行,2 个整数N,MN,M。
接下来NN行,第ii行表示节点ii可以染的颜色。第1个整数k_iki?,表示可以染的颜色数量。接下来k_iki?个整数,表示可以染的颜色编号。
最后N - 1N−1行,每行2个整数A_i,B_iAi?,Bi?,表示边(A_i,B_i)(Ai?,Bi?)。
输出格式:
1 个整数,表示所有的数。
输入输出样例
输入样例#1: 复制
2 2 1 1 2 1 2 1 2
输出样例#1: 复制
1
说明
• 对于30% 的数据,1 \le N \le 10; 1 \le M \le 41≤N≤10;1≤M≤4;
• 对于60% 的数据,1 \le N \le 200; 1 \le M \le 2001≤N≤200;1≤M≤200;
• 对于100% 的数据,1 \le N \le 5000; 1 \le M \le 50001≤N≤5000;1≤M≤5000。
dfs+组合数学 爆零、、(可能是我写的问题)
#include<cstdio> #include<cstring> #include<iostream> #include<algorithm> #define N 5100 #define LL long long #define mod 1000000007 using namespace std; long long ans; bool vis[N],vist[N],boo[N][N]; int n,m,x,y,tot,k[N],head[N],a[N][N],same[N][N]; int read() { int x=0,f=1; char ch=getchar(); while(ch<‘0‘||ch>‘9‘){if(ch==‘-‘)f=-1;ch=getchar();} while(ch>=‘0‘&&ch<=‘9‘) x=x*10+ch-‘0‘,ch=getchar(); return x*f; } struct Edge { int to,next; }edge[N]; int add(int x,int y) { tot++; edge[tot].to=y; edge[tot].next=head[x]; head[x]=tot; } int dfs(int x) { for(int i=head[x];i;i=edge[i].next) { int t=edge[i].to; if(!vis[t]) { vis[t]=true; if(!vist[t]) { vist[t]=true; ans=(ans%mod+1ll*(k[x]-same[x][t])*k[t]%mod+(1ll*same[x][t]*(k[t]-1)%mod))%mod; } dfs(t); vis[t]=false; } } } int main() { n=read(),m=read(); for(int i=1;i<=n;i++) { k[i]=read(); for(int j=1;j<=k[i];j++) a[i][j]=read(),boo[i][a[i][j]]=true; } for(int i=1;i<n;i++) { x=read(),y=read(); add(x,y),add(y,x); for(int j=1;j<=k[y];j++) if(boo[x][a[y][j]]) same[x][y]++,same[y][x]++; } vist[1]=true;dfs(1); printf("%lld",ans); return 0; }
0分代码
不会做了,粘个题解
令f[i][j]f[i][j]表示以ii为根的这棵子树在ii为颜色jj的时候的方案数,根据乘法原理可得f[i][j]=πf[k][c]f[i][j]=πf[k][c] 其中kk是ii的所有儿子,cc是所有与jj不同的颜色。
因此我们可以很容易相处一个O(n^3)O(n3)的是算法:枚举所有点,枚举它的颜色,枚举每一个儿子,枚举儿子的颜色,因为一个点只有一个父亲,所以枚举儿子的那一层加上枚举每一个点一共是O(n)O(n)的。
回过头来再看数据n<=5000n<=5000,m<=5000m<=5000,上述的算法似乎运行起来很吃力。那我们需要向一个办法去将复杂度变成O(n^2)O(n2),仔细思考后我们会发现,其实枚举儿子那一层是没有必要的,因为我只要颜色不相同的总数,与其O(m)O(m)地去求和,不如之前在枚举到它的时候就处理好所有的和,然后O(1)O(1)地去剪掉这个颜色,从而求出除去这个颜色外的方案数,达到O(n^2)O(n2)的复杂度。
这道题还是很不错的,这种思想在很多时候都能用得上。
# include <algorithm> # include <iostream> # include <cstring> # include <cstdio> # include <vector> # include <cmath> # define R register # define mod 1000000007 using namespace std; int e,n,m,f[5010][5010],h[5010],tot[5010],sum[5010],x,y,d; struct pp{int v,pre;}ed[10010]; inline void in(R int &a){ R char c = getchar();R int x=0, f=1; while(!isdigit(c)) {if(c == ‘-‘) f=-1; c = getchar();} while(isdigit(c)) x = (x<<1)+(x<<3)+c-‘0‘,c = getchar(); a=x*f; } inline void add(R int x,R int y){ ed[++e] = (pp){y,h[x]}; h[x] = e; } inline void dfs(R int fa,R int x){//树形DP一般用DFS来实现 for(R int i=h[x]; i; i=ed[i].pre) { R int p = ed[i].v; if(p == fa) continue; dfs(x,p); } for(R int j=1; j<=m; ++j) { if(!f[x][j]) continue;//没有这种颜色 for(R int i=h[x]; i; i=ed[i].pre) { R int p = ed[i].v; if(p == fa) continue; f[x][j] = 1LL*f[x][j]*(tot[p]-f[p][j])%mod; } while(f[x][j]<0) f[x][j] += mod;//上边(tot[p]-f[p][j])可能会变成负数,这里把它变回来。 tot[x] = (1LL*tot[x]+1LL*f[x][j])%mod; } } inline int yg(){ in(n),in(m); for(R int i=1; i<=n; ++i) { in(sum[i]); for(R int j=1; j<=sum[i]; ++j) in(d),f[i][d]++; } for(R int i=1; i<n; ++i) { in(x),in(y); add(x,y),add(y,x); } add(0,1);//为了好写,新建一个原点连向1,这样就不用额外求tot[1]了 dfs(0,0); cout << tot[1];//tot[1]就是最终的答案 } int youngsc = yg(); int main(){;}