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. 【Java虚拟机3】类加载器

    前言 Java虚拟机设计团队有意把类加载阶段中的"通过一个类的全限定名来获取描述该类的二进制字节流"这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需的类. ...

  2. 什么,你还使用 webpack?别人都在用 vite 搭建项目了

    一.vite 到底是干嘛的? vite 实际上就是一个面向现代浏览器,基于 ES module 实现了一个更轻快的项目构建打包工具. vite 是法语中轻快的意思. vite 的特点: 1.轻快的冷服 ...

  3. 如何从一台OPC Server访问多个PLC

    项目中遇到如下情况: 1. 整条生产线由多个PLC分别控制,但是所有PLC在同一个局域网内.PLC采用西门子的S7-200 Smart 2. 客户希望在操作工站的电脑(跟PLC在同一个局域网内)上提供 ...

  4. the Agiles Scrum Meeting 9

    会议时间:2020.4.17 20:00 1.每个人的工作 今天已完成的工作 个人结对项目增量开发组:基本实现个人项目创建.仓库自动分配,修复bug issues: 增量组:准备评测机制,增加仓库自动 ...

  5. GitHub Universe 2021|MS Reactor 邀你共聚年度盛会

    GitHub Universe 2021 将于2021年10月27-28日(PDT)在线直播,MS Reactor 将与 CSDN 合作进行转播,与你一同观看这场全球开发者盛会. 关于 GitHub ...

  6. 将manjaro作为主力开发系统,我遇到了哪些坑。

    首先遇到的问题就是企业微信. 最开始几天,我直接去安装企业微信和微信,安装全都报错了. 无奈之下,只好安装了virtual box,装了一个win7,可以正常使用微信,企业微信,最开始蛋疼的地方是,企 ...

  7. Python pylint requires Python '>=3.4.*' but the running Python is 2.7.12

    用pylint 1.9.x 安装 pip install pylint==1.9.3. 或者换源 pip install -i https://pypi.tuna.tsinghua.edu.cn/si ...

  8. 恶意代码の奇客pdf分析

    目录 恶意代码の奇客pdf分析 奇客PDF安装包分析 静态分析基础技术 上传杀毒网 查壳 编译时间 导入表分析 资源查看 动态分析基础技术 Process Monitor监控 Process Expl ...

  9. 王爽汇编第五章,[bx]和loop指令

    目录 王爽汇编第五章,[bx]和loop指令 [bx]和loop指令 例子: 王爽汇编第五章,[bx]和loop指令 [bx]和loop指令 [bx]之前我们介绍寄存器的时候,已经很详细的说明过了,b ...

  10. vue三级路由显示+面包屑

    问题一:如何让三级路由内容显示显示在一级路由页面 可以说是我点级二级路由导航的时候是不发生跳转的,但还要去动态的生成面包屑 const routes = [{ path: '/', name: 'Ho ...