题解:[NOI 2026] 线段

题目传送门

题意分析

若干条线段构成的集合是优美的,当且仅当这些线段覆盖的并集是一条大线段,且每个点被覆盖的次数 \(\leq2\)

覆盖次数为 \(0\) 的点只能在大线段的左边或者右边。

考虑一条一条从左到右加入线段,不妨钦定 \(l_1\leq l_2\leq l_3\leq\cdots\leq l_n\),当 \(i<j,l_i=l_j\)\(r_i\leq r_j\)

\(f_{i,j,k}\) 表示最右边的覆盖次数为 \(2\) 的点为 \(i\),覆盖次数为 \(1\) 的点为 \(j\)\(k\) 条线段。

钦定不存在的时候为 \(0\),初始化就是 \(f_{0,0,0}=1\)

之后考虑转移,加入线段 \([l,r]\),为了合法需要满足 \(i<l\leq j\),转移即:

\[f_{\min(j,r),\max(j,r),k+1}\leftarrow f_{\min(j,r),\max(j,r),k+1}+f_{i,j,k} \]

暴力转移可以做到 \(\mathcal O\left(nm^2k\right)\),考虑优化。

容易发现转移时 \(i\) 的取值不重要,可以写成:

\[f_{\min(j,r),\max(j,r),k+1}\leftarrow f_{\min(j,r),\max(j,r),k+1}+\sum_{i=0}^{l-1}f_{i,j,k} \]

因而可以维护 \(f_{i,j,k}\)\(i\) 这一维的前缀和 \(\displaystyle\textit{pre}_{x,j,k}=\sum_{i<x}f_{i,j,k}\)。时间复杂度 \(\mathcal O(nmk)\)

但是常数过大,且爆 MLE。

因为 \(l-1\) 单调不降,可以维护指针 \(p\),然后把 \(\textit{pre}\) 状态压到二维 \(\textit{pre}_{j,k}\)。或者把 \(f,\textit{pre}\)\(k\) 这一维都滚掉。

常数仍然你太大,考虑卡常。对 \(998244353\) 取模可以不写 %P,而是:

while(x>=P){
    x-=P;
    break;
}

因为编译器期望 while(x)x 为假。抓取的时候有一点用。

最终 luogu 3.98s 极限卡过了。

AC 代码

#include <vector>
void init(int c, int t);
std::vector<int> segment(int n, int m, int k, std::vector<int> l, std::vector<int> r);
//#include<bits/stdc++.h>
#include<algorithm>
#include<iostream>
using namespace std;
typedef long long ll;
constexpr const int N=3000,M=1e3,K=200,P=998244353;
int f[M+1][M+1][K+1],pre[M+1][K+1];
void init(int c,int t){
	
}
vector<int>segment(int n,int m,int K,vector<int>l,vector<int>r){
	vector<int>ans(K+1);
	vector<pair<int,int>>a(n);
	for(int i=0;i<n;i++){
		a[i]={l[i],r[i]};
	}
	sort(a.begin(),a.end());
	for(int i=0;i<=m;i++){
		for(int j=0;j<=m;j++){
			for(int k=0;k<=K;k++){
				f[i][j][k]=0;
			}
		}
	}
	for(int j=0;j<=m;j++){
		for(int k=0;k<=K;k++){
			pre[j][k]=0;
		}
	}
	f[0][0][0]=1;
	int p=0;
	pre[0][0]=1;
	for(auto [l,r]:a){
		while(p<l-1){
			p++;
			for(int j=l;j<=m;j++){
				for(int k=0;k<K;k++){
					pre[j][k]+=f[p][j][k];
					while(pre[j][k]>=P){
						pre[j][k]-=P;
						break;
					}
				}
			}
		}
		for(int k=0;k<K;k++){
			for(int j=l;j<=r;j++){
				f[j][r][k+1]+=pre[j][k];
				while(f[j][r][k+1]>=P){
					f[j][r][k+1]-=P;
					break;
				}
			}
			for(int j=r+1;j<=m;j++){
				f[r][j][k+1]+=pre[j][k];
				while(f[r][j][k+1]>=P){
					f[r][j][k+1]-=P;
					break;
				}
			}
		}
		f[0][r][1]++; 
		pre[r][1]++;
	}
	#define file
	for(int k=1;k<=K;k++){
		ll pl=0;
		for(int i=0;i<=m;i++){
			for(int j=0;j<=m;j++){
				pl+=f[i][j][k];
			}
		}
		ans[k]=pl%P;
	}
	return ans;
}
posted @ 2026-08-04 14:00  TH911  阅读(3)  评论(0)    收藏  举报