[省选前集训2021] 模拟赛3
树(tree)
题目描述
\(n\leq 10^5\)
解法
以前是暴力水过去的,结果今天考到了加强版,然后就凉了
不难发现可以用线段树分别维护以 \(u\) 为根的最长上升子序列和最长下降子序列,然后拼起来就可以了。
线段树的下标是开始位置的权值,可以快速算出 \(a[u]\) 为起始点的最长上升子序列和最长下降子序列。然后还要把子树的线段树合并上来,合并的时候可以更新一下答案(左右子树的子序列拼起来)
在 \(dfs\) 的时候顺便算一下经过 \(a[u]\) 的子序列即可,时间复杂度 \(O(n\log n)\)
#include <cstdio>
#include <iostream>
#include <algorithm>
#include <set>
using namespace std;
const int M = 100005;
const int up = 100000;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,tot,cnt,ans,a[M],b[M],f[M],rt[M];
int mx[50*M][2],ls[50*M],rs[50*M];
struct edge
{
int v,next;
edge(int V=0,int N=0) : v(V) , next(N) {}
}e[2*M];
void ins(int &x,int l,int r,int id,int f,int t)
{
if(!x) x=++cnt;
mx[x][f]=max(mx[x][f],t);
if(l==r) return ;
int mid=(l+r)>>1;
if(mid>=id) ins(ls[x],l,mid,id,f,t);
else ins(rs[x],mid+1,r,id,f,t);
}
int ask(int x,int l,int r,int L,int R,int f)
{
if(L>r || l>R || !x) return 0;
if(L<=l && r<=R) return mx[x][f];
int mid=(l+r)>>1;
return max(ask(ls[x],l,mid,L,R,f),ask(rs[x],mid+1,r,L,R,f));
}
int merge(int x,int y)
{
if(!x || !y) return x+y;
ans=max(ans,max(mx[ls[x]][0]+mx[rs[y]][1],mx[rs[x]][1]+mx[ls[y]][0]));
mx[x][0]=max(mx[x][0],mx[y][0]);
mx[x][1]=max(mx[x][1],mx[y][1]);
ls[x]=merge(ls[x],ls[y]);
rs[x]=merge(rs[x],rs[y]);
return x;
}
void dfs(int u,int fa)
{
int lis=0,lds=0;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==fa) continue;
dfs(v,u);
int t1=ask(rt[v],1,m,1,a[u]-1,0);
int t2=ask(rt[v],1,m,a[u]+1,up,1);
ans=max(ans,max(t1+lds,t2+lis)+1);
lis=max(lis,t1);
lds=max(lds,t2);
rt[u]=merge(rt[u],rt[v]);
}
ins(rt[u],1,m,a[u],0,lis+1);
ins(rt[u],1,m,a[u],1,lds+1);
}
int main()
{
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
n=read();
for(int i=1;i<=n;i++)
a[i]=b[i]=read();
sort(b+1,b+1+n);
m=unique(b+1,b+1+n)-b-1;
for(int i=1;i<=n;i++)
a[i]=lower_bound(b+1,b+m+1,a[i])-b;
for(int i=1;i<n;i++)
{
int u=read(),v=read();
e[++tot]=edge(v,f[u]),f[u]=tot;
e[++tot]=edge(u,f[v]),f[v]=tot;
}
dfs(1,0);
printf("%d\n",ans);
}
多项式(poly)
题目描述
给定多项式 \(f_1(x)=\sum_{i=0}^na_i\cdot x^i\),有关于 \(f\) 的递推式 \(f_i(x)=b_if_{i-1}'(x)+c_if_{i-1}(x)\)
给出数组 \(a,b,c\),求 \(f_n(x)\),模 \(998244353\)
\(n\leq 100000\)
解法
考试时候直接切了,贴一下考试时候的笔记吧。
还原递推的路径说不定是一个好方法,考虑后面的数对前面数的贡献。
考虑 \(i<j\) 时 \(j\) 对 \(i\) 的贡献,也就是只考虑这个基本的数给他的贡献。
一共需要选择 \(j-i\) 次求导,然后选出 \(c_i\) 出来和 \(j-i\) 个 \(b_i\),写出生成函数(记号是求导次数):
后面那个柿子直接 \(O(n\log^2n)\) 分治加 \(\tt NTT\) 求出,记系数为 \(f[i]\),则答案是:
然后把 \(a[j]\cdot j!\) 翻转一下,在 \(n-i\) 出拿答案即可,时间复杂度 \(O(n\log^2n)\)
对不起,打出来一遍过掉样例。
#include <cstdio>
#include <iostream>
using namespace std;
#define int long long
const int M = 500005;
const int MOD = 998244353;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,len,a[M],b[M],c[M],f[M],fac[M],inv[M],rev[M];
void init(int n)
{
fac[0]=inv[0]=inv[1]=1;
for(int i=2;i<=n;i++) inv[i]=(MOD-MOD/i)*inv[MOD%i]%MOD;
for(int i=2;i<=n;i++) inv[i]=inv[i]*inv[i-1]%MOD;
for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
}
int qkpow(int a,int b)
{
int r=1;
while(b>0)
{
if(b&1) r=r*a%MOD;
a=a*a%MOD;
b>>=1;
}
return r;
}
void NTT(int *a,int len,int op)
{
for(int i=0;i<len;i++)
{
rev[i]=(rev[i>>1]>>1)|((i&1)*(len/2));
if(i<rev[i]) swap(a[i],a[rev[i]]);
}
for(int s=2;s<=len;s<<=1)
{
int t=s/2,w=(op==1)?qkpow(3,(MOD-1)/s):qkpow(3,MOD-1-(MOD-1)/s);
for(int i=0;i<len;i+=s)
for(int j=0,x=1;j<t;j++,x=x*w%MOD)
{
int fe=a[i+j],fo=a[i+j+t];
a[i+j]=(fe+x*fo)%MOD;
a[i+j+t]=((fe-x*fo)%MOD+MOD)%MOD;
}
}
if(op==1) return ;
int inv=qkpow(len,MOD-2);
for(int i=0;i<len;i++) a[i]=a[i]*inv%MOD;
}
void cdq(int *a,int l,int r)
{
if(l==r)
{
a[0]=c[l];a[1]=b[l];
return ;
}
int mid=(l+r)>>1,m=r-l+1;
int f[4*m]={},g[4*m]={};
cdq(f,l,mid);
cdq(g,mid+1,r);
len=1;while(len<=m) len<<=1;
NTT(f,len,1);NTT(g,len,1);
for(int i=0;i<len;i++) f[i]=f[i]*g[i]%MOD;
NTT(f,len,-1);
for(int i=0;i<=m;i++) a[i]=f[i];
}
signed main()
{
freopen("poly.in","r",stdin);
freopen("poly.out","w",stdout);
n=read();init(1e5);
for(int i=0;i<=n;i++) a[i]=read()*fac[i]%MOD;
for(int i=2;i<=n;i++) b[i]=read();
for(int i=2;i<=n;i++) c[i]=read();
cdq(f,2,n);//分治套NTT
for(int i=0;i<=n/2;i++) swap(a[i],a[n-i]);//翻转
len=1;while(len<=2*n) len<<=1;
NTT(a,len,1);NTT(f,len,1);
for(int i=0;i<len;i++) a[i]=a[i]*f[i]%MOD;
NTT(a,len,-1);
for(int i=0;i<=n;i++)
{
int tmp=a[n-i];
printf("%lld ",tmp*inv[i]%MOD);
}
puts("");
}
求和(sum)
题目背景
在某一场省选模拟赛之后,\(\tt zxy\) 凝视着无比简洁的做法,轻轻叹息了一句:
朝算贡献,夕死可矣。
题目描述
设 \(f(x)\) 表示将 \(x\) 的所有数码从小到大排序所得的数(忽略前导 \(0\) ),求 \(\sum_{i=1}^X f(i)\)
\(n\leq 700\),表示 \(X\) 的位数
解法
讲一个 \(O(10^2n)\) 吊打全场的做法,我用这个做法跑到了洛谷 \(\tt rank1\)
\(n\) 这么大就考虑数位 \(dp\) 吧,但你发现直接算根本不行,因为加入一个数之后会影响到很多数字的贡献。
那么我们就不要跑一次算贡献,我们跑 \(9\) 次算出每种数字 \(d\) 的贡献,每次只考虑一种数字的贡献是及其简单的
设 \(f(i,0/1)\) 表示算到了第 \(i\) 位,是否顶到了上界,对于数位 \(d\) 的总贡献,\(g(i,0/1)\) 表示算到了第 \(i\) 位,是否顶到了上界,假设在此基础上加入数字 \(d\) 的贡献应该是多少,转移枚举填入的数字。
\(f\) 的转移:
-
填的数字小于 \(d\) :\(f(i)\leftarrow f(i-1)\),表示排在前面不会对贡献有影响
-
填的数字就是 \(d\) :\(f(i)\leftarrow g(i-1)+10\cdot f(i-1)\),表示可以让以前的 \(d\) 右移,并且这个 \(d\) 的贡献是 \(g(i-1)\)
-
填的数字大于 \(d\) :\(f(i)\leftarrow 10\cdot f(i-1)\),表示可以让以前的 \(d\) 右移
\(g\) 的转移:
- 填的数字小于等于 \(d\) :\(g(i)\leftarrow g(i-1)\),表示不会对前面的贡献有影响
- 填的数字大于 \(d\) :\(g(i)\leftarrow 10\cdot g(i-1)\),表示会让填入的 \(d\) 右移
那么递推一下就可以做到 \(O(10^2n)\)
再讲一下考试时候的想法吧,如果觉得没什么价值可以跳过。
因为考试时候我是不会数位 \(dp\) 做法的,我想的是看 \(f(x)\) 什么样子再去算对应的 \(x\) 有多少个。
如果 \(n=999....999\) 这种情况可以直接做背包(用可重集的排列),然后我们枚举 \(x\) 的前几位(和上界 \(n\) 相同),就可以套 \(n=999...999\) 的做法,直接做时间复杂度 \(O(10^2n^3)\),某位巨佬用生成函数优化转移可以做到 \(O(10^2n\log n)\)
下面给出的是 \(O(10^2n)\) 的好做法
#include <cstdio>
#include <cstring>
const int MOD = 1e9+7;
const int M = 1005;
#define int long long
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,d,ans,g[M][2],f[M][2];char s[M];
void dp()
{
memset(f,0,sizeof f);
memset(g,0,sizeof g);
g[0][1]=d;
for(int i=0;i<n;i++)
for(int j=0;j<=1;j++)
{
int t=s[i+1]-'0';
for(int k=0;k<=9;k++)//枚举填的数
{
if(j && k>t) break;
int op=(j&&k==t);
if(k<d)
{
f[i+1][op]=(f[i+1][op]+f[i][j])%MOD;
g[i+1][op]=(g[i+1][op]+g[i][j])%MOD;
}
if(k==d)
{
f[i+1][op]=(f[i+1][op]+g[i][j]+10*f[i][j])%MOD;
g[i+1][op]=(g[i+1][op]+g[i][j])%MOD;
}
if(k>d)
{
f[i+1][op]=(f[i+1][op]+10*f[i][j])%MOD;
g[i+1][op]=(g[i+1][op]+10*g[i][j])%MOD;
}
}
}
ans=(ans+f[n][0]+f[n][1])%MOD;
}
signed main()
{
freopen("sum.in","r",stdin);
freopen("sum.out","w",stdout);
scanf("%s",s+1);n=strlen(s+1);
for(d=1;d<=9;d++)//算贡献
dp();
printf("%lld\n",ans);
}