【BZOJ3167/4824】[Heoi2013]Sao/[Cqoi2017]老C的键盘

【BZOJ3167】[Heoi2013]Sao

Description

WelcometoSAO(StrangeandAbnormalOnline)。这是一个VRMMORPG,含有n个关卡。但是,挑战不同关卡的顺序是一个很大的问题。有n–1个对于挑战关卡的限制,诸如第i个关卡必须在第j个关卡前挑战,或者完成了第k个关卡才能挑战第l个关卡。并且,如果不考虑限制的方向性,那么在这n–1个限制的情况下,任何两个关卡都存在某种程度的关联性。即,我们不能把所有关卡分成两个非空且不相交的子集,使得这两个子集之间没有任何限制。

Input

第一行,一个整数T,表示数据组数。对于每组数据,第一行一个整数n,表示关卡数。接下来n–1行,每行为“i sign j”,其中0≤i,j≤n–1且i≠j,sign为“<”或者“>”,表示第i个关卡必须在第j个关卡前/后完成。

T≤5,1≤n≤1000

Output

对于每个数据,输出一行一个整数,为攻克关卡的顺序方案个数,mod1,000,000,007输出。

Sample Input

1
10
5 > 8
5 > 6
0 < 1
9 < 4
2 > 5
5 < 9
8 < 1
9 > 3
1 < 7

Sample Output

2580

题解:憋了一下午想出来的树形DP题~

如何设状态呢?显然应该是个二维的状态。设f[i][j]表示在i的子树中,i位于第j个位置的方案数。那么我们如何将x的当前状态与他的儿子的状态合并呢?

先只考虑x<y的情况,我们设原来的siz[x]=sa,siz[y]=sb,sa+sb=sc,我们想用f[x][a]和f[y][b]来更新f[x][c](c>=a+b),为了区分新的f和旧的f,我们用g[c]表示新的f。

如果x位于位置c,那么它左边有c-1个位置,相当于将y的序列中左边的数与x的序列中左边的数进行了二路归并,并且归并的顺序可以随便确定,那么方案数就是$C_{c-1}^{a-1}$,同理,右面的sc-c个数也可以归并处理,方案数是$C_{sc-c}^{sa-a}$,所以得到方程:

$g[c]=\sum\limits_{a=1}^{c}\sum\limits_{b=1}^{c-a}C_{c-1}^{a-1}C_{sc-c}^{sa-a}f[x][a]*f[y][b]$

可以将f[y][b]提出来

$g[c]=\sum\limits_{b=1}^{c}f[y][b]\sum\limits_{a=1}^{c-b}C_{c-1}^{a-1}C_{sc-c}^{sa-a}f[x][a]$

这个东西就可以用前缀和维护了~

