1 条题解

  • 0
    @ 2025-8-24 22:56:33

    自动搬运

    查看原文

    来自洛谷,原作者为

    avatar LionBlaze
    @wsx 我这么弱你还说我批话/ll || 有实力或没那么有实力的欢迎加入 WROI /team/96917 || 前置声明:不当除了 WROI 之外任何公开赛的背锅人。接受私信协商。

    搬运于2025-08-24 22:56:33,当前版本为作者最后更新于2024-06-19 20:11:53,作者可能在搬运后再次修改,您可在原文处查看最新版

    自动搬运只会搬运当前题目点赞数最高的题解,您可前往洛谷题解查看更多

    以下是正文


    其实我一开始看到也是拓扑排序 + DP,并且和 spfa_ 大佬最后的结论一样,但会更详细点。

    为什么要拓扑排序?

    小杨有个包含 nn 个节点 mm 条边的有向无环图
    根据路径上的经过节点的先后顺序可以得到一个节点权值的序列。

    正是拓扑排序的拿手好戏。

    DP 开始

    定义

    第一种

    直接套,fif_i 表示从某个点到点 ii,最长不下降子序列的最大长度。

    然后每遇到一条边就更新一次。

    然后……就没有然后了。吗? \color{white}{\texttt{吗?}}

    时间复杂度:一共 mm 条边,每条边转移时间复杂度为 O(n)O(n),总时间复杂度为 O(nm)O(nm),毫无疑问会 TLE。

    看来不行。

    第二种

    抓住题目的“弱点”,发现一个很奇怪的条件,1Ai101 \le A_i \le 10

    所以可以成为 ff 的第二维:

    fi,jf_{i,j} 代表从某个点开始,到点 ii,满足最后一个数字为 jj 的最长不下降子序列的最大长度。

    初始状态

    所有 fi,Ai=1f_{i,A_i} = 1:只有当前点。

    转移顺序

    拓扑序,如果不是,在转移当前点时可能某个前驱节点还没转移到,如果从那个节点转移到这个节点刚好是最优解(或者最优解的一部分),答案就会出错。

    状态转移方程

    重点来了!

    对于点 vv 的某一个前驱结点 uu(或者对于点 uu 的某个后继结点 vv),有两种转移:

    第一种

    一种是将点 vv 加入到点 uu 的最长不下降子序列中,比如:

    此时点 uu 的最长不下降子序列为 1,2,31,2,3(即 fu,3=3f_{u,3} = 3),而 Av3A_v \ge 3,则这种转移会使 fv,Av=fu,3+1=4f_{v, A_v} = f_{u, 3} + 1 = 4,前提是原 fv,Av<4f_{v, A_v} < 4(不然得到更劣的解)。

    不难知道:

    $$\forall 1 \le i \le A_v:f_{v, A_i}=\max(f_{v, A_i}, f_{u, i} + 1) $$

    右边很好理解。左边为什么是 1iAv1 \le i \le A_v 呢?不可以大于 AvA_v 吗?

    不可以。

    因为在不下降序列的末尾增加一个比原来的末尾更小的元素,增加后就不是不下降序列了。

    比如你在不下降序列 1,2,31,2,3 最后加入一个 22,最后还满足不下降吗?显然不满足。

    第二种

    第二种就是不算上元素 vv,本来是 1,2,31,2,3 现在还是 1,2,31,2,3,子序列嘛,可以去除某些元素(这里是去除元素 vv)。

    $$\forall 1 \le i \le 10 : f_{v,i} = \max(f_{v,i},f_{u,i}) $$

    答案

    所有有意义的 fi,jf_{i,j} 的最大值(有意义:1in1\le i \le n1j101 \le j \le 10

    完结撒花~代码

    #include <queue>
    #include <cstdio>
    #include <vector>
    
    using namespace std;
    
    vector<int> web[100005]; //邻接表
    int a[100005]; //A
    int ind[100005]; //入度
    int f[100005][15];
    /*
    f[i][j] 代表从xxx节点到i节点,末尾为j的最长不下降子序列长度
    */
    
    int main()
    {
    	int n, m;
    	scanf("%d%d", &n, &m);
    	for(int i=1;i<=n;i++)
    	{
    		scanf("%d", a + i);
    	}
    	for(int i=1;i<=m;i++)
    	{
    		int u, v;
    		scanf("%d%d", &u, &v);
    		web[u].push_back(v); //建图
    		ind[v]++; //入度++
    	}
    	queue<int> q; //拓扑排序
    	for(int i=1;i<=n;i++)
    	{
    		if(ind[i] == 0) //入度为0,进队
    		{
    			q.push(i);
    		}
    		f[i][a[i]] = 1; //f初始
    	}
    	while(!q.empty())
    	{
    		int u = q.front();
    		q.pop();
    		for(int v : web[u])
    		{
    			if(!--ind[v]) q.push(v); //入度变为0
    			for(int i=1;i<=a[v];i++)
    			{
    				if(f[u][i] + 1 > f[v][a[v]]) f[v][a[v]] = f[u][i] + 1; //第一种转移
    			}
    			for(int i=1;i<=10;i++)
    			{
    				if(f[u][i] > f[v][i]) f[v][i] = f[u][i]; //第二种转移
    			}
    		}
    	}
    	int maxn = 0;
    	for(int i=1;i<=n;i++)
    	{
    		for(int j=1;j<=10;j++)
    		{
    			if(f[i][j] > maxn) maxn = f[i][j]; //取最大
    		}
    	}
    	printf("%d\n", maxn);
    	return 0;
    }
    
    • 1

    信息

    ID
    9983
    时间
    1000ms
    内存
    512MiB
    难度
    3
    标签
    递交数
    0
    已通过
    0
    上传者