zoukankan      html  css  js  c++  java
  • [省选前集训2021] 模拟赛3

    树(tree)

    题目描述

    点此看题

    (nleq 10^5)

    解法

    以前是暴力水过去的,结果今天考到了加强版,然后就凉了

    不难发现可以用线段树分别维护以 (u) 为根的最长上升子序列和最长下降子序列,然后拼起来就可以了。

    线段树的下标是开始位置的权值,可以快速算出 (a[u]) 为起始点的最长上升子序列和最长下降子序列。然后还要把子树的线段树合并上来,合并的时候可以更新一下答案(左右子树的子序列拼起来)

    (dfs) 的时候顺便算一下经过 (a[u]) 的子序列即可,时间复杂度 (O(nlog 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
    ",ans);
    }
    

    多项式(poly)

    题目描述

    给定多项式 (f_1(x)=sum_{i=0}^na_icdot x^i),有关于 (f) 的递推式 (f_i(x)=b_if_{i-1}'(x)+c_if_{i-1}(x))

    给出数组 (a,b,c),求 (f_n(x)),模 (998244353)

    (nleq 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(nlog^2n)) 分治加 ( t 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(nlog^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)

    题目背景

    在某一场省选模拟赛之后,( t zxy) 凝视着无比简洁的做法,轻轻叹息了一句:

    朝算贡献,夕死可矣。

    题目描述

    点此看题

    (f(x)) 表示将 (x) 的所有数码从小到大排序所得的数(忽略前导 (0) ),求 (sum_{i=1}^X f(i))

    (nleq 700),表示 (X) 的位数

    解法

    讲一个 (O(10^2n)) 吊打全场的做法,我用这个做法跑到了洛谷 ( t 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)+10cdot f(i-1)),表示可以让以前的 (d) 右移,并且这个 (d) 的贡献是 (g(i-1))

    • 填的数字大于 (d)(f(i)leftarrow 10cdot f(i-1)),表示可以让以前的 (d) 右移

    (g) 的转移:

    • 填的数字小于等于 (d)(g(i)leftarrow g(i-1)),表示不会对前面的贡献有影响
    • 填的数字大于 (d)(g(i)leftarrow 10cdot 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^2nlog 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
    ",ans);
    }
    
  • 相关阅读:
    Python 学习笔记(十三)Python函数(二)
    Python 学习笔记(十三)Python函数(一)
    Python 学习笔记(十二)Python文件和迭代(二)
    tb数据过多用省略号显示
    js,el表达式,<c:if>
    html元素标签时间格式化
    oracle链接报错shared memory realm does not exist
    mysql查找字段在哪个表中
    删除数据库重复数据
    excel使用poi操作。
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/14555480.html
Copyright © 2011-2022 走看看