codeforces 533-B Work Group-树DP-

博客主要讨论了如何解决Codeforces上的一道题目,涉及到寻找一个子树,其中每个成员的直接下属数量都是偶数,目标是找到权值最大的集合。博主分享了初始思路的错误,即仅考虑选择偶数个直接子节点,而忽略了可以选择子节点的子孙。通过引入奇偶关系的动态规划(DP)状态转移,博主给出了正确的解题方法,最终得出dp[x][1]为最优解。

http://codeforces.com/problemset/problem/533/B


题意:

每个人有一个直接的领导,1是总裁,现在要找一个最大的集合,每个领导领导的人的数量都是偶数,问最大的值是多少。 

给n,n个人

接下来n行 P,x,pi表示该人领导是pi,X表示该人权值为X


求一个子树(集合),里面所有人的下属的个数和都为偶数,求权值最大的一个集合,输出权值:

一开始方向就走错了。。以为是只需要选根节点U的 偶数个儿子节点(我的意思是偶数条直接分支)便可,没考虑到可以直接选U的孙子节点,这样一来就可以选奇数条直接分支了

(也就是,假设U有三个儿子,我开始以为只能选2个合法的儿子,最后发现可以选两个合法的儿子,并且从第三个儿子的子孙里再选偶数个合法子孙)

所以dp应该用奇偶关系来递推比较好

dp[u][0] 从u往下选偶数个合法后代节点的权值和 (必定不选自身,最后是以u的偶数个合法后代节点构成的森林)
dp[u][1] 从u往下选奇数个各法后代节点的权值和 (  最后可能是以u为根的一棵树,或奇数个u的合法后代节点构成的森林 )

每次递归初始化  dp[x][1]=-8223372036854775807; //第一次dp,实际dp[x][1]不存在,所以设为-inf 

int tmp=dp[u][0],tmp2=dp[u][1];
dp[u][0]=max(tmp+dp[v][0],tmp2+dp[v][1]);
dp[u][1]=max(tmp+dp[v][1],tmp2+dp[v][0]);

最后  dp[x][1]=max(dp[x][1],dp[x][0]+tm[x]); 

答案便是dp[x][1]啦。(比dp[x][0]大)


#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <iostream>
using namespace std;

const double pi=acos(-1.0);
double eps=0.000001; 
int max(int a,int b)
{return a<b?b:a;}
__int64 min(__int64 a,__int64 b)
{return a>b?b:a;}
int max(int a,int b,int c)
{return max(a,max(b,c));}

vector< vector<int> > mp(200005+5);
int tm[200005];
int p;
 __int64 dp[200005][2];
void  dfs(int x)
{ 
	int i;  
	dp[x][1]=-8223372036854775807; //第一次dp,实际dp[x][1]不存在,所以设为-inf 
	for (i=0;i<mp[x].size();i++)
	{
		int v=mp[x][i];
		dfs(v);
		__int64 tmp1=dp[x][0],tmp2=dp[x][1];
		dp[x][0]=max(tmp1+dp[v][0],tmp2+dp[v][1]);
		dp[x][1]=max(tmp2+dp[v][0],tmp1+dp[v][1]);
	}  
	dp[x][1]=max(dp[x][1],dp[x][0]+tm[x]); 
} 
int n;

int main()
{ 
	cin>>n;
	int i;
	int hd=-1;
	for (i=1;i<=n;i++)
	{
		scanf("%d%d",&p,&tm[i]);
		if (p==-1) {hd=i;continue;}
		mp[p].push_back(i);
	} 
	 
	 dfs(hd);
	printf("%I64d\n",dp[hd][1]);
	
	
	return 0;
	
}


第二次写的代码

#include <cstdio>
#include <cmath>
#include <cstring>
#include <string>
#include <algorithm>
#include <queue>
#include <map>
#include <set>
#include <vector>
#include <iostream>
using namespace std;

const double pi=acos(-1.0);
double eps=0.000001; 

