ARC104游记
ARC104游记
本人打的第一场 ARC ,还好没有太难看。
A Plus Minus
题意简述
给定 \(X+Y\) 和 \(X-Y\) ,求 \(X\) 和 \(Y\) 。
题目分析
\(X=((X+Y)+(X-Y))/2,Y=((X+Y)-(X-Y))/2\) 。
参考代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
int f;char c;
for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
static char q[64];int cnt=0;
if(!x)pc('0');if(x<0)pc('-'),x=-x;
while(x)q[cnt++]=x%10+'0',x/=10;
while(cnt--)pc(q[cnt]);
}
int main(){
int a,b;
read(a),read(b);
write((a+b)/2),pc(' '),write((a-b)/2);
return 0;
}
B DNA Sequence
题意简述
给定碱基序列 \(S\) ,询问 \(S\) 中有多少个子串 \(T\) 满足 \(T\) 重排后可以与重排前配对。
\(1\le |S|\le 5000\) 。
题目分析
\(T\) 重排后可以与重排前配对当前仅当 \(T\) 中 A
的数量等于 T
的数量、 C
的数量等于 G
的数量,枚举所有子串,然后前缀和差分或者动态维护求出 A
T
C
G
数量,直接判断即可。
参考代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
int f;char c;
for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
static char q[64];int cnt=0;
if(!x)pc('0');if(x<0)pc('-'),x=-x;
while(x)q[cnt++]=x%10+'0',x/=10;
while(cnt--)pc(q[cnt]);
}
const int maxn=5005;
char s[maxn];
int A[maxn],C[maxn],G[maxn],T[maxn];
int main(){
int n;read(n);
scanf("%s",s+1);
for(int i=1;i<=n;++i){
A[i]=A[i-1]+(s[i]=='A');
C[i]=C[i-1]+(s[i]=='C');
G[i]=G[i-1]+(s[i]=='G');
T[i]=T[i-1]+(s[i]=='T');
}
int ans=0;
for(int i=1;i<=n;++i){
for(int j=i;j<=n;++j){
if(A[j]-A[i-1]==T[j]-T[i-1]&&C[j]-C[i-1]==G[j]-G[i-1])
++ans;
}
}
write(ans),pc('\n');
return 0;
}
C Fair Elevator
题意简述
有一个长度为 \(2N\) 的序列 \(1,2,\dots,2N\) ,有 \(N\) 个配对关系 \(A_i\to B_i(A_i<B_i)\) ,满足每个 \(1\sim 2N\) 中的每个数只出现一次,并且如果 \(l_0,r_0\) 配对且 \(l_1,r_1\) 配对且 \(l_0<l_1<r_0\) 那么必须满足 \(r_0-l_0=r_1-l_1\) ,现在有些关系损坏了(即有一些 \(A_i\) 和 \(B_i\) 将不会给出),请问给出的配对关系是否可能合法(满足上述条件)。
\(1\le N\le 100\) 。
题目分析
假设存在配对关系 \(l\to r(l<r)\) ,并且没有配对关系跨越 \(l\) ,那么必然存在配对关系 \(l+1\to r+1,l+2\to r+2,\dots,r-1\to r+r-l-1\) ,也就是说这些配对关系构成了一个区间 \([l,r+r-l-1]\) ,由此可以得到启发:或许我们可以使用区间 dp 来解决这个问题。
设 \(dp(l,r)\) 表示仅考虑 \([l,r]\) 这个区间内的数两两之间的配对,是否可以使得这个区间内的数互相两两配对。
两种情况 \(dp(l,r)\) 是 true ,第一种情况就是 \([l,r]\) 中的配对关系恰好构成了这个区间,第二种情况就是这个区间可以通过某个分界点分成两个区间,而这两个区间都是 true 。
第一种情况直接暴力判断即可,第二种情况直接暴力枚举递归即可,时间复杂度 \(\mathcal O(n^3)\) ,细节可能有点多,注意特判(我居然在考场上一遍 A ,真的舒适)。
参考代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
int f;char c;
for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
static char q[64];int cnt=0;
if(!x)pc('0');if(x<0)pc('-'),x=-x;
while(x)q[cnt++]=x%10+'0',x/=10;
while(cnt--)pc(q[cnt]);
}
const int maxn=105;
int match[maxn*2],n;
int Match(int d,int u){
int re=true;
if(match[d]){
if(match[d]<=n*2)
re&=(match[d]==u);
else
re&=(match[d]==n*2+1);
}
if(match[u]){
if(match[u]<=n*2)
re&=(match[u]==d);
else
re&=(match[u]==n*2+2);
}
if(match[d]==n*2+1&&match[u]==n*2+2)
re=false;
return re;
}
// 0:no match 1-n*2:ma n*2+1:up n*2+2:down
int dp[maxn][maxn];
int solve(int l,int r){
if(~dp[l][r])return dp[l][r];
int len=r-l+1,re=true;
for(int i=l;i<=r&&re;++i)
re&=Match(l-1+i,l-1+i+len);
if(re)return dp[l][r]=true;
for(int x=l;x<r&&!re;++x)
re|=solve(l,x)&&solve(x+1,r);
return dp[l][r]=re;
}
int main(){
int ok=true;read(n);
for(int i=1;i<=n&&ok;++i){
int a,b;
read(a),read(b);
if(a!=-1&&b!=-1&&a>=b)ok=false;
else{
if(a==-1&&b==-1)continue;
else if(a==-1){
if(match[b])ok=false;
else match[b]=n*2+2;
}
else if(b==-1){
if(match[a])ok=false;
else match[a]=n*2+1;
}
else{
if(match[a]||match[b])ok=false;
else match[a]=b,match[b]=a;
}
}
}
memset(dp,-1,sizeof dp);
if(!ok)puts("No");
else puts(solve(1,n)?"Yes":"No");
return 0;
}
D Multiset Mean
题意简述
给定 \(N,K,M\) ,对于每个整数 \(1\le x\le N\) ,求出构造整数序列 \(\{a\}\) ,满足 \(a\) 的长度为 \(N\) ,并且对于任意的 \(1\le i\le N\) ,都满足 \(0\le a_i\le K\) ,且 \(\frac{\sum_{i=1}^na_i\times i}{\sum_{i=1}^na_i}=x\) (带权平均数为 \(x\) )对 \(M\) 取模的方案数。
\(1\le N,K\le 100,10^8\le M\le 10^9+9\) ,保证 \(M\) 为质数。
题目分析
平均数为 \(x\) 不太好算,考虑到:
令 \(f(n,m)\) 为构造长度为 \(n\) 的序列 \(\{a\},0\le a_i\le K\) 满足 \(m=\sum_{i=1}^na_i\times i\) 的方案数,然后递推计算,需要用到类似前缀和优化的方法,预处理时间复杂度是 \(\mathcal O(N^3K)\) 的。
然后枚举 \(x\) ,直接枚举 \(m\) ,找出令上面等式左边右边均为 \(m\) 的方案数,需要注意一点细节,比如 \(a_x\) 的取值,这部分的时间复杂度是 \(\mathcal O(N^3K)\) 的。
参考代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<assert.h>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
int f;char c;
for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
static char q[64];int cnt=0;
if(!x)pc('0');if(x<0)pc('-'),x=-x;
while(x)q[cnt++]=x%10+'0',x/=10;
while(cnt--)pc(q[cnt]);
}
const int maxn=105,maxs=510000;
int m;
int mo(const int x){
return x>=m?x-m:x;
}
int k;
int dp[maxn][maxs];
int main(){
int n;
read(n),read(k),read(m);
dp[0][0]=1;
for(int i=1,mx=0;i<=n;++i){
mx+=i*k;
dp[i][0]=1;
for(int j=0;j<i;++j)
dp[i][j]=dp[i-1][j];
for(int j=i;j<=mx;++j){
dp[i][j]=mo(dp[i][j-i]+dp[i-1][j]);
if(j>=i*(k+1))dp[i][j]=mo(m-dp[i-1][j-i*(k+1)]+dp[i][j]);
}
}
for(int i=1;i<=n;++i){
if(i==1||i==n)write(k),pc('\n');
else{
int l=i-1,r=n-i,ans=0;
int mx=min(l*(l+1)/2,r*(r+1)/2)*k;
for(int j=1;j<=mx;++j)
ans=mo(ans+1ll*dp[l][j]*dp[r][j]%m);
ans=1ll*ans*(k+1)%m;ans=mo(ans+k);
write(ans),pc('\n');
}
}
return 0;
}
E Random LIS
这题考场上没切,感觉这种 dp 方法以前都没有用过,所以有点不太会用。
题目简述
给定长度为 \(N\) 的序列 \(\{A\}\) ,有一个整数序列 \(\{a\}\) ,其中 \(a_i\) 在 \([1,A_i]\) 中均匀随机,求 \(a_i\) 最长严格递增子序列的期望长度,模数 \(10^9+7\) 。
\(1\le N\le 6,1\le A_i\le 10^9\) 。
题目分析
这个 \(N\) 怎么这么小啊?这个 \(A\) 怎么这么大啊?
考虑直接枚举所有可能的大小关系,也就是枚举有顺序集合划分 \(S_1,S_2,\dots,S_k\) ,满足 \(S_i\) 中的 \(a\) 两两相等,并且 \(S_i\) 中的 \(a\) 小于 \(S_{i+1}\) 中的 \(a\) (当 \(N=6\) 时,这个枚举次数为 \(4683\) ),然后求方案数,因为 \(S\) 都被枚举出来了,所以这个方案数对答案的贡献也就求出来了,接下来的问题就是求方案数了。
求方案数的时候把所有相等的 \(a\) 合并,其中的 \(A_i\) 去最小值,问题就转化成了:给定长度为 \(N\) 的序列 \(\{A\}\) ,有一个整数序列 \(\{a\}\) ,其中 \(a_i\) 在 \([1,A_i]\) 中均匀随机,求 \(a_i\) 是严格递增序列的方案数。
然后就变成了这道题目了:CF1295F;这道题目也有点像:[APIO2016]划艇。
在这里简单讲一下做法,值域这么大,显然要离散化,不妨规定离散化后的第 \(i\) 小的数和第 \(i+1\) 小的数组成的区间为第 \(i\) 个区间,离散化完后设 \(f_{i,j}\) 表示仅考虑序列的前 \(i\) 项,其中 \(a_i\) 的取值在离散化后的第 \(j\) 个区间内的方案数,转移就是枚举 \(a_i\) 前面有多少个也在第 \(j\) 个区间内。
具体转移如下(设第 \(j\) 个区间为 \(I_j\) ,区间长度为 \(L_j\) , \(a_k\) 的取值区间为 \(S_k\) ):
参考代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
int f;char c;
for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
static char q[64];int cnt=0;
if(!x)pc('0');if(x<0)pc('-'),x=-x;
while(x)q[cnt++]=x%10+'0',x/=10;
while(cnt--)pc(q[cnt]);
}
const int maxn=10,mod=1000000007;
int mo(const int x){
return x>=mod?x-mod:x;
}
int power(int a,int x){
int re=1;
while(x){
if(x&1)re=1ll*re*a%mod;
a=1ll*a*a%mod,x>>=1;
}
return re;
}
int inv[maxn],st[maxn],cp[maxn],tp,all[maxn],f[maxn],g[maxn];
int solve(void){
int cn=0;
for(int i=1;i<=tp;++i)
st[i]=cp[i];
for(int i=1;i<=tp;++i)
all[cn++]=++st[i],f[i]=0;
all[cn++]=1;sort(all,all+cn);
cn=unique(all,all+cn)-all;
for(int i=1;i<=tp;++i)
st[i]=lower_bound(all,all+cn,st[i])-all;
f[0]=1;
for(int i=0;i<cn-1;++i){
int len=all[i+1]-all[i];g[0]=1;
for(int j=1;j<=tp;++j)g[j]=1ll*g[j-1]*inv[j]%mod*(len-j+1)%mod;
for(int j=tp;j;--j)if(i<st[j]){
for(int k=j-1;~k;--k){
f[j]=mo(f[j]+1ll*f[k]*g[j-k]%mod);
if(i>=st[k])break;
}
}
}
return f[tp];
}
int a[maxn],id[maxn],n,ans,pos[maxn],mx[maxn];
void dfs(int no){
if(no==0){
int cnt=0;
for(int i=n;i>=1;--i){
mx[i]=1;
for(int j=i+1;j<=n;++j){
if(pos[i]==pos[j]||id[j]>=id[i])continue;
mx[i]=max(mx[i],mx[j]+1);
}
cnt=max(cnt,mx[i]);
}
return ans=mo(ans+1ll*cnt*solve()%mod),void();
}
int U=1<<no;++tp;
for(int s=1;s<U;++s){
cp[tp]=1000000001;
int up=no;
for(int i=no;i>=1;--i){
if((s>>(i-1))&1){
cp[tp]=min(cp[tp],a[id[i]]);
pos[up]=tp;swap(id[i],id[up--]);
}
}
dfs(up);
for(int i=1;i<=no;++i){
if((s>>(i-1))&1){
swap(id[i],id[++up]);
}
}
}
--tp;
}
int main(){
read(n);int sum=1;
for(int i=1;i<=n;++i)
read(a[i]),id[i]=i,inv[i]=(i==1?1:1ll*(mod-mod/i)*inv[mod%i]%mod),sum=1ll*sum*a[i]%mod;
dfs(n);write(1ll*ans*power(sum,mod-2)%mod);pc('\n');
return 0;
}
F Visibility Sequence
这题考场上也没有 A ,不过这题感觉还是比 E 要好想的,可惜我先做的 E ,然后就几乎没有想 F 了。
题意简述
对于一个长度为 \(N\) 的序列 \(\{H\}\) ,定义 \(\{H\}\) 的“特殊序列”为长度也为 \(N\) 的序列 \(\{P\}\) ,满足 \(P_i=\max\{\{j\mid j<i\&\& H_j>H_i\}\cup\{-1\}\}\) 。
现在给定一个长度为 \(N\) 的序列 \(\{X\}\) ,请问对于所有满足 \(1\le H_i\le X_i\) 的序列 \(\{H\}\) ,有多少个不同的“特殊序列”。
\(1\le N\le 100,1\le X_i\le 10^5\) 。
题目分析
说句闲话,这题 \(X_i\) 也可以开到 \(10^9\) 。
方便起见,不妨认为 \(H_0=\infty\) ,这样的话对于 \(1\sim N\) 的每一个数, \(P\) 都不为 \(-1\) 。
考虑在 \(i\) 与 \(P_i\) 中连一条边,那么我们必然会得到一棵树,而这棵树的根就是 \(0\) ,考虑 dfs 这棵树,显然我们能够找到一种方法使得 dfs 序从 \(1\) 到 \(N\) 递增,这个比较好理解,画张图就知道了。
一棵子树管辖的 dfn 序必然是一个区间,这也对应着原序列的一个区间,所以可以考虑使用区间 dp 来做,但是问题是, \(X_i\) 如何处理。
注意到这里的 \(P_i\) 我们求的是本质不同的个数,也就是说,对于相同的 \(P_i\) ,我们只需要找到一个满足特殊序列为 \(P\) 的构造方案即可,由于 \(X_i\) 的限制,所以我们要让这个构造方案给每个 \(i\) 分配的 \(H_i\) 尽可能小,这样就可以尽可能多地构造方案了。
如何分配尽可能小的 \(H\) ?对于某个节点 \(u\) 和它所有的儿子节点 \(\{v\}\) 和在它左边的第一个兄弟节点 \(w\) , \(H_u\ge\max(\max\{H_v\}+1,H_w)\) ,因为只有这样,才满足我们给它构造的树形结构,那么,我们不妨认为一个构造方案是合法的,当且仅当对于任意的 \(u\) ,都满足 \(H_u=\max(\max\{H_v\}+1,H_w)\) ,不难发现,合法的构造方案中的 \(H_i\) 最大为 \(N+1\) ,并且每一种合法的构造方案都对应着一种方案,并且没有重复。
由此我们可以得知,我们只用统计合法的构造方案即可,这样可以做到不重不漏,并且可以判断是否满足 \(H_i\le X_i\) 。
如何统计?设 \(f_{l,r,x}\) 表示 \([l,r]\) 作为一棵树,根为 \(l\) ,并且 \(H_l=x\) 的合法方案数,那么我们可以枚举断点,然后给 \(l\) 加一棵子树,由于增加的子树导致了 \(H_l\) 的改变:
还有一种情况,就是由于兄弟的 \(H\) 导致了添加子树的 \(H\) 增大:
注意一下后面的 \([X_i\ge x-1]\) ,因为要增大的前提是能够增大。
发现有 \(\sum_{y=1}^xf_{l,i-1,y}\) ,所以直接前缀和优化。
时间复杂度: \(\mathcal O(N^4)\) 。
参考代码
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
#define ch() getchar()
#define pc(x) putchar(x)
template<typename T>inline void read(T&x){
int f;char c;
for(f=1,c=ch();c<'0'||c>'9';c=ch())if(c=='-')f=-f;
for(x=0;c<='9'&&c>='0';c=ch())x=x*10+(c&15);x*=f;
}
template<typename T>inline void write(T x){
static char q[64];int cnt=0;
if(!x)pc('0');if(x<0)pc('-'),x=-x;
while(x)q[cnt++]=x%10+'0',x/=10;
while(cnt--)pc(q[cnt]);
}
const int maxn=105,mod=1000000007;
int mo(const int x){
return x>=mod?x-mod:x;
}
int f[maxn][maxn][maxn],g[maxn][maxn][maxn],a[maxn];
int main(){
int n;read(n);a[1]=++n;
for(int i=2;i<=n;++i)read(a[i]);
for(int i=1;i<=n;++i){
f[i][i][1]=1;
for(int j=1;j<=n;++j)
g[i][i][j]=mo(g[i][i][j-1]+f[i][i][j]);
}
for(int len=1;len<n;++len){
for(int l=1,r=l+len;r<=n;++l,++r){
for(int x=2;x<=n&&x<=a[l];++x){
for(int i=l+1;i<=r;++i){
f[l][r][x]=mo(f[l][r][x]+1ll*g[l][i-1][x]*f[i][r][x-1]%mod);
f[l][r][x]=mo(f[l][r][x]+1ll*f[l][i-1][x]*g[i][r][x-2]*(a[i]>=x-1)%mod);
}
}
for(int x=1;x<=n;++x)
g[l][r][x]=mo(g[l][r][x-1]+f[l][r][x]);
}
}
write(g[1][n][n]),pc('\n');
return 0;
}
总结
自己考试的时候还是太急躁了,一直都在想 E ,后面发现了类似的题目也是做不出来,只能干着急,就没有去想 F 了,真是一大失误。
不过总体成绩还是很好的。