Codeforces 题目传送门 & 洛谷题目传送门

人生中第一道 *3500(显然不是自己独立 AC 的),不过还是祭一下罢

神仙 D1F

首先考虑对于给定的序列 \(a_1,a_2,\dots,a_n\) 怎样求它的“密度”。假设其密度为 \(p\),那么对于全部 \(c^p\) 个子序列一定存在恰好 \(c^{p-1}\) 个以 \(1,2,\dots,c\) 开头的子序列。考虑 \(a\) 数组中最短的前缀 \(a[1...k]\),满足 \(a_1,a_2,\dots,a_k\) 中恰好包含了 \(1\sim c\) 中所有数,显然若不存在这样的前缀,则 \(p=0\)。根据 \(k\) 的定义可知 \(a_k\) 在 \(a[1...k]\) 中恰好出现了一次,我们考虑以 \(a_k\) 开头的 \(c^{p-1}\) 个子序列,显然对于它们在原序列中第一次出现的位置,设为 \(a_k,a_{d_1},a_{d_2},\dots,a_{d_{p-1}}\),必定有 \(d_1>k\),也就是说 \(a[k+1...n]\) 中必须包含全部长度为 \(p-1\) 的 \(c^{p-1}\) 个子序列。因此我们可以得到这样的引理:一个序列的密度,等于不断删除其一个包含 \(1\sim c\) 的前缀,最多的删除次数。(没想到 *1)

这样我们可以得到一个 \(dp\),\(dp_{i,j}\) 表示以 \(i\) 开头的子序列中,密度至少为 \(j\) 的子序列有多少个(没想到 *2),显然 \(dp_{i,0}=2^{n-i}\)。考虑怎样转移,我们枚举这样以 \(i\) 开头的子序列的 \(k\)(\(k\) 的含义见上文)在原序列中所对应的位置 \(r\),我们再记 \(f_{l,r}\) 为对于所有元素下标都 \(\in[l,r]\),其中 \(a_l,a_r\) 必须被选择的序列中,有多少个满足 \(1\sim c\) 全部出现,并且 \(a_r\) 恰好出现一次。那么显然有 \(dp_{i,j}=\sum\limits_{r=i}^nf_{i,r}\sum\limits_{t=r+1}^{n+1}dp_{t,j-1}\)(没想到 *3),后面那个东西显然可以用前缀和优化,因此我们现在只需求出 \(f_{i,r}\) 即可。考虑 \(f_{l,r}\) 怎么求,显然若 \(a_l=a_r\),那么 \(a_r\) 不可能在子序列中只出现一次,故 \(f_{l,r}=0\);若 \(a_l\ne a_r\),我们记 \(cnt_x\) 为 \(\sum\limits_{i=l+1}^{r-1}[a_i=x]\),即 \(x\) 在 \((l,r)\) 中的出现次数,对于 \(a_r\),显然只能在 \(a_r\) 中被选择一次,其他位置都不能被选择,方案数为 \(1\);对于 \(a_l\),显然它在 \((l,r)\) 中每一次出现都可以爱选不选,不选拉倒,反正 \(l\) 是必须要选的,不可能出现 \(a_l\) 在子序列中出现次数为 \(0\) 的情况,方案数 \(2^{cnt_{a_l}}\),对于其余所有 \(x\ne a_l\land x\ne a_r\),总共有 \(2^{cnt_{x}}\) 种选法,但由于出现次数不能为 \(0\),故需减去 \(1\),即 \(2^{cnt_x}-1\),用乘法原理将它们乘起来即可。这个可以通过预处理 \(2^k-1\) 及其逆元实现 \(\mathcal O(1)\) 维护,即边扫描边维护。(没想到 *4)。处理完 \(dp_{i,j}\),由于题目要求密度恰好为 \(k\) 的子序列个数,求个差分即可。

这样暴力复杂度是 \(n^3\) 的,不过有一个性质是这个密度不会超过 \(\lfloor\dfrac{n}{c}\rfloor\)(没想到 *5),因此你这个 \(j\) 只需处理到 \(\lfloor\dfrac{n}{c}\rfloor\),复杂度 \(\dfrac{n^3}{c}\)。