vector<int >mp[200005];
__int64 max(__int64 a,__int64 b)
{return a>b?a:b;}
__int64 dp[200005][2];
__int64 aa[200005];
void dfs(int x)
{
	int i;
	dp[x][0]=0;
	dp[x][1]=-200000000005;
		for (i=0;i<mp[x].size();i++)
		{
			int v=mp[x][i];
			dfs(v);
			__int64 tmp1=dp[x][0],tmp2=dp[x][1];
			dp[x][0]=max(tmp1+dp[v][0],tmp2+dp[v][1]);
			dp[x][1]=max(tmp2+dp[v][0],tmp1+dp[v][1]);
		}

		dp[x][1]=max(dp[x][1],dp[x][0]+aa[x]);
}

int main()
{
	int n;
	cin>>n;
	int i,x,y;
	int rt=0;
	for (i=1;i<=n;i++)
	{
		scanf("%d%d",&x,&aa[i]);
		if (x==-1) rt=i;
		else
			mp[x].push_back(i);
	}
	dfs(rt);
	printf("%I64d\n",max(dp[rt][0],dp[rt][1]));
	
	
	
	
	
	
	
	
	
	
	
	
	return 0;
	
}


我们来分析并解决这个 C++14 题目。 --- ### ✅ **题目理解** 给定一个目标排列 $ p $,长度为 $ n $。 初始排列 $ s = \{0, 1, 2, ..., n-1\} $。 我们要通过 **恰好 $ n-1 $ 次相邻交换**(每次交换位置 $ q_i $ 和 $ q_i+1 $)把 $ s $ 变成 $ p $。 排列 $ q $ 是一个长度为 $ n-1 $ 的序列,每个元素在 $ [0, n-2] $ 范围内,并且是一个排列(即不重复,覆盖所有值)。 我们要统计有多少个这样的排列 $ q $,使得按顺序执行这些相邻交换后,$ s $ 变成 $ p $。 答案对 $ 10^9 + 7 $ 取模。 --- ### 🔍 **关键观察** 1. 每次交换是**相邻元素交换**。 2. 总共进行 $ n-1 $ 次交换,且 $ q $ 是 $ \{0,1,...,n-2\} $ 的一个排列 → 每个相邻位置恰好被交换一次(但顺序不同)。 3. 因此,这相当于:我们以某种顺序对每一对相邻位置 $ (i, i+1) $ 执行一次交换操作。 注意:**不是每个位置只能参与一次交换!** 实际上,某个位置可能参与多次交换,因为不同的 $ q_i $ 可能涉及同一个位置(比如先交换 (1,2),再交换 (0,1),那么位置1参与了两次)。 但是,**每条边(相邻对)只被“选中”一次作为交换操作的位置。** 所以整个过程是:按照 $ q $ 给出的顺序,依次交换相邻两个元素。 这类似于:你有一个排序过程,使用固定的 $ n-1 $ 次相邻交换,每条边用一次,但顺序任意。 --- ### 🧠 核心思想:逆向思维 + 动态规划 这个问题与**冒泡排序的路径计数**有关,也可以从**置换的逆序结构**出发考虑。 但我们注意到: > 每一条相邻交换边 $ (i, i+1) $ 恰好使用一次,顺序由 $ q $ 决定。 我们可以将问题转化为: - 初始数组 $ s = [0,1,...,n-1] $ - 我们要经过一系列相邻交换(总共 $ n-1 $ 步,每步选择一个尚未使用的相邻位置进行交换),最终变成 $ p $ 问:有多少种选择交换顺序的方式(即多少个排列 $ q $)可以实现这一变换? --- ### ⚙️ 更进一步:模拟逆过程 or 状态搜索? 由于 $ n \leq 50 $,不能暴力枚举所有 $ q $(阶乘爆炸)。 但 $ n \leq 50 $,而状态空间很大,也不能直接状压 DP。 然而,注意到总交换次数是 $ n-1 $,而且每条边只能用一次 —— 这提示我们可以考虑 **交换顺序的合法性**。 另一个思路来自 **置换分解和相邻交换生成群**,但这太抽象。 --- ### 💡 巧妙转化:从目标反推初始(逆过程) 考虑:我们知道最终结果是 $ p $,而我们是从恒等排列 $ id = [0,1,...,n-1] $ 出发,经过一系列相邻交换得到的。 但更重要的是:**每条相邻边只能被交换一次**。也就是说,我们是在选择一个交换顺序,每一步选一个未被选过的相邻位置进行交换。 这其实等价于:我们构造了一个长度为 $ n-1 $ 的操作序列,每步交换某个相邻对,且所有操作位置互不相同。 这类问题可以用 **DP + 插入法 / 位置移动跟踪** 来做。 --- ### ✅ 正解思路:基于位置移动的动态规划(区间 DP 或 位置偏移跟踪) 参考经典问题:“通过给定的相邻交换序列排序”,本题类似“统计能达成某排列的相邻交换顺序数量”。 #### 关键洞察: 考虑每个数字的**移动路径**。例如,数字 $ x $ 要从初始位置 $ x $ 移动到目标位置 $ pos[x] $。 每次相邻交换会影响两个元素的位置。 但更有效的方法是: > 将整个过程视为:我们逐步构建排列,每次允许交换一对相邻元素(该边还没交换过)。我们要从 $ [0,1,...,n-1] $ 变成 $ p $。 但是正向模拟复杂。 --- ### ✅ 推荐方法:**BFS / DP on Permutations 不可行(n=50太大)** 所以我们需要更聪明的办法。 --- ## 🌟 最佳思路:**转换为逆序对贡献 + 归并路径计数** 我们换一种方式思考: 假设我们想让初始排列 $ s = [0,1,...,n-1] $ 变成目标排列 $ p $。 这可以通过一系列相邻交换完成,最少需要 $ \text{inv}(p) $ 次交换(逆序对数),但这里我们必须恰好做 $ n-1 $ 次交换,且每条边最多用一次。 但注意:我们不是求最短路径,而是必须使用 **每个相邻位置交换一次**,共 $ n-1 $ 次,顺序是排列 $ q $。 这就意味着:我们的交换序列 $ q $ 是 $ 0 $ 到 $ n-2 $ 的一个排列,表示我们依次交换哪些相邻对。 因此,这是一个**固定操作集(每条边一次),不同顺序导致不同结果**的问题。 --- ### 📌 经典结论: > 设我们有一组相邻交换操作,每个边 $ (i,i+1) $ 恰好使用一次,顺序不定。那么所有可能的结果排列构成一个集合,其大小等于 $ 2^{n-1} }?不对。 实际上,这种操作序列所能达到的排列是有限制的。 但我们关心的是:**有多少个操作顺序(即 $ q $ 的排列)能让初始排列变为 $ p $**? --- ### ✅ 正确做法:**动态规划,逐位确定交换的影响** 我们采用如下思路(参考 NOI/IOI 类似题): > 使用 DP,状态为当前排列是否可达,但由于 $ n \leq 50 $,无法存整个排列。 但是 $ n \leq 50 $ 太大,不能记录整个排列。 然而,数据范围说 $ n \leq 50 $,但只有 100% 数据 $ n \leq 50 $,30% $ n \leq 10 $。 对于 $ n \leq 10 $,可以直接 BFS 枚举所有可能的状态(排列),状态数 $ 10! = 3628800 $,勉强可做。 但对于 $ n = 50 $,显然不行。 所以我们必须寻找组合规律。 --- ## 🚀 突破口:**Burnside 引理?No。线性代数?No。** 再看样例: ``` n = 3 p = [1, 2, 0] ``` 初始 s = [0,1,2] q = [0,1]: - swap(0,1): [1,0,2] - swap(1,2): [1,2,0] ✅ q = [1,0]: - swap(1,2): [0,2,1] - swap(0,1): [2,0,1] ❌ ≠ p 所以只有一个合法的 q。 说明:**不同的交换顺序会导致不同的结果。** --- ### ✅ 新思路:**每个交换操作改变排列,我们可以建图:状态 -> 操作 -> 新状态** 但由于 $ n > 10 $ 时状态太多,必须找规律。 --- ## 🎯 发现性质:**相邻交换序列与排列的奇偶性** 每次相邻交换改变排列的奇偶性(反转数 ±1,奇偶性翻转)。 我们进行了 $ n-1 $ 次交换。 所以: - 若 $ n-1 $ 为偶数,则最终排列与初始排列同奇偶; - 若 $ n-1 $ 为奇数,则奇偶性相反。 初始排列是 $ [0,1,...,n-1] $,逆序数为 0(偶排列)。 所以最终排列 $ p $ 必须满足: - $ \text{inv}(p) \equiv n-1 \pmod{2} $ 否则答案为 0。 这是第一个剪枝条件。 但在样例中: - p = [1,2,0] → inversions: (1,0)? no; (2,0)? yes; (1,2) ok → only one inversion? wait: 索引 0:1, 1:2, 2:0 对 (0,2): 1 > 0 → yes 对 (1,2): 2 > 0 → yes → 2 个逆序对 → 偶数 n-1 = 2 → even → 符合! 所以成立。 但这只是必要条件。 --- ## 🧩 终极思路:**递归构造 + 期望位置调整** 参考论文或已知模型:这类问题可用 **"bubble sort tree"** 或 **"Coxeter group A_{n-1}"** 中的唯一分解路径来处理,但我们简化。 --- ## ✅ 正解:**区间 DP + 相邻交换顺序的独立性** 我们发现一个重要事实: > 每个相邻交换操作 $ (i,i+1) $ 只能使用一次,所以整个过程就像是一个“打乱的冒泡排序”,每轮只能交换指定的一对。 但我们换个角度:**考虑每个元素是如何移动的。** 设 $ target[i] $ 表示元素 $ i $ 在目标排列中的位置。 元素 $ i $ 初始在位置 $ i $,需要移动到 $ target[i] $。 它只能通过不断与左右邻居交换来移动。 每次交换 $ (j,j+1) $,如果元素 $ i $ 在位置 $ j $,它可以右移到 $ j+1 $;如果在 $ j+1 $,可以左移到 $ j $。 但它能否移动,取决于交换发生的时机。 关键点:**一个元素跨越某个边 $ (j,j+1) $ 的唯一方式是,在它位于该边一侧时,发生了这次交换。** 而且,**每条边只能交换一次**,所以一旦交换发生,两个元素就固定了相对顺序(除非后续其他交换影响,但不能再交换回来!) ⚠️ **重点来了:每条边 $ (j,j+1) $ 只能交换一次,这意味着两个元素经过这条边的相对顺序只能被决定一次。** 这启发我们:**这类似于网络排序器(sorting network)**,其中比较器(这里是交换器)有固定位置,顺序由我们安排。 --- ## 🌟 类比 Sorting Network 我们有 $ n-1 $ 个“交换门”:位置 $ 0,1,...,n-2 $,每个门连接 $ (i,i+1) $,我们将这些门按某个顺序激活(每个一次),问有多少种激活顺序能把输入排列 $ [0,1,...,n-1] $ 变成 $ p $。 这就是 **Sorting Network 的功能计数问题**。 这个问题是 #P-hard 的一般情况,但 $ n \leq 50 $ 且结构简单(只有相邻门),或许有特殊解法。 但幸运的是,这里的“门”是完整的:我们有所有相邻对,只是顺序未知。 --- ## ✅ 解法:**动态规划,状态为当前哪些边已被使用,以及当前排列** 但由于排列有 $ n! $ 种,边有 $ n-1 $ 条,状态总数 $ 2^{n-1} \times n! $,当 $ n=10 $ 时约为 $ 2^9 \times 10! = 512 \times 3628800 \approx 1.8e9 $,不可行。 所以即使 $ n=10 $ 也难。 --- ## 🤯 转折点:**逆向构造 + 贪心删除最后一步** 我们考虑:**最后一次交换是哪一个?** 假设最后一次交换是 $ k $,即交换了位置 $ k $ 和 $ k+1 $。 那么,在这次交换之前,数组应该是:把当前排列 $ p $ 中的 $ p[k] $ 和 $ p[k+1] $ 交换回来。 记作 $ p' $。 然后问题变成:能否通过剩下的 $ n-2 $ 次交换(除 $ k $ 外的所有边)把初始排列变成 $ p' $? 这给出了一个递归结构。 定义函数 $ f(S, arr) $:使用边集合 $ S $(未使用的边索引集合),能否通过这些边的某种顺序把初始排列变成 $ arr $,返回方案数。 初始调用:$ f(\{0,1,...,n-2\}, p) $ 转移:枚举最后一个使用的边 $ k \in S $,构造前驱排列 $ arr' = arr $ 但交换 $ k,k+1 $ 位置的元素,然后递归 $ f(S \setminus \{k\}, arr') $ 边界:当 $ S = \emptyset $,检查 $ arr == [0,1,...,n-1] $?是则返回 1,否则 0。 ✅ 这是一个可行的记忆化搜索! 虽然状态很多,但 $ n \leq 10 $ 时,$ 2^{n-1} = 512 $,排列数 $ 10! = 3628800 $,总状态数最多 $ 512 \times 3628800 $,远远超过内存。 但我们可以通过 **哈希排列 + 记忆化** 来剪枝。 但 $ n \leq 10 $ 时,总状态数理论上太多。 不过实际搜索深度为 $ n-1 $,分支因子最多 $ n-1 $,总节点数 $ (n-1)! \times ? $,仍然很大。 但我们可以优化:**只搜索可达路径**。 --- ## ✅ 最终策略:**Meet-in-the-middle 或 IDA*?不现实。** 我们尝试写一个记忆化搜索,针对小 $ n $。 但题目要求 $ n \leq 50 $,说明一定有 **组合数学公式或线性 DP**。 --- ## 🚨 重新审题:有没有隐藏性质? 我们注意到:**每条边只交换一次,所以整个系统是一个“一次性相邻交换网络”**。 重要性质:**交换操作不可逆**。一旦你交换了 $ (i,i+1) $,就不能再交换回来。 所以,两个元素如果想要交叉穿过彼此,必须在它们路径上的所有边都按正确顺序激活。 但这依然复杂。 --- ## 🌟 灵感来源:CodeForces / AtCoder 类似题 这个问题与以下问题相似: > “Count the number of ways to arrange adjacent swaps (each edge once) to transform identity to p.” 存在一个 known solution:使用 **DP over subsets of edges**, with state = current permutation. 但由于 $ n \leq 10 $ 只有 30%,我们可以这样做: ### 对于 $ n \leq 10 $:使用记忆化搜索(DFS + memo) ### 对于 $ n \leq 50 $:必须找到规律 但等等,样例输出是 1,是不是意味着大多数情况下答案很小? --- ## ✅ 观察:交换顺序必须满足拓扑约束 考虑每个元素 $ i $ 从位置 $ i $ 移动到 $ pos_p(i) $。 它需要穿过一系列相邻边。 例如,若 $ i $ 向右移动,则它必须在它右边的那些边 $ (j,j+1) $ 上向右穿过去。 但每次穿越一个边 $ (j,j+1) $,必须发生在那个边的交换操作**之前**,当它在左边时。 更精确地说: > 元素 $ i $ 要从位置 $ a $ 移动到 $ b $,它必须在它穿越的每条边 $ (j,j+1) $ 上,**在交换发生时,它正好在左侧(或右侧)**。 而且,**它穿越一条边的时刻就是那条边被交换的时刻**。 并且,**它穿越边的顺序必须符合路径顺序**。 例如,从左到右移动:它必须按从左到右的顺序穿越各边。 但更重要的是:**两个元素交换时,它们的身份决定了谁往哪走**。 --- ## 🚀 正解:**Eulerian Path in Swap Graph? No.** 经过调研,此类问题的标准解法是: > **使用动态规划,状态为当前哪些边已经被使用,但压缩表示为轮廓线或位置分布。** 但有一种更巧妙的方法:**将问题转化为矩阵 or Kirchhoff,但不适用**。 --- ## ✅ Accepted Approach: **Recursion with Pruning (for small n)** 鉴于 $ n \leq 50 $,但 30% $ n \leq 10 $,而 100% $ n \leq 50 $,很可能 intended solution 是 **O(n^2) or O(n^3) DP**。 我们尝试找 pattern。 --- ## 🧪 手动计算小例子 ### Example 1: n=2 p=[1,0] s=[0,1] q must be [0] (only choice) swap(0,1) => [1,0] → matches so answer = 1 p=[0,1] → need 0 inversions, but n-1=1 odd → parity mismatch → answer = 0 ### Example 2: n=3 p=[1,2,0] → answer=1 (given) Let's try p=[0,1,2] (identity) We need even number of inversions: 0, n-1=2 even → possible. How many q lead to identity? Try q=[0,1]: - swap0: [1,0,2] - swap1: [1,2,0] ≠ id q=[1,0]: - swap1: [0,2,1] - swap0: [2,0,1] ≠ id q=[0,1] and [1,0] both not work. What about other sequences? Only two. So no way to return to identity after two adjacent swaps that are both used once. Wait, is it possible? Start: [0,1,2] Suppose we want to end at [0,1,2]. Net effect: no change. But each swap changes the arrangement. Unless we do something like: - swap(0,1): [1,0,2] - swap(0,1) again: but can't, because each edge only once. So impossible to return. In fact, using each adjacent swap exactly once means we are applying a fixed set of transpositions in some order. The resulting permutation depends on the order. But for identity, unless the product of transpositions is identity, which is rare. So likely answer = 0 for identity when n>1. But in our case, only specific orders work. --- ## ✅ Final Plan: Use DFS with Memoization for Small n Given the complexity, and that n<=50 might have special structure, but no known closed form, we implement a DFS that works for n<=10, and hope that larger cases have patterns. But since the problem asks for a program, and constraints up to 50, there must be a efficient solution. --- ## 🌟 Breakthrough: This is equivalent to counting linear extensions of a poset! Each element must cross certain edges. For an element i to move from pos i to pos j, it must cross the edges between. Moreover, the relative order of crossing events must respect the spatial order. Specifically, if two elements cross the same edge, the one who was left must be on left before the swap. And for an element to move right across edge k, it must be at position k before the swap at k happens. Similarly, to move left, it must be at k+1. This creates dependencies between swap times. We can create a DAG of constraints: - If element a needs to cross edge k before element b crosses edge k in the opposite direction, then swap k must happen when a is on left, b on right. But more importantly, for an element to cross edge k, it must have already crossed all edges from its start to its path. Thus, the time (step) when edge k is used must be after the time when the element arrived at the correct side. This leads to a system of constraints on the swap times. Then, the number of valid permutations q is the number of topological sorts of this constraint graph. That is: **the number of linear extensions of the partial order defined by the movement requirements**. This is a known approach. --- ### ✅ Solution Outline: 1. For each element `x`, find its initial position `i = x` and final position `j = pos_in_p[x]`. 2. It must cross every edge between `min(i,j)` and `max(i,j)-1` (if moving right), or `max(i,j)` to `min(i,j)-1` (left). 3. The order of crossing must be sequential: - If moving right: must cross `i -> i+1`, then `i+1->i+2`, etc. - So the swap on edge `k` must happen after it has entered the left side, which requires it to have crossed previous edges. 4. Therefore, for a right-moving element, the swap times must satisfy: `t[i] < t[i+1] < ... < t[j-1]` 5. For a left-moving element: `t[j] > t[j+1] > ... > t[i-1]` → so `t[j] > t[j+1]`, etc., i.e., `t[j+1] < t[j]`, so the sequence is decreasing in index, increasing in time? Let's see: If element moves from i=2 to j=0, it must: - first cross edge 1 (positions 1<->2), then edge 0 (0<->1) - so swap time[1] must be before swap time[0]? No: When it is at position 2, to move to 1, it must swap at edge 1. Then at position 1, to move to 0, swap at edge 0. So it crosses edge 1 first, then edge 0. Therefore, swap time for edge 1 must be **before** swap time for edge 0. So for a left-moving element from i to j (i>j), it must cross edges i-1, i-2, ..., j in that order. So: `t[i-1] < t[i-2] < ... < t[j]` Similarly, for right-moving: `t[i] < t[i+1] < ... < t[j-1]` So in both cases, the swap times along the path must be in increasing order of the edge indices for right-move, decreasing for left-move? No: - Right-move from i to j (i<j): cross edges i, i+1, ..., j-1 → must be in increasing order of edge index → so `t[i] < t[i+1] < ... < t[j-1]` - Left-move from i to j (i>j): cross edges i-1, i-2, ..., j → must be in decreasing order of edge index → so `t[i-1] < t[i-2] < ... < t[j]` Wait, the time should increase as the crossings happen. So for left-move: first cross edge i-1, then i-2, ..., lastly j. So `t[i-1] < t[i-2] < ... < t[j]` But the edge indices are decreasing, while times are increasing. So the condition is: for a left-moving element, the swap times on edges from j to i-1 must be in reverse index order. Define for each edge k, let `t[k]` be the step (from 0 to n-2) when it is used. Then for each element x moving from init_pos to final_pos, we get a chain of inequalities on `t[k]`. Example: x moves from 0 to 2: must cross edge 0, then edge 1 → so `t[0] < t[1]` x moves from 2 to 0: must cross edge 1, then edge 0 → so `t[1] < t[0]` These are constraints on the permutation `t[0..n-2]` (which is a permutation of 0..n-2). The number of such permutations satisfying all constraints is the number of linear extensions of the partial order defined by the union of all these chains. Then the answer is the number of linear extensions of this poset. And this can be computed by DP over subsets of edges. State: bitmask of which edges have been assigned a time (or rather, which swaps have been scheduled) But `n-1 <= 49`, so bitmask won't work. Instead, we can use DP on the number of ways to assign times to edges respecting the constraints. But how to compute the number of linear extensions of a poset given as union of chains? This is hard in general, but here the poset is a union of paths (chains), and the ground set is edges {0,1,...,n-2}. And each element's movement gives a chain on a contiguous interval of edges. Moreover, the chains are on intervals, and the relation is `t[a] < t[b] < ...` for consecutive edges. So the entire poset is defined by a set of precedence constraints on contiguous subarrays. This is solvable by dynamic programming. --- ### ✅ Final Algorithm: 1. Precompute for each element x: - initial position: x - final position: pos[x] where p[pos[x]] = x - if x < pos[x]: move right → must have `t[x] < t[x+1] < ... < t[pos[x]-1]` - if x > pos[x]: move left → must have `t[x-1] < t[x-2] < ... < t[pos[x]]` 2. Collect all such inequalities. Each is of the form `t[i] < t[j]` for consecutive or in chain. 3. We need to count the number of permutations of `{0,1,...,n-2}` for the values `t[0],t[1],...,t[n-2]` (wait, no) Actually, `t[k]` is the time when edge k is used, so `t` is a permutation of `0` to `n-2`. We want to count the number of assignments of distinct times to edges 0..n-2 such that all the chain constraints are satisfied. This is exactly the number of linear extensions of the poset. Since the constraints are on contiguous intervals and are total orders, we can use: > **Dynamic Programming with state as the set of minimal elements available** But state space too big. Alternatively, use inclusion-exclusion or generate all permutations for n-1 <= 9 (when n<=10), and for larger n use advanced methods. Given time, we implement the exponential algorithm for small n. --- ### Code (C++14) for n <= 10: ```cpp #include <bits/stdc++.h> using namespace std; const int MOD = 1000000007; int main() { int n; cin >> n; vector<int> p(n); for (int i = 0; i < n; i++) { cin >> p[i]; } // If n == 1, then no swaps, only valid if p is identity if (n == 1) { if (p[0] == 0) { cout << 1 << endl; } else { cout << 0 << endl; } return 0; } // Build target position for each number vector<int> pos(n); for (int i = 0; i < n; i++) { pos[p[i]] = i; } // We will collect constraints: edge i must happen before edge j vector<vector<bool>> before(n-1, vector<bool>(n-1, false)); // before[i][j] = true means t[i] < t[j] bool has_contradiction = false; for (int x = 0; x < n; x++) { int init = x; int fin = pos[x]; if (init == fin) continue; if (init < fin) { // Move right: cross edges init, init+1, ..., fin-1 in that order for (int e = init; e < fin-1; e++) { before[e][e+1] = true; } } else { // Move left: cross edges init-1, init-2, ..., fin for (int e = init-1; e > fin; e--) { before[e][e-1] = true; } } } // Now, we need to check for cycles in the precedence graph // And count the number of topological sorts of the path graph with extra edges // Since n-1 <= 49, but for now assume n<=10, so n-1<=9 if (n > 10) { // For larger n, this method may be slow, but we hope constraints are few // In contest, might need better algorithm, but here we do DFS for n-1 up to 9 // If n>10, skip // Actually, 100% n<=50, so we need better, but for now only handle n<=10 // We'll use brute-force over all (n-1)! orders vector<int> edges; for (int i = 0; i < n-1; i++) edges.push_back(i); int count = 0; do { // Check if this order satisfies all before constraints vector<int> time(n-1); // time[e] = when edge e is used for (int idx = 0; idx < n-1; idx++) { time[edges[idx]] = idx; } bool valid = true; for (int i = 0; i < n-1 && valid; i++) { for (int j = 0; j < n-1; j++) { if (before[i][j] && time[i] >= time[j]) { valid = false; break; } } } if (valid) { // Now simulate the process to verify vector<int> s(n); iota(s.begin(), s.end(), 0); for (int idx = 0; idx < n-1; idx++) { int e = edges[idx]; swap(s[e], s[e+1]); } if (s == p) { count++; } } } while (next_permutation(edges.begin(), edges.end()) && count < 2); // next_permutation requires sorted // But next_permutation only iterates once if not sorted sort(edges.begin(), edges.end()); count = 0; do { vector<int> time(n-1); for (int idx = 0; idx < n-1; idx++) { time[edges[idx]] = idx; } bool valid = true; for (int i = 0; i < n-1 && valid; i++) { for (int j = 0; j < n-1; j++) { if (before[i][j] && time[i] >= time[j]) { valid = false; break; } } } if (!valid) continue; // Simulate vector<int> s(n); iota(s.begin(), s.end(), 0); for (int e : edges) { swap(s[e], s[e+1]); } if (s == p) { count++; } } while (next_permutation(edges.begin(), edges.end())); cout << count % MOD << endl; } else { // n <= 10, so n-1 <= 9, 9! = 362880 vector<int> edges; for (int i = 0; i < n-1; i++) edges.push_back(i); int count = 0; do { vector<int> time(n-1); for (int idx = 0; idx < n-1; idx++) { time[edges[idx]] = idx; } bool valid = true; for (int i = 0; i < n-1; i++) { for (int j = 0; j < n-1; j++) { if (before[i][j] && time[i] >= time[j]) { valid = false; break; } } if (!valid) break; } if (!valid) continue; // Simulate the swap sequence vector<int> s(n); for (int i = 0; i < n; i++) s[i] = i; for (int e : edges) { swap(s[e], s[e+1]); } if (s == p) { count++; } } while (next_permutation(edges.begin(), edges.end())); cout << count % MOD << endl; } return 0; } ``` --- ### Explanation: 1. **Constraint Collection**: For each element, based on its movement direction, we add constraints that certain edges must be swapped before others. 2. **Brute-force Enumeration**: We enumerate all possible orders of edge swaps (i.e., all permutations of edges $ 0 $ to $ n-2 $). 3. **Validation**: For each order, we check: - Does it satisfy the constraints (e.g., if an element moves right, earlier edges must be used before later ones)? - When simulated, does it transform $ [0,1,...,n-1] $ into $ p $? 4. **Count and Output**: Count valid ones modulo $ 10^9+7 $. This works for $ n \leq 10 $, as $ (n-1)! \leq 9! = 362880 $. For $ n > 10 $, this is too slow. For $ n \leq 50 $, a more efficient method (e.g., dynamic programming on linear extensions with tree decomposition) is needed, but beyond scope. --- ### However, note: the constraints from element movements may already imply the simulation result. So maybe we can skip simulation. But in some cases, the constraints are necessary but not sufficient (because multiple elements interact), so simulation is safe. ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值