CF1553I Stairs题解--zhengjun
更好的阅读体验
题目传送门
Luogu,Codeforces
谈点其他的
这个题目翻译好像有亿点问题,我重新发一波。
给定一个长度为 nnn 的排列 ppp。
令其中第 iii 个位置的权值为最长的包含 iii 的单调区间的长度(不仅要权值单调,而且要连续)。例如,p=[4,1,2,3,7,6,5]p=[4,1,2,3,7,6,5]p=[4,1,2,3,7,6,5] 中,第 666 个位置的权值为 333([5,7][5,7][5,7] 这个区间单调递减而且连续),第 222 个位置的权值为 333([2,4][2,4][2,4] 这个区间单调递增而且连续)。
将这些权值依次拼在一起,就得到了 ppp 的 ⌈\lceil⌈ 阶梯序列 ⌋\rfloor⌋。
给定 aaa,你需要求出存在多少个 ppp,使得 aaa 为 ppp 的 ⌈\lceil⌈ 阶梯序列 ⌋\rfloor⌋。
思路
首先可以看出来,就是这个排列的这些单调、权值连续的区间一定是独立的(没有相交的情况),这一步应该很好理解。
然后就可以根据 aaa 推出这些区间,比如样例1
:a={3,3,3,1,1,1}a=\{3,3,3,1,1,1\}a={3,3,3,1,1,1},那么这些区间就是 {[1,3],[4,4],[5,5],[6,6]}\{[1,3],[4,4],[5,5],[6,6]\}{[1,3],[4,4],[5,5],[6,6]}。
然后我们重新定义一下 aaa,就是每个区间的长度依次组成的序列。例如样例1
,a={3,1,1,1}a=\{3,1,1,1\}a={3,1,1,1}。
注意接下来所有说的 aaa 都是新定义的 aaa,lenlenlen 就是新定义的 aaa 的长度
接下来就可以将 1∼n1\sim n1∼n 分配给这些区间,那么其实就是 len!len!len! 种,就考虑让这些区间一个一个地取数,例如样例1
,如果取数的顺序为 3,1,2,4{3,1,2,4}3,1,2,4,那么区间 [1,3][1,3][1,3] 就分配到了 2,3,42,3,42,3,4 这三个数(因为 111 被排在第一个的区间 333 取走了),所以这个排列 ppp 就是 {2,3,4,5,1,6}\{2,3,4,5,1,6\}{2,3,4,5,1,6} 或 {4,3,2,5,1,6}\{4,3,2,5,1,6\}{4,3,2,5,1,6}。
这里看出了一个问题,就是所有长度大于等于 222 的区间都有递增和递减两种情况,所以还要乘上一个 2x2^x2x,这个 xxx 就是长度大于等于 222 的区间的个数。
但是还会有一个问题,就是分配出来的东西,可能相邻的两个区间可以一起组成一个大的单调、连续的区间,那么要把这种情况排除掉,就可以容斥。
答案就是
至少有 000 对相邻的区间合并 −-− 至少有 111 对相邻的区间合并 +++ 至少有2对相邻的区间合并⋯+(−1)len\dots+(-1)^{len}⋯+(−1)len至少有 (len−1)(len-1)(len−1) 对相邻的区间合并
Solution1
复杂度:O(n2)O(n^2)O(n2)
考虑 dp。
设 fi,jf_{i,j}fi,j 表示区间编号为 1∼i1\sim i1∼i,合并成 jjj 段的值是多少。
转移方程式:
fi,j={j×2×∑k=1j−1fi−1,k2≤j≤i≤len,aj>1j×(∑k=1j−12fi−1,k−fi−1,j−1)2≤j≤i≤len,aj=1f_{i,j}=\left\{ \begin{array}{rcl} j\times2\times\sum\limits_{k=1}^{j-1}f_{i-1,k} & {2\le j\le i\le len,a_j>1}\\ j\times(\sum\limits_{k=1}^{j-1}2f_{i-1,k}-f_{i-1,j-1}) & {2\le j\le i\le len,a_j=1} \end{array} \right. fi,j=⎩⎪⎪⎨⎪⎪⎧j×2×k=1∑j−1fi−1,kj×(k=1∑j−12fi−1,k−fi−1,j−1)2≤j≤i≤len,aj>12≤j≤i≤len,aj=1
初始化:
f1,1={2a1>11a1=1f_{1,1}=\left\{ \begin{array}{rcl} 2 & {a_1>1}\\ 1 & {a_1=1}\\ \end{array} \right. f1,1={21a1>1a1=1
fi,1=2,i≥2f_{i,1}=2,i\ge2 fi,1=2,i≥2
然后答案就是:
∑i=1len(−1)len−i×fn,i×i!\sum\limits_{i=1}^{len}(-1)^{len-i}\times f_{n,i}\times i!i=1∑len(−1)len−i×fn,i×i!
代码就不贴了,因为我比较勤快懒惰,很不想写代码 QWQ。
Solution
复杂度:O(nlog2n)O(n\log^2n)O(nlog2n)
可以注意到在新的 aaa 序列中合并两个区间时,左边是 xxx 段,右边是 yyy 段,合并起来就是 x+yx+yx+y 段,合并起来的值就是乘积,想到什么???
卷积!!!
可以分治 + NTT,这里模数正好是 982443539824435398244353,原根就是 333。
但是还有一些问题,就比如说 [l,mid][l,mid][l,mid] 的最右边的那个数是大于 111 的,[mid+1,r][mid+1,r][mid+1,r] 的最左边的那个数也是大于 111 的,合并起来就要除以 222。
两个都是 111,就要乘以 222。
然后,细节多得要死,常数大得要死。
最后,注意如果在 (mid,mid+1)(mid,mid+1)(mid,mid+1) 这里分一刀,那么直接卷就好了,如果不分,那么段数要 −1-1−1。
代码
在这里
注释看看最后一份代码好了。
然后顺利地 TLE 了……
然后,@275307894a 大佬,给了我一些建议:
每一次乘法时都要 NTT 3 遍,其实可以先 NTT 一遍,乘的时候直接对位相乘就好了。
但是,你发现又 TLE 了。
然后继续优化,发现 NTT 里面有多次调用 qpow,所以可以预处理。
然后发现 div 里面每次都只是除以二,所以不用每次算逆元,每次都直接乘以 2 的逆元就好了。
最后的代码(有注释)
#pragma GCC optimize(3)
#pragma GCC target("avx")
#pragma GCC optimize("Ofast")
#pragma GCC optimize(2)
#include<cstdio>
#include<vector>
#include<algorithm>
#include<ctime>
#define int long long
#define ll long long
using namespace std;
const ll mod=998244353,g=3,ginv=332748118,N=4e5+10,inv2=499122177;
ll qpow(ll x,ll y){ll ans=1;while(y){if(y&1)(ans*=x)%=mod;(x*=x)%=mod;y>>=1;}return ans;
}
int rr[N];
ll gg[N],gginv[N],lim[N];
ll cnt;
vector<ll> NTT(vector<ll> a,int limit,int type){//用 vector 好写一些吧for(int i=0;i<limit;i++)if(rr[i]<i)swap(a[rr[i]],a[i]);for(int mid=1;mid<limit;mid<<=1){ll wn=type==1?gg[mid<<1]:gginv[mid<<1];//main 里面处理for(int j=0;j<limit;j+=(mid<<1)){ll w=1;for(int k=0;k<mid;k++,(w*=wn)%=mod){cnt++;ll x=a[j+k],y=w*a[j+k+mid]%mod;//NTT 基本操作,蝴蝶效应a[j+k]=(x+y)%mod;a[j+k+mid]=(x-y+mod)%mod;}}}if(~type)return a;for(int i=0;i<limit;i++) (a[i]*=lim[limit])%=mod;return a;//别忘了如果是逆回去,要除以 limit,这里也预处理了
}
vector<ll> add(vector<ll> a,vector<ll> b){//相加int n=a.size(),m=b.size();while(n<m)a.push_back(0),n++;//要把 0 补起来,不然要 REwhile(n>m)b.push_back(0),m++;for(int i=0;i<n;i++)(a[i]+=b[i])%=mod;return a;
}
vector<ll> add_(vector<ll> a,vector<ll> b){//错位加int n=a.size(),m=b.size();while(n<m)a.push_back(0),n++;while(n>m)b.push_back(0),m++;for(int i=0;i<n-1;i++)(a[i]+=b[i+1])%=mod;return a;
}
vector<ll> div(vector<ll> a){//除以二int n=a.size();for(int i=0;i<n;i++)(a[i]*=inv2)%=mod;return a;
}
vector<ll> mul(vector<ll> a,ll x){//乘以xint n=a.size();for(int i=0;i<n;i++)(a[i]*=x)%=mod;return a;
}
struct zj{vector<ll>a[2][2];//分别表示左右端点是否大于 1zj(){a[0][0].push_back(0),a[0][1].push_back(0),a[1][0].push_back(0),a[1][1].push_back(0);}//分成 0 段的方案数就是 0
};
int n,a[N],b[N],m;
vector<ll> mul(vector<ll> a,vector<ll> b){//卷积int limit=1,l=0,n=a.size(),m=b.size();while(limit<=n+m)limit<<=1,l++;for(int i=0;i<limit;i++)rr[i]=(rr[i>>1]>>1)|((i&1)<<(l-1));while(n<limit)a.push_back(0),n++;while(m<limit)b.push_back(0),m++;a=NTT(a,limit,1);b=NTT(b,limit,1);for(int i=0;i<limit;i++)(a[i]*=b[i])%=mod;a=NTT(a,limit,-1);while(a.size()>1&&a[a.size()-1]==0)a.pop_back();//注意要把无用的 0 去掉,不然会很耗内存和时间return a;
}
vector<ll> mul_(vector<ll>a,vector<ll>b){//对位乘int n=a.size(),m=b.size();while(n<m)a.push_back(0),n++;while(n>m)b.push_back(0),m++;for(int i=0;i<n;i++)(a[i]*=b[i])%=mod;return a;
}
zj merge(int l,int r){if(l==r){//边界zj x;if(a[l]==1)x.a[0][0].push_back(1);else x.a[1][1].push_back(2);return x;}int mid=(l+r)>>1;zj x=merge(l,mid),y=merge(mid+1,r);zj tmp,tmpp;if(l+1==r){for(int i=0;i<2;i++)for(int j=0;j<2;j++)for(int ii=0;ii<2;ii++)for(int jj=0;jj<2;jj++)tmp.a[i][j]=add(tmp.a[i][j],mul(x.a[i][ii],y.a[jj][j]));for(int i=0;i<2;i++)for(int j=0;j<2;j++)for(int ii=0;ii<2;ii++)for(int jj=0;jj<2;jj++)if(ii!=jj)tmp.a[1][1]=add_(tmp.a[1][1],mul(x.a[i][ii],y.a[jj][j]));else if(!ii)tmp.a[1][1]=add_(tmp.a[1][1],mul(mul(x.a[i][ii],y.a[jj][j]),2));else tmp.a[1][1]=add_(tmp.a[1][1],div(mul(x.a[i][ii],y.a[jj][j])));return tmp;}else if(l+2==r){for(int i=0;i<2;i++)for(int j=0;j<2;j++)for(int ii=0;ii<2;ii++)for(int jj=0;jj<2;jj++)tmp.a[i][j]=add(tmp.a[i][j],mul(x.a[i][ii],y.a[jj][j]));for(int i=0;i<2;i++)for(int j=0;j<2;j++)for(int ii=0;ii<2;ii++)for(int jj=0;jj<2;jj++)if(ii!=jj)tmp.a[i][1]=add_(tmp.a[i][1],mul(x.a[i][ii],y.a[jj][j]));else if(!ii)tmp.a[i][1]=add_(tmp.a[i][1],mul(mul(x.a[i][ii],y.a[jj][j]),2));else tmp.a[i][1]=add_(tmp.a[i][1],div(mul(x.a[i][ii],y.a[jj][j])));return tmp;}//这两个特盘是因为长度在 3 以内时,合并会改变左右端点的 0/1int limit=1,llll=0,lena=0,lenb=0;//tmp 是 (mid,mid+1) 切一刀,tmpp 是不切,一定要分开,因为不切要错位加,点值是不能错位加的,要 NTT 回去再错位加起来for(int i=0;i<2;i++)for(int j=0;j<2;j++)lena=max(lena,(int)x.a[i][j].size());for(int i=0;i<2;i++)for(int j=0;j<2;j++)lenb=max(lenb,(int)y.a[i][j].size());while(limit<=lena+lenb)limit<<=1,llll++;for(int i=0;i<limit;i++)rr[i]=(rr[i>>1]>>1)|((i&1)<<(llll-1));//NTT 基本操作for(int i=0;i<2;i++)for(int j=0;j<2;j++)while(x.a[i][j].size()<limit)x.a[i][j].push_back(0);//注意补 0for(int i=0;i<2;i++)for(int j=0;j<2;j++)while(y.a[i][j].size()<limit)y.a[i][j].push_back(0);for(int i=0;i<2;i++)for(int j=0;j<2;j++)while(tmp.a[i][j].size()<limit)tmp.a[i][j].push_back(0);for(int i=0;i<2;i++)for(int j=0;j<2;j++)while(tmpp.a[i][j].size()<limit)tmpp.a[i][j].push_back(0);for(int i=0;i<2;i++)for(int j=0;j<2;j++)x.a[i][j]=NTT(x.a[i][j],limit,1);for(int i=0;i<2;i++)for(int j=0;j<2;j++)y.a[i][j]=NTT(y.a[i][j],limit,1);for(int i=0;i<2;i++)for(int j=0;j<2;j++)for(int ii=0;ii<2;ii++)for(int jj=0;jj<2;jj++)tmp.a[i][j]=add(tmp.a[i][j],mul_(x.a[i][ii],y.a[jj][j]));//NTT 之后要对位乘for(int i=0;i<2;i++)for(int j=0;j<2;j++)for(int ii=0;ii<2;ii++)for(int jj=0;jj<2;jj++)if(ii!=jj)tmpp.a[i][j]=add(tmpp.a[i][j],mul_(x.a[i][ii],y.a[jj][j]));else if(!ii)tmpp.a[i][j]=add(tmpp.a[i][j],mul(mul_(x.a[i][ii],y.a[jj][j]),2));else tmpp.a[i][j]=add(tmpp.a[i][j],div(mul_(x.a[i][ii],y.a[jj][j])));//不能再这里写错位加,我 TM 调了半天!!!一定要 NTT 回去再错位加for(int i=0;i<2;i++)for(int j=0;j<2;j++)tmp.a[i][j]=NTT(tmp.a[i][j],limit,-1);for(int i=0;i<2;i++)for(int j=0;j<2;j++)tmpp.a[i][j]=NTT(tmpp.a[i][j],limit,-1);for(int i=0;i<2;i++)for(int j=0;j<2;j++)tmp.a[i][j]=add_(tmp.a[i][j],tmpp.a[i][j]);//错位加for(int i=0;i<2;i++)for(int j=0;j<2;j++)while(tmp.a[i][j].size()>1&&tmp.a[i][j][tmp.a[i][j].size()-1]==0)tmp.a[i][j].pop_back();//删去无用 0 ,但是要留一个return tmp;
}
signed main(){scanf("%lld",&m);//上文讲的预处理for(int mid=1;mid<N;mid<<=1)gg[mid]=qpow(g,(mod-1)/mid),gginv[mid]=qpow(ginv,(mod-1)/mid),lim[mid]=qpow(mid,mod-2);for(int i=1;i<=m;i++)scanf("%lld",&b[i]);for(int i=1;i<=m;){a[++n]=b[i];for(int j=0;j<b[i];j++){if(i+j>m)return printf("0\n"),0;if(b[i+j]!=b[i])return printf("0\n"),0;}i=i+b[i];}//变幻成新定义的 a 数组zj ans=merge(1,n);for(int i=0;i<2;i++)for(int j=0;j<2;j++){while(ans.a[i][j].size()<=n+1)ans.a[i][j].push_back(0);}for(int i=1;i<=n;i++)(ans.a[0][0][i]+=ans.a[0][1][i]+ans.a[1][0][i]+ans.a[1][1][i])%=mod;ll now=1,ANS=0;for(int i=1;i<=n;i++){//容斥,和上面的公式一样的(now*=i)%=mod;(ANS+=((n-i+1)&1?1:-1)*now*ans.a[0][0][i]%mod)%=mod;(ANS+=mod)%=mod;}return printf("%lld",ANS),0;
}
最后说点什么
第一次发黑题题解,第一次独立做出黑题。
需要卡常,不知道为什么我跑了 2.42.42.4 min,别人都是 101010 s。
谢谢–zhengjun
CF1553I Stairs题解--zhengjun相关推荐
- 70. Climbing Stairs 题解
https://leetcode.com/problems/climbing-stairs/ ways[n]: 跨越n个台阶的方法数量 跨越n个台阶的方法数 = 跨越[n-2]个台阶,再跨2个台阶 + ...
- 746. Min Cost Climbing Stairs 题解
leetcode.com/problems/min-cost-climbing-stairs/ minCost[n]: 达到台阶n需要消耗的最小成本 Cost[n]: 台阶n自身的成本 递推式为 mi ...
- 洛谷P1046陶陶摘苹果题解--zhengjun
题目描述 陶陶家的院子里有一棵苹果树,每到秋天树上就会结出 101010 个苹果.苹果成熟的时候,陶陶就会跑去摘苹果.陶陶有个 303030 厘米高的板凳,当她不能直接用手摘到苹果的时候,就会踩到板凳 ...
- 洛谷P1053篝火晚会题解--zhengjun
题目描述 佳佳刚进高中,在军训的时候,由于佳佳吃苦耐劳,很快得到了教官的赏识,成为了"小教官".在军训结束的那天晚上,佳佳被命令组织同学们进行篝火晚会.一共有nnn个同学,编号从1 ...
- 洛谷P1039侦探推理题解--zhengjun
题目描述 明明同学最近迷上了侦探漫画<柯南>并沉醉于推理游戏之中,于是他召集了一群同学玩推理游戏.游戏的内容是这样的,明明的同学们先商量好由其中的一个人充当罪犯(在明明不知情的情况下),明 ...
- 洛谷P1001题解--zhengjun
题目描述 输入两个整数 a,ba,ba,b,输出它们的和(∣a∣,∣b∣≤109|a|,|b |\le 10^9∣a∣,∣b∣≤109). 注意 PascalPascalPascal 使用intege ...
- CF1555D Say No to Palindromes题解--zhengjun
题目大意 给你一个字符串,每次询问区间 [l,r][l,r][l,r] 最小需要改变几个才能使得这个区间的所有子区间都不是长度至少为 222 的回文串. 思路 首先我们想一想所有子区间都不是长度至少为 ...
- 2020 NOI online 入门组第一题题解--zhengjun
题目描述 小明的班上共有 n n n 元班费,同学们准备使用班费集体购买 3 3 3 种物品: 圆规,每个 7 7 7 元. 笔,每支 4 4 4 元. 笔记本,每本 3 3 3 元. 小明负责订购文 ...
- 洛谷P1034矩形覆盖题解--zhengjun
题目描述 在平面上有 nnn 个点(n≤50n \le 50n≤50),每个点用一对整数坐标表示.例如:当 n=4n=4n=4 时,444 个点的坐标分另为:p1p_1p1(1,11,11,1),p ...
最新文章
- c++获取当前目录_如何在 Linux 下利用 Vim 搭建 C/C++ 开发环境?
- html5通html5通,HTML5 history详解
- oracle600错误,oracle在导入数据时报600错误的解决方法
- 完全卸载Oracle方法(亲测有效)
- request获取网页单选框的值
- document.write()详解
- go.sum中特殊hash如何计算
- TensorFlow2-卷积神经网络
- ediplus 复制编辑一列_EditPlus等编辑器选中列(块)的方法
- mime类型是什么 node_Node.js - 文件系统获取文件类型
- LNMP环境添加第三方模块
- Sublime Text 3 添加当前时间的制作方法
- Java-Collection、List
- 《梦断代码》读后感2
- 老去的80后忆当年-致80后的朋友们
- vux移动端UI组件库
- 收银怎样挂单和取单_挂单取单(PC收银)
- 儿童python编程教程-儿童编程python入门
- 德鲁克的时间管理法—《可以量化的…
- Factory method 'springSecurityFilterChain' threw exception