不过值得惭愧的是,我的代码的上界设的不紧,或是循环顺序不太对,复杂度其实应该是O(n^3)的,似乎可以改一改使得复杂度变成树形背包的优雅的O(n^2)。不过还是卡过了,就没有改,求不卡~不过处理4824那题还是很轻松的,因为是完全二叉树嘛~

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long ll;
const ll P=1000000007;
int n,cnt;
ll ans;
ll C[1010][1010];
ll f[1010][1010],g[1010],s[1010];
int to[2010],next[2010],head[1010],val[2010],fa[1010],siz[1010];
char str[5];
void dfs(int x)
{
	int i,y,a,b,c;
	siz[x]=1,f[x][1]=1;
	for(i=head[x];i!=-1;i=next[i])	if(to[i]!=fa[x])
	{
		y=to[i],fa[y]=x,dfs(y);
		memset(g,0,sizeof(g[0])*(siz[x]+siz[y]+1));
		if(val[i]==1)
		{
			for(c=2;c<=siz[x]+siz[y];c++)
			{
				s[max(1,c-siz[y])-1]=0;
				for(b=1;b<=min(siz[x],c-1);b++)
					s[b]=(s[b-1]+C[c-1][b-1]*C[siz[x]+siz[y]-c][siz[x]-b]%P*f[x][b])%P;
				for(a=1;a<=min(siz[y],c-1);a++)
				{
					if(c-a>min(siz[x],c-1))	s[c-a]=s[min(siz[x],c-1)];
					if(c-a<max(1,c-siz[y]))	s[c-a]=0;
					g[c]=(g[c]+s[c-a]*f[y][a])%P;
				}
			}
			f[x][1]=0;
			for(c=2;c<=siz[x]+siz[y];c++)	f[x][c]=g[c];
		}
		else
		{
			for(c=2;c<=siz[x]+siz[y];c++)
			{
				s[max(1,c-siz[y])-1]=0;
				for(b=1;b<=min(siz[x],c-1);b++)
					s[b]=(s[b-1]+C[c-1][b-1]*C[siz[x]+siz[y]-c][siz[x]-b]%P*f[x][siz[x]-b+1])%P;
				for(a=1;a<=min(siz[y],c-1);a++)
				{
					if(c-a>min(siz[x],c-1))	s[c-a]=s[min(siz[x],c-1)];
					if(c-a<max(1,c-siz[y]))	s[c-a]=0;
					g[c]=(g[c]+s[c-a]*f[y][siz[y]-a+1])%P;
				}
			}
			f[x][siz[x]+siz[y]]=0;
			for(c=1;c<siz[x]+siz[y];c++)	f[x][c]=g[siz[x]+siz[y]-c+1];
		}
		siz[x]=siz[x]+siz[y];
	}
}
inline void add(int a,int b,int c)
{
	to[cnt]=b,val[cnt]=c,next[cnt]=head[a],head[a]=cnt++;
}
void work()
{
	scanf("%d",&n);
	int i,a,b;
	memset(head,-1,sizeof(head)),cnt=0;
	memset(f,0,sizeof(f)),memset(fa,0,sizeof(fa));
	for(i=1;i<n;i++)
	{
		scanf("%d%s%d",&a,str,&b),a++,b++;
		if(str[0]==‘>‘)	add(a,b,1),add(b,a,0);
		if(str[0]==‘<‘)	add(a,b,0),add(b,a,1);
	}
	dfs(1);
	ans=0;
	for(i=1;i<=n;i++)	ans=(ans+f[1][i])%P;
	printf("%lld\n",ans);
}
int main()
{
	int T,i,j;
	C[0][0]=1;
	for(i=1;i<=1000;i++)
	{
		C[i][0]=1;
		for(j=1;j<=i;j++)	C[i][j]=(C[i-1][j-1]+C[i-1][j])%P;
	}
	scanf("%d",&T);
	while(T--)	work();
	return 0;
}//5 10 5 > 8 5 > 6 0 < 1 9 < 4 2 > 5 5 < 9 8 < 1 9 > 3 1 < 7 10 6 > 7 2 > 0 9 < 0 5 > 9 7 > 0 0 > 3 7 < 8 1 < 2 0 < 4 10 2 < 0 1 > 4 0 > 5 9 < 0 9 > 3 1 < 2 4 > 6 9 < 8 7 > 1 10 0 > 9 5 > 6 3 > 6 8 < 7 8 > 4 0 > 6 8 > 5 8 < 2 1 > 8 10 8 < 3 8 < 4 1 > 3 1 < 9 3 < 7 2 < 8 5 > 2 5 < 6 0 < 9
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
typedef long long ll;
const ll P=1000000007;
int n,cnt;
ll ans;
ll C[1010][1010];
ll f[1010][1010],g[1010],s[1010];
int to[2010],next[2010],head[1010],val[2010],fa[1010],siz[1010];
char str[5];
void dfs(int x)
{
	int i,y,a,b,c;
	siz[x]=1,f[x][1]=1;
	for(i=head[x];i!=-1;i=next[i])	if(to[i]!=fa[x])
	{
		y=to[i],fa[y]=x,dfs(y);
		memset(g,0,sizeof(g[0])*(siz[x]+siz[y]+1));
		if(val[i]==1)
		{
			for(c=2;c<=siz[x]+siz[y];c++)
			{
				s[max(1,c-siz[y])-1]=0;
				for(b=1;b<=min(siz[x],c-1);b++)
					s[b]=(s[b-1]+C[c-1][b-1]*C[siz[x]+siz[y]-c][siz[x]-b]%P*f[x][b])%P;
				for(a=1;a<=min(siz[y],c-1);a++)
				{
					if(c-a>min(siz[x],c-1))	s[c-a]=s[min(siz[x],c-1)];
					if(c-a<max(1,c-siz[y]))	s[c-a]=0;
					g[c]=(g[c]+s[c-a]*f[y][a])%P;
				}
			}
			f[x][1]=0;
			for(c=2;c<=siz[x]+siz[y];c++)	f[x][c]=g[c];
		}
		else
		{
			for(c=2;c<=siz[x]+siz[y];c++)
			{
				s[max(1,c-siz[y])-1]=0;
				for(b=1;b<=min(siz[x],c-1);b++)
					s[b]=(s[b-1]+C[c-1][b-1]*C[siz[x]+siz[y]-c][siz[x]-b]%P*f[x][siz[x]-b+1])%P;
				for(a=1;a<=min(siz[y],c-1);a++)
				{
					if(c-a>min(siz[x],c-1))	s[c-a]=s[min(siz[x],c-1)];
					if(c-a<max(1,c-siz[y]))	s[c-a]=0;
					g[c]=(g[c]+s[c-a]*f[y][siz[y]-a+1])%P;
				}
			}
			f[x][siz[x]+siz[y]]=0;
			for(c=1;c<siz[x]+siz[y];c++)	f[x][c]=g[siz[x]+siz[y]-c+1];
		}
		siz[x]=siz[x]+siz[y];
	}
}
inline void add(int a,int b,int c)
{
	to[cnt]=b,val[cnt]=c,next[cnt]=head[a],head[a]=cnt++;
}
void work()
{
	scanf("%d",&n);
	int i,a,b;
	memset(head,-1,sizeof(head)),cnt=0;
	memset(f,0,sizeof(f)),memset(fa,0,sizeof(fa));
	for(i=1;i<n;i++)
	{
		scanf("%d%s%d",&a,str,&b),a++,b++;
		if(str[0]==‘>‘)	add(a,b,1),add(b,a,0);
		if(str[0]==‘<‘)	add(a,b,0),add(b,a,1);
	}
	dfs(1);
	ans=0;
	for(i=1;i<=n;i++)	ans=(ans+f[1][i])%P;
	printf("%lld\n",ans);
}
int main()
{
	int T,i,j;
	C[0][0]=1;
	for(i=1;i<=1000;i++)
	{
		C[i][0]=1;
		for(j=1;j<=i;j++)	C[i][j]=(C[i-1][j-1]+C[i-1][j])%P;
	}
	scanf("%d",&T);
	while(T--)	work();
	return 0;
}//5 10 5 > 8 5 > 6 0 < 1 9 < 4 2 > 5 5 < 9 8 < 1 9 > 3 1 < 7 10 6 > 7 2 > 0 9 < 0 5 > 9 7 > 0 0 > 3 7 < 8 1 < 2 0 < 4 10 2 < 0 1 > 4 0 > 5 9 < 0 9 > 3 1 < 2 4 > 6 9 < 8 7 > 1 10 0 > 9 5 > 6 3 > 6 8 < 7 8 > 4 0 > 6 8 > 5 8 < 2 1 > 8 10 8 < 3 8 < 4 1 > 3 1 < 9 3 < 7 2 < 8 5 > 2 5 < 6 0 < 9
时间: 2024-10-06 13:42:56

