【bzoj4013】[HNOI2015]实验比较 树形dp+组合数学
2016-03-02 09:57
323 查看
湖南人太厉害了!!!根本毫无思路呀!!!
首先,题目中说了“对每张图片 i,小 D 都最多只记住了某一张质量不比 i 差的另一张图片 Ki。”
所以,这是棵树,或者森林。如果有环,则无解,输出0。
把相等的点用并查集合并在一起,看做一个点
考虑dp,f[i][j]表示以i为根的子树合并成j段的方案数(相同的点合并起来)
考虑将两棵独立的子树u和v合并起来有多少种方案
g[i]+=f[u][j]*f[v][k]*C() (枚举j和k,max(j,k)<=i<=j+k)
考虑这两个合并起来有多少种方案,
问题等价于i个箱子,j个白球,k个黑球,把每个球都放进一个箱子里,每个箱子每种颜色的球最多一个,每个箱子都不能空的方案数。
先将j个白球放进i个箱子中,剩下i-j个箱子,先拿黑球填满,剩下k+j-i个黑球放在j个放了白球的箱子中。总方案数为C(j,i)*C(k+j-i,j)
∴g[i]+=f[u][j]*f[v][k]*C(j,i)*C(k+j-i,j) (max(j,k)<=i<=j+k)
考虑一下实现的细节,
对于每个节点,每次合并的时候,用一个新的数组记录,像背包一样
枚举j和k,再枚举能转移到的i
每一个节点,如果它是叶子节点,那么f[u][1]=1;
否则,因为根节点不可能和任何一个节点相等,所以f[u][i]=f[u][i-1]
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<iostream>
#define maxn 410
#define mod 1000000007
using namespace std;
struct yts
{
int x,y;
}a[maxn];
int head[maxn],to[maxn],next[maxn];
long long f[maxn][maxn],g[maxn];
int fa[maxn];
bool vis[maxn];
long long c[maxn][maxn];
int d[maxn],size[maxn];
int n,m,num,t,M;
char s[5];
void addedge(int x,int y)
{
num++;to[num]=y;next[num]=head[x];head[x]=num;d[y]++;
}
int find(int x)
{
if (fa[x]==x) return x;
else return fa[x]=find(fa[x]);
}
bool dfs(int x,int fa)
{
vis[x]=1;
bool flag=0;
for (int p=head[x];p;p=next[p])
if (to[p]!=fa)
{
if (vis[to[p]]) return 0;
if (!dfs(to[p],x)) return 0;
if (flag)
{
memset(g,0,sizeof(g));
for (int j=1;j<=size[x];j++)
for (int k=1;k<=size[to[p]];k++)
if (f[x][j] && f[to[p]][k])
for (int i=max(j,k);i<=j+k;i++)
g[i]=(g[i]+(long long)((f[x][j]*f[to[p]][k]%mod)*c[i][j]%mod)*c[j][k+j-i]%mod)%mod;
size[x]+=size[to[p]];
for (int i=1;i<=size[x];i++) f[x][i]=g[i];
}
else
{
size[x]=size[to[p]];flag=1;
for (int i=1;i<=size[x];i++) f[x][i]=f[to[p]][i];
}
}
if (x)
{
size[x]++;
if (flag) for (int i=size[x];i>=1;i--) f[x][i]=f[x][i-1];
else f[x][1]=1;
}
return 1;
}
int main()
{
scanf("%d%d",&n,&M);
c[0][0]=1;
for (int i=1;i<=100;i++)
{
c[i][0]=1;
for (int j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1;i<=M;i++)
{
int x,y;
scanf("%d%s%d",&x,s,&y);
if (s[0]=='=')
{
int f1=find(x),f2=find(y);
fa[f1]=f2;
}
else
{
a[++m].x=x;a[m].y=y;
}
}
for (int i=1;i<=m;i++)
{
int f1=find(a[i].x),f2=find(a[i].y);
addedge(f1,f2);
if (f1==f2) {printf("0\n");return 0;}
}
for (int i=1;i<=n;i++) if (!d[find(i)]) addedge(0,find(i));
if (!dfs(0,-1)) {printf("0\n");return 0;}
for (int i=1;i<=n;i++) if (fa[i]==i && !vis[i]) {printf("0\n");return 0;}
long long ans=0;
for (int i=1;i<=size[0];i++) ans=(ans+f[0][i])%mod;
printf("%lld\n",ans);
return 0;
}
首先,题目中说了“对每张图片 i,小 D 都最多只记住了某一张质量不比 i 差的另一张图片 Ki。”
所以,这是棵树,或者森林。如果有环,则无解,输出0。
把相等的点用并查集合并在一起,看做一个点
考虑dp,f[i][j]表示以i为根的子树合并成j段的方案数(相同的点合并起来)
考虑将两棵独立的子树u和v合并起来有多少种方案
g[i]+=f[u][j]*f[v][k]*C() (枚举j和k,max(j,k)<=i<=j+k)
考虑这两个合并起来有多少种方案,
问题等价于i个箱子,j个白球,k个黑球,把每个球都放进一个箱子里,每个箱子每种颜色的球最多一个,每个箱子都不能空的方案数。
先将j个白球放进i个箱子中,剩下i-j个箱子,先拿黑球填满,剩下k+j-i个黑球放在j个放了白球的箱子中。总方案数为C(j,i)*C(k+j-i,j)
∴g[i]+=f[u][j]*f[v][k]*C(j,i)*C(k+j-i,j) (max(j,k)<=i<=j+k)
考虑一下实现的细节,
对于每个节点,每次合并的时候,用一个新的数组记录,像背包一样
枚举j和k,再枚举能转移到的i
每一个节点,如果它是叶子节点,那么f[u][1]=1;
否则,因为根节点不可能和任何一个节点相等,所以f[u][i]=f[u][i-1]
#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<algorithm>
#include<iostream>
#define maxn 410
#define mod 1000000007
using namespace std;
struct yts
{
int x,y;
}a[maxn];
int head[maxn],to[maxn],next[maxn];
long long f[maxn][maxn],g[maxn];
int fa[maxn];
bool vis[maxn];
long long c[maxn][maxn];
int d[maxn],size[maxn];
int n,m,num,t,M;
char s[5];
void addedge(int x,int y)
{
num++;to[num]=y;next[num]=head[x];head[x]=num;d[y]++;
}
int find(int x)
{
if (fa[x]==x) return x;
else return fa[x]=find(fa[x]);
}
bool dfs(int x,int fa)
{
vis[x]=1;
bool flag=0;
for (int p=head[x];p;p=next[p])
if (to[p]!=fa)
{
if (vis[to[p]]) return 0;
if (!dfs(to[p],x)) return 0;
if (flag)
{
memset(g,0,sizeof(g));
for (int j=1;j<=size[x];j++)
for (int k=1;k<=size[to[p]];k++)
if (f[x][j] && f[to[p]][k])
for (int i=max(j,k);i<=j+k;i++)
g[i]=(g[i]+(long long)((f[x][j]*f[to[p]][k]%mod)*c[i][j]%mod)*c[j][k+j-i]%mod)%mod;
size[x]+=size[to[p]];
for (int i=1;i<=size[x];i++) f[x][i]=g[i];
}
else
{
size[x]=size[to[p]];flag=1;
for (int i=1;i<=size[x];i++) f[x][i]=f[to[p]][i];
}
}
if (x)
{
size[x]++;
if (flag) for (int i=size[x];i>=1;i--) f[x][i]=f[x][i-1];
else f[x][1]=1;
}
return 1;
}
int main()
{
scanf("%d%d",&n,&M);
c[0][0]=1;
for (int i=1;i<=100;i++)
{
c[i][0]=1;
for (int j=1;j<=i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
for (int i=1;i<=n;i++) fa[i]=i;
for (int i=1;i<=M;i++)
{
int x,y;
scanf("%d%s%d",&x,s,&y);
if (s[0]=='=')
{
int f1=find(x),f2=find(y);
fa[f1]=f2;
}
else
{
a[++m].x=x;a[m].y=y;
}
}
for (int i=1;i<=m;i++)
{
int f1=find(a[i].x),f2=find(a[i].y);
addedge(f1,f2);
if (f1==f2) {printf("0\n");return 0;}
}
for (int i=1;i<=n;i++) if (!d[find(i)]) addedge(0,find(i));
if (!dfs(0,-1)) {printf("0\n");return 0;}
for (int i=1;i<=n;i++) if (fa[i]==i && !vis[i]) {printf("0\n");return 0;}
long long ans=0;
for (int i=1;i<=size[0];i++) ans=(ans+f[0][i])%mod;
printf("%lld\n",ans);
return 0;
}
相关文章推荐
- Android 多线程
- 在Mac环境下安装AndroidStudio
- 【论】提示消息的幽默性
- 为被选元素设置一个以上的属性和值。
- SPRING IN ACTION 第4版笔记-第二章WIRING BEANS-006-当构造函数有集合时的注入
- iOS 微信支付
- javascript 中的货币格式化
- redis常用配置
- 每次Xcode升级后,所有的插件都使用不鸟的解决方案Xcode7.2 Xcode7.1
- 合同比对
- 在xcode5中修改整个项目名
- 两步建立 ssh 反向隧道
- 6-2-二叉树(二叉链表存储)-树和二叉树-第6章-《数据结构》课本源码-严蔚敏吴伟民版
- SQL Server导入数据报错"无法在只读列“Id”中插入数据"
- SAX,DOM,Pull的比较
- 2016年3月2日09:53:31 记
- 函数备忘录
- 《从零开始学Swift》学习笔记(Day 27)——可选类型
- 《从零开始学Swift》学习笔记(Day 27)——可选类型
- Java笔记1:创建新的project