zoukankan      html  css  js  c++  java
  • Hello 2019 (D~G)


    Codeforces 1097

    比赛链接

    咕咕了一个月...终于闲的没事想起补了...

    ABC代码没在本地(而且懒),就不放了...
    (然而当时C题FST了真是想...= =)

    D.Makoto and a Blackboard(DP 期望)

    (Description)
    给定(n,k)。每次(n)会等概率地变成自己的一个约数(包括(1,n)),求变化(k)次后(n)期望是多少。
    (nleq10^{15}, kleq10^4)

    (Solution)
    (n)质因数分解,(n=p_1^{a_1}p_2^{a_2}...p_k^{a_k}),每次变化后每个质因子的次数(a_i)会随机变成(0sim a_i)中的一个数,且每个(a_i)的变化是独立的。
    所以可以对每个质因子分别计算期望,最后乘起来。
    (f[i][j])表示(i)次变化后,(a)变成(j)的概率,(f[0][a]=1)
    转移就是(f[i][j]=sum_{k=j}^afrac{f[i-1][k]}{k+1})
    把所有质因数都算一遍,乘起来就好了。
    质因子个数、次数都是(log)级别的(其实乘起来也就(log)级别?),所以复杂度是(O(klog^2n))(其实也就是(O(klog n))?)。

    我竟然把逆元求成阶乘逆元了然后调二十分钟= =我也是醉了。

    //217ms	0KB
    #include <cstdio>
    #include <algorithm>
    #define mod 1000000007
    #define Mod(x) x>=mod&&(x-=mod)
    typedef long long LL;
    const int N=52;
    
    int inv[N];
    
    int Solve(int x,int K,int a)
    {
    	static int sum[N];
    	for(int i=0; i<a;++i) sum[i]=0;
    	sum[a]=1, sum[a+1]=0;
    	for(int i=1; i<=K; ++i)
    		for(int j=a; ~j; --j)
    			sum[j]=sum[j+1]+1ll*sum[j]*inv[j+1]%mod, Mod(sum[j]);
    	LL ans=0;
    	for(int i=0,t=1; i<=a; ++i) ans+=1ll*t*sum[i]%mod, t=1ll*t*x%mod;
    	return ans%mod;
    }
    
    int main()
    {
    	inv[1]=1;
    	for(int i=2; i<N; ++i) inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    
    	LL n,ans=1; int K; scanf("%I64d%d",&n,&K);
    	for(int i=2; 1ll*i*i<=n; ++i)//这个枚举貌似可以优化(每次+=6?)
    		if(!(n%i))
    		{
    			int a=1; n/=i;
    			while(!(n%i)) ++a, n/=i;
    			ans=ans*Solve(i,K,a)%mod;
    		}
    	if(n!=1) ans=ans*Solve(n%mod,K,1)%mod;//n可能很大 别忘取模!
    	printf("%I64d
    ",ans);
    
    	return 0;
    }
    

    E.Egor and an RPG game(思路 LIS Dilworth定理)

    (Description)
    (k)表示将一个排列(A_i)划分成若干个递增或递减子序列,所需子序列个数的最小值。令(f(n))表示所有(1...n)的排列中,(k)的最大值。(有点绕...就是用最少的序列划分排列,(f(n))是所有(1)(n)的排列中所需序列最多的那个个数)
    (T)次询问,每次给定一个(1...n)的排列(A_i),你需要将其分成(kleq f(n))段递增或递减的序列并输出方案(不要求(k)最小,只需(leq f(n)))。
    (T,sum nleq10^5)

    (Solution)
    首先考虑(f(n))是多少。
    随便找一些排列比如题解中的这个:({1, 3, 2, 6, 5, 4, 10, 9, 8, 7, 15, 14, 13, 12, 11})(k=5),能发现(k)是最大的满足(frac{i(i+1)}{2}leq n)的正整数(i)
    所以我们猜(f(n)=k),其中(k)是最大的满足(frac{k(k+1)}{2}leq n)的正整数。
    事实上确实可以证明。也就是对于任意(n<frac{k'(k'+1)}{2}),总能将序列分成不超过(k'-1)个单调子序列。

    (LIS)为此时的最长上升子序列,若(|LIS|geq k'),删掉(LIS),则此时(n-|LIS|<frac{k'(k'+1)}{2}-k'=frac{(k'-1)k'}{2}),显然是一个同样的规模更小的问题。
    否则(|LIS|leq k'-1),由(Dilworth)定理,(最小链覆盖=最长反链),本题中就是(下降子序列个数=最长上升子序列长度leq k'-1)。(对于(i<j,A_i>A_j),连单向边(i o j),则最长反链即(LIS),任意一条路径即下降子序列)
    所以此时可以将序列分成不超过(k'-1)个下降子序列。

    证完了。。也是够神。。不知道别的可能的做法,懒得看了。。

    输出(|LIS|)个下降子序列时,令(f[i])表示以(i)结尾的(LIS)长度,把(f[i])相同的位置划分到同一个子序列中就可以了。(显然这些数是递减的)
    复杂度(O(nsqrt{n}log n))(需要(O(sqrt{n}))(LIS))。

    //358ms	3600KB
    #include <cmath>
    #include <cstdio>
    #include <cctype>
    #include <vector>
    #include <cstring>
    #include <algorithm>
    #define gc() getchar()
    typedef long long LL;
    const int N=1e5+5;
    
    int tot,A[N],f[N];
    std::vector<int> ans[N];
    struct BIT
    {
    	int n,t[N];
    	#define lb(x) (x&-x)
    	inline int Query(int p)
    	{
    		int res=0;
    		for(; p; p^=lb(p)) res=std::max(res,t[p]);
    		return res;
    	}
    	inline void Modify(int p,int v)
    	{
    		for(; p<=n; p+=lb(p)) t[p]=std::max(t[p],v);
    	}
    	inline void Clear(int p)
    	{
    		for(; p<=n; p+=lb(p))
    			if(!t[p]) break;
    			else t[p]=0;
    	}
    }T;
    
    inline int read()
    {
    	int now=0;register char c=gc();
    	for(;!isdigit(c);c=gc());
    	for(;isdigit(c);now=now*10+c-48,c=gc());
    	return now;
    }
    void Solve(int n)
    {
    	static bool vis[N];
    	int mx=0;
    	for(int i=1; i<=n; ++i) mx=std::max(mx,f[i]=T.Query(A[i]-1)+1), T.Modify(A[i],f[i]);
    	for(int i=1; i<=n; ++i) T.Clear(A[i]);
    	int l=1,r=sqrt(n*2),mid;
    	while(l<r)//k(k+1)/2<=n (k+1)(k+2)/2>n
    		if(mid=l+r>>1,mid*(mid+1)>n*2) r=mid;//可能会爆int。。但k的上界是sqrt(2n)。
    		else l=mid+1;
    	int k=l;
    	if(mx>=k)
    	{
    		++tot;
    		for(int i=n,now=mx,v=N; i; --i)
    			if(f[i]==now && A[i]<v)
    				--now, v=A[i], vis[i]=1, ans[tot].push_back(A[i]);
    		std::reverse(ans[tot].begin(),ans[tot].end());
    		int cnt=0;
    		for(int i=1; i<=n; ++i)
    			if(!vis[i]) A[++cnt]=A[i];
    			else vis[i]=0;
    		if(cnt) Solve(cnt);
    	}
    	else
    	{
    		for(int i=1; i<=n; ++i) ans[tot+f[i]].push_back(A[i]);
    		tot+=mx;
    	}
    }
    
    int main()
    {
    	for(int Tot=read(); Tot--; )
    	{
    		const int n=read(); T.n=n;
    		for(int i=1; i<=n; ++i) A[i]=read();
    		tot=0, Solve(n);
    		printf("%d
    ",tot);
    		for(int i=1; i<=tot; ++i,putchar('
    '))
    		{
    			printf("%d",ans[i].size());
    			for(int j=0,l=ans[i].size(); j<l; ++j) printf(" %d",ans[i][j]);
    			ans[i].clear();
    		}
    	}
    	return 0;
    }
    

    F.Alex and a TV Show(bitset)

    (Description)
    (n)个多重集合,初始都为空。有(q)次操作,操作共四种:
    (1 x v):将第(x)个集合赋值为({v})
    (2 x y z):把第(x)个集合设为第(y)个集合与第(z)个集合的并。
    (3 x y z):把第(x)个集合设为({gcd(a,b)|ain y,bin z})
    (4 x v):询问第(x)个集合中,数字(v)出现次数模(2)后的值。
    (nleq10^5, qleq10^6),出现的所有数字均为正整数且(leq7000)

    (Solution)
    先考虑第三个类似卷积的操作如何处理。题解用到了类似(FFT)的方法,将“卷积”转化成对应位置的“点值”分别进行运算。
    (x)位置处,不去维护数字(x)的出现次数,而是维护(x)所有倍数的出现次数。这样就可以对不同位置的点值分别维护“(gcd)卷积”啦。
    比如(y)集合中(x)的倍数有(a)个,(z)集合中(x)的倍数有(b)个,那么“卷积”之后(x)的倍数就有(a*b)个(而且这个(a,b)与其它位置的值互不影响)。
    具体实现,因为只需要判断奇偶性,拿一个大小为(7000)(bitset A_i)。操作二就是(A_y ^{wedge}A_z)(加),操作三就是(A_y&A_z)(乘)。
    但是查询的时候需要容斥:(Ans=A_x-A_{2x}-A_{3x}+A_{6x})...注意到容斥系数只有(mu=1)(-1),而(1equiv-1(mod 2)),所以直接把(mu(i*x) eq0)的这些位置上的值加起来就好,也就是全与起来再模(2)
    这样每次操作的复杂度都是(O(frac{7000}{w}))

    //764ms	97900KB
    #include <cstdio>
    #include <cctype>
    #include <bitset>
    #include <algorithm>
    #define gc() getchar()
    typedef long long LL;
    const int N=1e5+5,M=7001;
    
    int mu[N];
    std::bitset<M> A[N],ori[M],Mu[M];
    
    inline int read()
    {
    	int now=0;register char c=gc();
    	for(;!isdigit(c);c=gc());
    	for(;isdigit(c);now=now*10+c-48,c=gc());
    	return now;
    }
    void Init()
    {
    	static int P[M>>3];
    	static bool notP[M];
    	mu[1]=1;
    	for(int i=2,cnt=0; i<M; ++i)
    	{
    		if(!notP[i]) P[++cnt]=i, mu[i]=-1;
    		for(int j=1,v; j<=cnt&&(v=P[j]*i)<M; ++j)
    		{
    			notP[v]=1;
    			if(!(i%P[j])) break;
    			mu[v]=-mu[i];
    		}
    	}
    }
    
    int main()
    {
    	Init();
    	for(int i=1; i<M; ++i)
    		for(int j=i; j<M; j+=i)
    			ori[j][i]=1, Mu[i][j]=(mu[j/i]!=0);
    
    	for(int n=read(),q=read(),x,y,z,v; q--; )
    		switch(read())
    		{
    			case 1: x=read(),v=read(),A[x]=ori[v]; break;
    			case 2: x=read(),y=read(),z=read(),A[x]=A[y]^A[z]; break;
    			case 3: x=read(),y=read(),z=read(),A[x]=A[y]&A[z]; break;
    			case 4: x=read(),v=read(),putchar(48+((A[x]&Mu[v]).count()&1)); break;
    		}
    	return 0;
    }
    

    G.Vladislav and a Great Legend(斯特林数 树形DP)

    (Description)
    给定一棵(n)个点的树和正整数(k),定义(f(s))表示使点集(s)中的点连通所需的最少边数。求(sum_{s eqvarnothing}f(s)^k)
    (nleq10^5, kleq200)

    (Solution)
    依旧套路,把(f(s)^k)用第二类斯特林数((m^n=sum_{k=0}^mC_m^kS(n,k)k!))展开:

    [egin{aligned}Ans&=sum_{s eqvarnothing}sum_{i=0}^kC_{f(s)}^iS(k,i)i!\&=sum_{i=0}^kS(k,i)i!sum_{s eqvarnothing}C_{f(s)}^iend{aligned} ]

    后面的(sum_{s eqvarnothing}C_{f(s)}^i)实际就是任选(i)条边,对应多少个点集。考虑树DP去算。
    (f[i][j])表示选中的所有点的(LCA)(i)时,从这些生成树中选了(j)条边对应的点集有多少个(生成树包括(i o fa[i])这条边,因为不在这个时候计算这条边好像不好做...表示不会)。
    每个点初始化(f[x][0]=2)(可以在也可以不在点集中)。合并子树的时候就是树形背包,(f'[x][i+j]=sum_{v=son[x]}f[x][i]*f[v][j])
    合并完子树的贡献后,再算上(x o fa[x])这条边的贡献(可能出现在边集中),即(f[x][i])+=(f[x][i-1])
    此时会有不合法的情况:(x)子树外没有选点,但选了(x o fa[x])这条边。
    减去合并完子树后时的(f)的贡献就行了,即(Ans_i)-=(f[x][i-1])
    还有要注意(f[x][0])是包括空集这一种情况的(不选任何点),但是又不能直接去掉(直接--(f[x][0])),组合的时候可能用到。
    所以在用(f[x][0])更新其它值时注意把这种情况去掉:(Ans_1)再加上这个(1),用(x o fa[x])的边更新后的(f[x][1])要减掉这个(1)

    还可以把(i!)乘进去,就成了维护下降幂。。不想看了。。

    //467ms	90200KB
    #include <cstdio>
    #include <cctype>
    #include <algorithm>
    #define mod 1000000007
    #define gc() getchar()
    #define Mod(x) x>=mod&&(x-=mod)
    #define Add(x,v) (x+=v)>=mod&&(x-=mod)
    typedef long long LL;
    const int N=1e5+5;
    
    int K,Enum,H[N],nxt[N<<1],to[N<<1],f[N][203],Ans[N];
    
    inline int read()
    {
    	int now=0;register char c=gc();
    	for(;!isdigit(c);c=gc());
    	for(;isdigit(c);now=now*10+c-48,c=gc());
    	return now;
    }
    inline void AE(int u,int v)
    {
    	to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum;
    	to[++Enum]=u, nxt[Enum]=H[v], H[v]=Enum;
    }
    void DFS(int x,int fa)
    {
    	static int sz[N],g[N];
    	f[x][0]=2, sz[x]=1;
    	for(int i=H[x],v; i; i=nxt[i])
    		if((v=to[i])!=fa)
    		{
    			DFS(v,x);
    			for(int a=0,l=std::min(sz[x]-1,K); a<=l; ++a)
    				for(int b=0; b<=sz[v]&&a+b<=K; ++b)//注意这里不是到sz[v]-1(还有v->fa[v]这条边)
    					Add(g[a+b],1ll*f[x][a]*f[v][b]%mod);
    			sz[x]+=sz[v];
    			for(int j=0,l=std::min(sz[x]-1,K); j<=l; ++j) f[x][j]=g[j], g[j]=0;
    		}
    	if(x!=1)
    	{
    		for(int i=1,l=std::min(sz[x],K); i<=l; ++i) Add(Ans[i],mod-f[x][i-1]);//注意这里也不是sz[x]-1。。(不写这个取min更保险一些)
    		++Ans[1];
    	}
    	else for(int i=0,l=std::min(sz[1]-1,K); i<=K; ++i) Add(Ans[i],f[1][i]);
    	for(int i=std::min(sz[x],K); i; --i) Add(f[x][i],f[x][i-1]);
    	Add(f[x][1],mod-1);
    }
    
    int main()
    {
    	static int S[203][203];
    	const int n=read(),K=read(); ::K=K;
    	for(int i=1; i<n; ++i) AE(read(),read());
    	DFS(1,1);
    	S[0][0]=1;
    	for(int i=1; i<=K; ++i)
    		for(int j=1; j<=i; ++j) S[i][j]=S[i-1][j-1]+1ll*S[i-1][j]*j%mod, Mod(S[i][j]);
    	LL ans=0;
    	for(int i=0,fac=1; i<=K; ++i) ans+=1ll*S[K][i]*fac%mod*Ans[i]%mod, fac=1ll*fac*(i+1)%mod;
    	printf("%I64d
    ",ans%mod);
    
    	return 0;
    }
    

    H.

    不想做了。。

    咕咕
    
  • 相关阅读:
    [hihoCoder] #1093 : 最短路径·三:SPFA算法
    [hihoCoder] #1089 : 最短路径·二:Floyd算法
    [LeetCode] Number of Islands
    PRML5-神经网络(1)
    CUDA2.4-原理之性能优化及浮点运算
    PRML1-引言
    PGM1.1-简介
    AI1.1-人工智能史
    数学-矩阵计算(4)两种布局
    数学-矩阵计算(2)矩阵函数微积分前奏
  • 原文地址:https://www.cnblogs.com/SovietPower/p/10349515.html
Copyright © 2011-2022 走看看