【BZOJ3167/4824】[Heoi2013]Sao/[Cqoi2017]老C的键盘的相关文章

[BZOJ4824][CQOI2017]老C的键盘(树形DP)

4824: [Cqoi2017]老C的键盘 Time Limit: 10 Sec  Memory Limit: 512 MBSubmit: 193  Solved: 149[Submit][Status][Discuss] Description 老 C 是个程序员. 作为一个优秀的程序员,老 C 拥有一个别具一格的键盘,据说这样可以大幅提升写程序的速度,还能让写出来的程序 在某种神奇力量的驱使之下跑得非常快.小 Q 也是一个程序员.有一天他悄悄潜入了老 C 的家中,想要看看这个 键盘究竟有何妙

[CQOI2017]老C的键盘

[CQOI2017]老C的键盘 题目描述 额,网上题解好像都是用的一大堆组合数,然而我懒得推公式. 设\(f[i][j]\)表示以\(i\)为根,且\(i\)的权值为\(j\)的方案数. 转移: \[ f[i][j]=\sum f[sn_1][k]*f[sn_2][q] \] 需要判断一下\(k,q\)与\(j\)的关系满不满足题意就行了. 但是这样的答案显然不对,因为有些权值可能多次出现. 换句话说,有些权值可能没有出现.所以我们就用那个经典的容斥,枚举颜色数上界. 设\(g[s]\)表示颜色

