• ARC104 Solution Set


    A. Plus Minus

    小学数学解二元一次方程组。

    /*
    他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
    DONT NEVER AROUND . //
    */
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    char buf[1<<21],*p1=buf,*p2=buf;
    #define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
    int read()
    {
    	int x=0,f=1;
    	char c=getchar();
    	while(c<'0' || c>'9')	f=(c=='-'?-1:f),c=getchar();
    	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
    	return x*f;
    }
    void write(int x)
    {
    	if(x<0)	x=-x,putchar('-');
    	if(x>9)	write(x/10);
    	putchar(x%10+'0');
    }
    const int MOD=998244353;
    inline int Add(int u,int v){return u+v>=MOD?u+v-MOD:u+v;}
    inline int Sub(int u,int v){return u-v>=0?u-v:u-v+MOD;}
    inline int Mul(int u,int v){return LL(u)*LL(v)%MOD;}
    int QuickPow(int x,int p)
    {
    	if(p<0)	p+=MOD-1;
    	int ans=1,base=x;
    	while(p)
    	{
    		if(p&1)	ans=Mul(ans,base);
    		base=Mul(base,base);
    		p>>=1;
    	}
    	return ans;
    }
    int a,b;
    int main(){
    	a=read(),b=read();
    	write((a+b)/2),putchar(' '),write(a-(a+b)/2);
    	return 0;
    }
    

    B. DNA Sequence

    用烂了的套路。你显然可以把 AGTC 看成 URDL 最后走回原点,哈哈了。

    发现 \(n\) 只有 \(5 \times 10^3\),你把 \(A\) 看作 \(1\)\(T\) 看作 \(-1\)\(G\) 看作 \(10^4\)\(C\) 看作 \(-10^4\)。合法的区间就是和为 \(0\) 的区间。

    虽然这个题这样就可以做到 \(O(n)\) 了,但是我偏要写 \(O(n^2)\)。哈哈。

    /*
    他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
    DONT NEVER AROUND . //
    */
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    char s[5005];
    int n,sum[5005];
    int main(){
    	scanf("%d",&n);
    	scanf("%s",s+1);
    	for(int i=1;i<=n;++i)
    	{
    		if(s[i]=='A')	sum[i]=sum[i-1]+1;
    		else if(s[i]=='T')	sum[i]=sum[i-1]-1;
    		else if(s[i]=='G')	sum[i]=sum[i-1]+10000;
    		else	sum[i]=sum[i-1]-10000;
    	}
    	int ans=0;
    	for(int i=0;i<=n;++i)	for(int j=i+1;j<=n;++j)	if(sum[i]==sum[j])	++ans;
    	printf("%d",ans);
    	return 0;
    }
    

    C. Fair Elevator

    我觉得做着很痛苦。咕咕。

    D. Multiset Mean

    传说中的题意比题目难的题目。

    下面分析复杂度时把 \(n,k\) 看作同阶。

    注意到你直接做怎么做都是 \(O(n^5)\) 的。考虑枚举位置 \(i\) 的答案,那么选择 \(i-1\) 就相当于选 \(-1\),选 \(i+1\) 相当于选 \(+1\)。以此类推。

    那么我们在 \([1,i-1]\) 里面选出一些数(权值对应 \(i-1,i-2,\cdots,1\)),相当于在 \([1,i-1]\) 里选出一些数,记它们的和为 \(p\),又在 \([i+1,n]\) 里选出一些数(权值对应 \(1,2,\cdots,n-i\)),相当于在 \([1,n-i]\) 里选出一些数,记他们的和为 \(q\)。那么 \(p=q\) 的时候才合法。

    这两个问题互相独立且相似,我们可以通过一次 DP 预处理,通过背包解决这个问题。时间复杂度 \(O(n^4)\)

    /*
    他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
    DONT NEVER AROUND . //
    */
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    char buf[1<<21],*p1=buf,*p2=buf;
    #define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
    int read()
    {
    	int x=0;
    	char c=getchar();
    	while(c<'0' || c>'9')	c=getchar();
    	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
    	return x;
    }
    void write(int x)
    {
    	if(x>9)	write(x/10);
    	putchar(x%10+'0');
    }
    int MOD;
    inline int Add(int u,int v){return u+v>=MOD?u+v-MOD:u+v;}
    inline int Sub(int u,int v){return u-v>=0?u-v:u-v+MOD;}
    inline int Mul(int u,int v){return LL(u)*LL(v)%MOD;}
    int QuickPow(int x,int p)
    {
    	if(p<0)	p+=MOD-1;
    	int ans=1,base=x;
    	while(p)
    	{
    		if(p&1)	ans=Mul(ans,base);
    		base=Mul(base,base);
    		p>>=1;
    	}
    	return ans;
    }
    int n,K,dp[105][505005];
    int main(){
    	n=read(),K=read(),MOD=read();
    	if(n==1)
    	{
    		write(K%MOD);
    		return 0;
    	}
    	dp[0][0]=1;
    	for(int i=1;i<=n;++i)	for(int j=0;j<=K;++j)	for(int k=i*j;k<=i*(i+1)/2*K;++k)	dp[i][k]=Add(dp[i][k],dp[i-1][k-i*j]);
    	for(int i=1;i<=n;++i)
    	{
    		int ans=0,p=i-1,q=n-i;
    		for(int j=1;j<=505000;++j)	ans=Add(ans,Mul(dp[p][j],dp[q][j]));
    		ans=Mul(ans,K+1);
    		ans=Add(ans,K);
    		write(ans),puts("");
    	}
    	return 0;
    }
    

    E. Random LIS

    不喜欢这个题。先咕了。

    F. Visibility Sequence

    可以将 \(P\) 抽象成一个树形结构,做法是在序列最左加上一个极大值,加边 \(i \to P_i\) 即可。那么一个结点通过 DFS 序可以抽象成一段区间,想到区间 DP。

    注意到我们要求的是 \(P\),跟实际高度没有任何关系,那么我们构出的树深度当然是越小越好。先考虑怎么构造一棵树使得深度最小。首先叶子显然为 \(1\)。然后要满足以下性质:

    • 父亲的高度严格大于儿子的高度;
    • 左边的兄弟不大于右边的兄弟的高度。

    记点 \(p\) 的第一个左兄弟为 \(q\),儿子集合为 \(S\),那么一个点的最小高度就是 \(\max(h_q,\max_{u\in S}\{h_u\})\)。显然这种构造方法可以让 \(H,P\) 唯一对应。

    那么只需要问有多少种合法构造方案。我们定义 \(dp_{l,r,h}\) 表示,结点 \(l\)\([l,r]\) 中所有点构成的结点的根,并且构成的树的最小深度为 \(h\) 的方案数。

    考虑转移,枚举断点 \(i\)。分类讨论:

    • 加入这棵子树,其深度为 \(h-1\),那么根节点被迫将高度提高到 \(h\)(显然这棵子树加入进去是合法的,不然的话之前的树的高度没有这么低),方案数为:

    \[dp_{l,r,h} = \sum_{i=l+1}^{r} \left(\sum_{x=1}^{h}dp_{l,i-1,x}\right) dp_{i,r,h-1} \]

    • 加入这棵子树,之前根节点已经有深度为 \(h-1\) 的子树,我们要将这棵子树的深度被迫提高,那么我们加入深度在 \(1 \sim h-1\) 的子树皆可。注意到其深度最后会变成 \(h-1\),要保证这个根 \(i\) 的最大高度要大于等于 \(h-1\)。但是因为加入 \(h-1\) 的子树会和上面的方案算重,所以我们加入的子树只到 \(h-2\)

    \[dp_{l,r,h} = \sum_{i=l+1}^{r} \left(\sum_{x=1}^{h-2}dp_{i,r,x}\right) dp_{l,i-1,h} [a_i \geq h-1] \]

    注意到里面的求和式可以用前缀和优化。至此我们用 \(O(n^4)\) 的算法解决了问题。

    /*
    他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
    DONT NEVER AROUND . //
    */
    #include<bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    char buf[1<<21],*p1=buf,*p2=buf;
    #define getchar() (p1==p2 && (p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
    int read()
    {
    	int x=0;
    	char c=getchar();
    	while(c<'0' || c>'9')	c=getchar();
    	while(c>='0' && c<='9')	x=(x<<1)+(x<<3)+(c^'0'),c=getchar();
    	return x;
    }
    void write(int x)
    {
    	if(x>9)	write(x/10);
    	putchar(x%10+'0');
    }
    const int MOD=1e9+7;
    inline int Add(int u,int v){return u+v>=MOD?u+v-MOD:u+v;}
    inline int Sub(int u,int v){return u-v>=0?u-v:u-v+MOD;}
    inline int Mul(int u,int v){return LL(u)*LL(v)%MOD;}
    int QuickPow(int x,int p)
    {
    	if(p<0)	p+=MOD-1;
    	int ans=1,base=x;
    	while(p)
    	{
    		if(p&1)	ans=Mul(ans,base);
    		base=Mul(base,base);
    		p>>=1;
    	}
    	return ans;
    }
    int n,a[105],dp[105][105][105],sum[105][105][105];
    int main(){
    	n=read();
    	a[1]=++n;
    	for(int i=2;i<=n;++i)	a[i]=read();
    	for(int i=n;i;--i)
    	{
    		dp[i][i][1]=1;
    		for(int j=1;j<=min(n,a[i]);++j)	sum[i][i][j]=1;
    		for(int j=i+1;j<=n;++j)
    		{
    			for(int k=2;k<=min(n,a[i]);++k)
    			{
    				int &cur=dp[i][j][k];
    				for(int l=i+1;l<=j;++l)
    				{
    					/*
    					 第一个:加入了一个比较高大的子树。 
    					*/
    					cur=Add(cur,Mul(sum[i][l-1][k-1],dp[l][j][k-1]));
    					/*
    					 第二个:加入了一个子树,这个子树要比左兄弟更大。 
    					*/
    					if(a[l]>=k-1)	cur=Add(cur,Mul(dp[i][l-1][k],sum[l][j][k-1]));
    				}
    				sum[i][j][k]=Add(sum[i][j][k-1],cur);
    			}
    		}
    	}
    	write(sum[1][n][n]);
    	return 0;
    }
    
  • 相关阅读:
    Mysql 存储引擎中InnoDB与Myisam的主要区别
    [转]memmove函数
    _Obj* __STL_VOLATILE* __my_free_list
    [转]STL的内存分配器
    [转载]C++ 堆与栈简单的介绍
    [转载]__type_traits
    [转载]C++中 引用&与取地址&的区别
    [转载]delete指针之后应该赋值NULL
    [转载]C++中声明与定义的区别
    学习笔记ubuntu/shell
  • 原文地址:https://www.cnblogs.com/amagaisite/p/15978590.html
Copyright © 2020-2023  润新知