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\)(显然这棵子树加入进去是合法的,不然的话之前的树的高度没有这么低),方案数为:
- 加入这棵子树,之前根节点已经有深度为 \(h-1\) 的子树,我们要将这棵子树的深度被迫提高,那么我们加入深度在 \(1 \sim h-1\) 的子树皆可。注意到其深度最后会变成 \(h-1\),要保证这个根 \(i\) 的最大高度要大于等于 \(h-1\)。但是因为加入 \(h-1\) 的子树会和上面的方案算重,所以我们加入的子树只到 \(h-2\):
注意到里面的求和式可以用前缀和优化。至此我们用 \(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;
}