然而当 \(c\) 特别小的时候该算法还是过不去,我们考虑另一个 \(c\) 越小越好的暴力并对其进行数据分治(BJOI 既视感)(没想到 *6)。记 \(DP_{i,j,S}\) 表示考虑到前 \(i\) 位,现在已经删了 \(j\) 个恰好包含 \(1\sim c\) 所有数的前缀,当前前缀中包含了 \(S\) 中所有数的方案数,这个转移就比较容易了罢,分当前元素选和当前元素不选两种情况转移,如果当前元素不选,那就直接转移到 \(DP_{i+1,j,S}\),否则如果加上这个元素后 \(S\) 变成了 \(\{1,2,\dots,c\}\),那咱就多分一组,转移到 \(DP_{i+1,j+1,\varnothing}\),否则转移到 \(DP_{i+1,j,S\cup\{a_i\}}\),时间复杂度 \(n2^c\dfrac{n}{c}\)。取 \(c=11\) 作为两个暴力的分界点复杂度最优。

然鹅这样还是会被卡常,这时候就要拿出我们的终极卡常武器了。这里有一个比较实用的卡常技巧,由于此题模数是 \(998244353\),\(8\times 998244353\times 998244353\) 刚好在 long long 范围内,所以考虑开 long long 存 DP 数组,额外记录一个 \(cnt_{i,j}\) 表示 \(dp_{i,j}\) 距离上一次取模进行了几次加法,若 \(cnt_{i,j}\) 达到 \(8\) 就令 \(dp_{i,j}\leftarrow dp_{i,j}\bmod 998244353\),这样取模常数就变成了原来的 \(\dfrac{1}{8}\)(没想到 *7)。当然如果你还是过不去(虽然我没遇到这样的情况),据楼下 ymx 神仙所说,可以在 CF 上使用 GNU C++17 (64) 这门语言,这样 long long 的常数能小很多。