[bzoj4824][洛谷P3757][Cqoi2017]老C的键盘

Description 老 C 是个程序员. 作为一个优秀的程序员,老 C 拥有一个别具一格的键盘,据说这样可以大幅提升写程序的速度,还能让写出来的程序 在某种神奇力量的驱使之下跑得非常快.小 Q 也是一个程序员.有一天他悄悄潜入了老 C 的家中,想要看看这个 键盘究竟有何妙处.他发现,这个键盘共有n个按键,这n个按键虽然整齐的排成一列,但是每个键的高度却互不相同 .聪明的小 Q 马上将每个键的高度用 1 ~ n 的整数表示了出来,得到一个 1 ~ n 的排列 h1, h2,..., hn .为

Luogu P3757 [CQOI2017]老C的键盘

题目描述 老C的键盘 题解 显然对于每个数 x 都有唯一对应的 \(x/2\) , 然而对于每个数 x 却可以成为 \(x*2\) 和 \(x*2+1\) 的对应数 根据这一特性想到了啥??? 感谢leo101的友情点拨 二叉树!!! 所以可以把 x/2 看做是 x的父亲, 1 显然就是根 可以把 < 看作是由父亲连向儿子的有向边, > 看作是儿子连向父亲的有向边 所以就是求这棵树的拓扑序的方案数就好了!!! 考虑当前节点的两棵子树都已处理完的时候 在满足和 当前节点的关系的同时, 两颗子树在

BZOJ 4824: [Cqoi2017]老C的键盘

Description 上一题弱化版,\(n\leqslant 100\) Solution 树形DP. Code /************************************************************** Problem: 4824 User: BeiYu Language: C++ Result: Accepted Time:188 ms Memory:18580 kb *******************************************

bzoj4822: [Cqoi2017]老C的任务

4822: [Cqoi2017]老C的任务 Time Limit: 20 Sec  Memory Limit: 512 MBSubmit: 284  Solved: 152[Submit][Status][Discuss] Description 老 C 是个程序员. 最近老 C 从老板那里接到了一个任务--给城市中的手机基站写个管理系统.作为经验丰富的程序员,老 C 轻松 地完成了系统的大部分功能,并把其中一个功能交给你来实现.由于一个基站的面积相对于整个城市面积来说非常 的小,因此每个的基站

bzoj4823: [Cqoi2017]老C的方块(最小割)

4823: [Cqoi2017]老C的方块 题目:传送门 题解: 毒瘤题ORZ.... 太菜了看出来是最小割啥边都不会建...狂%大佬强强强   黑白染色?不!是四个色一起染,四层图跑最小割... 很惊奇的发现染完色之后只要是不喜欢的图形都一定可以由黄-->黑-->红-->绿 组成 那就很nice啦...兴高采烈的去敲代码...结果10^5*10^5???搞毛线...太弱了ORZ,又看了一波大佬的操作,用map存! woc...不谈了不谈了...撸撸撸(分情况分到想屎...虽然不多) 注

[HEOI2013]SAO

[HEOI2013]SAO 这道题是个不错的计数题,考察了调换求和顺序再前缀和优化,难点在状态设计,比较考察思维. 一句话题意:给你一棵数,树边为有向边,求其拓扑序数. 对DAG求拓扑数是一个NP问题,但是这里保证是一棵树,所以我们可以用树形DP来求解. 状态的设计上,光设结点编号\(u\)不够,还需要设计一维\(i\)表示结点\(u\)在以\(u\)为根的子树中的拓扑序的第\(i\)位,这样我们就可以写转移方程了. 对于\(u \rightarrow v\) \[ F'[u][k] = \Si

bzoj3167 [Heoi2013]Sao

传送门 这题神坑啊--明明是你菜 首先大家都知道原题等价于给每个点分配一个$1$~$n$且两两不同的权值,同时还需要满足一些大于/小于关系的方案数. 先看一眼数据范围,既然写明了$n\le 1000$,那就应该是什么$O(n^2)$的做法了.显然这个东西只能是个DP,考虑到题中给出的是一个树形结构,那么就可以利用子树的相对独立性进行DP:设$f_{i,j}$表示以$i$为根的子树中有$j$个点的权值大于$i$的权值时的方案数,显然最终答案就是$\sum_{i}f_{root,i}$. 然后考虑怎