[省选前集训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\),写出生成函数(记号是求导次数):

\[\frac{j!}{i!}\cdot[x^{j-i}]\prod_{i=2}^n(b_ix+c_i) \]

后面那个柿子直接 \(O(n\log^2n)\) 分治加 \(\tt NTT\) 求出,记系数为 \(f[i]\),则答案是:

\[g[i]=\frac{1}{i!}\sum_{j=i}^n(a[j]\cdot j!)\cdot f[j-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);
}
posted @ 2021-03-18 17:28  C202044zxy  阅读(62)  评论(0编辑  收藏  举报