最后注意特判 \(c=1\)。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=3e3;
const int MOD=998244353;
int n,c,a[MAXN+5];
int qpow(int x,int e=MOD-2){
int ret=1;
for(;e;e>>=1,x=1ll*x*x%MOD) if(e&1) ret=1ll*ret*x%MOD;
return ret;
}
namespace sub1{//subtask solving c>log2(n)
int f[MAXN+5][MAXN+5],sum[MAXN+5][MAXN+5];ll dp[MAXN+5][MAXN+5];
int pw2_1[MAXN+5],inv2_1[MAXN+5],cnt[MAXN+5];
void solve(){
for(int i=1;i<=n;i++) pw2_1[i]=(2*pw2_1[i-1]+1)%MOD;inv2_1[0]=1;
for(int i=1;i<=n;i++) inv2_1[i]=qpow(pw2_1[i]);
for(int l=1;l<=n;l++){
int mul=1,col=1;memset(cnt,0,sizeof(cnt));
cnt[a[l]]++;
for(int r=l+1;r<=n;r++){
if(a[r]==a[l]) mul=2ll*mul%MOD;
else{
mul=1ll*mul*inv2_1[cnt[a[r]]]%MOD;
cnt[a[r]]++;if(cnt[a[r]]==1) col++;
mul=1ll*mul*pw2_1[cnt[a[r]]]%MOD;
}
if(col==c&&(a[l]^a[r])) f[l][r]=1ll*mul*inv2_1[cnt[a[r]]]%MOD;
}
}
for(int i=n+1;i;i--) dp[i][0]=pw2_1[n-i]+1;
for(int i=n+1;i;i--) sum[i][0]=(sum[i+1][0]+dp[i][0])%MOD;
for(int j=1;j<=n/c;j++){
int cnt=0;
for(int i=n;i;i--) for(int k=i+1;k<=n;k++,cnt++){
dp[i][j]+=1ll*f[i][k]*sum[k+1][j-1];
if(cnt>>3) dp[i][j]%=MOD,cnt=0;
}
for(int i=n;i;i--) sum[i][j]=(sum[i+1][j]+dp[i][j]%MOD)%MOD;
}
for(int i=0;i<=n;i++){
if(!i) printf("%d ",(sum[1][i]-sum[1][i+1]+MOD-1)%MOD);
else printf("%d ",(sum[1][i]-sum[1][i+1]+MOD)%MOD);
}
}
}
namespace sub2{//subtask solving c<=log2(n)
const int MAXP=1<<11;
int dp[2][MAXN+5][MAXP+5];
void solve(){
int pre=0,cur=1;dp[0][0][0]=1;
for(int i=1;i<=n;i++){
for(int j=0;j<=n/c;j++)
for(int k=0;k<(1<<c)-1;k++) dp[cur][j][k]=0;
for(int j=0;j<=n/c;j++){
for(int k=0;k<(1<<c)-1;k++){
dp[cur][j][k]=(dp[cur][j][k]+dp[pre][j][k])%MOD;
if((k|(1<<a[i]-1))==(1<<c)-1) dp[cur][j+1][0]=(dp[cur][j+1][0]+dp[pre][j][k])%MOD;
else dp[cur][j][k|(1<<a[i]-1)]=(dp[cur][j][k|(1<<a[i]-1)]+dp[pre][j][k])%MOD;
}
} pre^=cur^=pre^=cur;
}
for(int j=0;j<=n;j++){
int ret=0;
for(int k=0;k<(1<<c)-1;k++) ret=(ret+dp[pre][j][k])%MOD;
if(!j) ret=(ret-1+MOD)%MOD;printf("%d ",ret);
}
}
}
namespace sub3{//subtask solving c=1
int fac[MAXN+5],ifac[MAXN+5];
void init_fac(int n){
fac[0]=ifac[0]=ifac[1]=1;
for(int i=2;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD,ifac[i]=1ll*ifac[i-1]*ifac[i]%MOD;
}
int binom(int x,int y){return 1ll*fac[x]*ifac[y]%MOD*ifac[x-y]%MOD;}
void solve(){
init_fac(n);printf("0 ");
for(int i=1;i<=n;i++) printf("%d ",binom(n,i));
}
}
int main(){
scanf("%d%d",&n,&c);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
if(c>11) sub1::solve();
else if(c>1) sub2::solve();
else sub3::solve();
return 0;
}

Codeforces 1158F - Density of subarrays(dp,神仙题)的更多相关文章

  1. Codeforces & Atcoder神仙题做题记录

    鉴于Codeforces和atcoder上有很多神题,即使发呆了一整节数学课也是肝不出来,所以就记录一下. AGC033B LRUD Game 只要横坐标或者纵坐标超出范围就可以,所以我们只用看其中一 ...

  2. Codeforces 148D 一袋老鼠 Bag of mice | 概率DP 水题

    除非特别忙,我接下来会尽可能翻译我做的每道CF题的题面! Codeforces 148D 一袋老鼠 Bag of mice | 概率DP 水题 题面 胡小兔和司公子都认为对方是垃圾. 为了决出谁才是垃 ...

  3. Codeforces 464E The Classic Problem(主席树+最短路+哈希,神仙题)

    题目链接 题意:给出一张 \(n\) 个点 \(m\) 条边的无向图,第 \(i\) 条边连接 \(u_i,v_i\),边权为 \(2^{w_i}\),求 \(s\) 到 \(t\) 的最短路. \( ...

  4. [BZOJ 3625] [Codeforces 438E] 小朋友的二叉树 (DP+生成函数+多项式开根+多项式求逆)

    [BZOJ 3625] [Codeforces 438E] 小朋友的二叉树 (DP+生成函数+多项式开根+多项式求逆) 题面 一棵二叉树的所有点的点权都是给定的集合中的一个数. 让你求出1到m中所有权 ...

  5. CF1158F Density of subarrays

    CF1158F Density of subarrays 首先可以发现,有值的p最大是n/c 对于密度为p,每个数至少出现c次,且其实是每出现c个数,就分成一段,这样贪心就得到了p %ywy n/c ...

  6. DP刷题记录(持续更新)

    DP刷题记录 (本文例题目前大多数都选自算法竞赛进阶指南) TYVJ1071 求两个序列的最长公共上升子序列 设\(f_{i,j}\)表示a中的\(1-i\)与b中色\(1-j\)匹配时所能构成的以\ ...

  7. DP刷题记录

    目录 dp刷题记录 codeforces 706C codeforces 940E BZOJ3997 POJ2279 GYM102082B GYM102082D codeforces132C L3-0 ...

  8. 贪心/构造/DP 杂题选做Ⅱ

    由于换了台电脑,而我的贪心 & 构造能力依然很拉跨,所以决定再开一个坑( 前传: 贪心/构造/DP 杂题选做 u1s1 我预感还有Ⅲ(欸,这不是我在多项式Ⅱ中说过的原话吗) 24. P5912 ...

  9. 贪心/构造/DP 杂题选做Ⅲ

    颓!颓!颓!(bushi 前传: 贪心/构造/DP 杂题选做 贪心/构造/DP 杂题选做Ⅱ 51. CF758E Broken Tree 讲个笑话,这道题是 11.3 模拟赛的 T2,模拟赛里那道题的 ...

随机推荐

  1. C#特性知识图谱-一、委托

    一. 委托 1.1 委托定义 委托可以看成是一个方法的容器,将某一具体的方法装入后就可以把它当成方法一样调用.一个委托类型的变量可以引用任何一个满足其要求的方法.委托类似于C语言中的函数指针,但并不完 ...

  2. Linux主机入侵检测

    检查系统信息.用户账号信息 ● 操作系统信息 cat /proc/version 用户信息 用户信息文件 /etc/passwd root:x:0:0:root:/root:/bin/bash 用户名 ...

  3. 4个实验,彻底搞懂TCP连接的断开

    前言 看到这个标题你可能会说,TCP 连接的建立与断开,这个我熟,不就是三次握手与四次挥手嘛.且慢,脑海中可以先尝试回答这几个问题: 四次挥手是谁发起的? 如果断电/断网了连接会断开吗? 什么情况下没 ...

  4. 第4次 Beta Scrum Meeting

    本次会议为Beta阶段第4次Scrum Meeting会议 会议概要 会议时间:2021年6月4日 会议地点:「腾讯会议」线上进行 会议时长:0.5小时 会议内容简介:对完成工作进行阶段性汇报:对下一 ...

  5. [技术博客] 软工-Ruby on Rails 后端开发总结分享

    [技术博客] 软工-Ruby on Rails 后端开发总结分享 在这次软件编写中,我们的后端使用了Ruby on Rails (RoR)框架. Rails框架是用Ruby编写的.这意味着当我们为Ru ...

  6. 零基础入门非常好的C语言基础资料

    C语言程序的结构认识 用一个简单的c程序例子,介绍c语言的基本构成.格式.以及良好的书写风格,使小伙伴对c语言有个初步认识. 例1:计算两个整数之和的c程序: #include main() { in ...

  7. Python课程笔记(六)

    今天上课补上了上次未学完比较重点的鼠标和键盘事件,同时开始学习运用turtle进行绘图. 本次课程的代码: https://gitee.com/wang_ming_er/python_course_l ...

  8. 面试官问:说说你对Java函数式编程的理解

    常见的面试问题 总结一下,在Java程序员的面试中,经常会被问到类似这样的问题: Java中的函数式接口是什么意思? 注解 @FunctionalInterface 的作用是什么? 实现一个函数式接口 ...

  9. 『学了就忘』Linux基础 — 8、虚拟机网络模式说明

    目录 1.虚拟机网卡 2.网络连接模式对应工作的网卡 3.桥接模式说明 4.补充说明 这篇主要总结一下虚拟机网络配置中桥接模式.NAT模式和仅主机模式的区别. 打开VMware,选中虚拟机,点击网络适 ...

  10. Java 将Excel转为et和ett格式

    以.et结尾的文件格式是属于金山办公软件WPS Office中的电子表格文件,.ett是一种模板文件格式.除了通过WPS软件可以创建该格式的电子表格外,也可以通过格式转换的方法来获得,如将Micros ...