ARC板刷计划

发布时间 2023-10-26 18:17:20作者: lsj2009

板刷自 ARC104 起所有 ARC 的 \(\text{C}\sim\text{E}\) 题。

ARC104

https://atcoder.jp/contests/arc104/tasks/arc104_c

ARC104C

首先观察性质。容易发现的是,如果两个人在电梯上的时间段有交,必然只会是如下可能:

也就是说所有人在电梯上的时间段只有可能是长这样子的:

考虑枚举每个图中所画的分割线,具体而言,设 \(f_i\) 表示前 \(i\) 个时刻是否有合法答案,那么可以得到转移方程:

\[f_i=\bigvee_{j=1}^{i} f_{j-1}\wedge\text{check}(j,i) \]

那么关键在于这个 check 函数怎么写。如果说要求这个区间内所有人均满足条件,那么需要符合以下要求:

  • 左端点在这个区间内的,右端点也必须要在这个区间内。
  • 右端点在这个区间内的,左端点也必须要在这个区间内。
  • 左端点必须在前半段。
  • 右端点必须在后半段。
  • 如果某个已知的区间在这个范围内,则其左端点相对于前半段和右端点相对于后半段位置应当是相等的。

然后就做完了,再判一下边界即可。

点击查看代码
#include<bits/stdc++.h>
//#define int long long
#define ll long long
#define ull unsigned long long
#define ld long double
#define PII pair<int,int>
#define INF 0x3f3f3f3f
#define INFLL 0x3f3f3f3f3f3f3f3f
#define chkmax(a,b) a=max(a,b)
#define chkmin(a,b) a=min(a,b)
#define rep(k,l,r) for(int k=l;k<=r;++k)
#define per(k,r,l) for(int k=r;k>=l;--k)
#define cl(f,x) memset(f,x,sizeof(f))
using namespace std;
const int N=2e2+5;
int p[N],cnt[N],l[N],r[N],n;
bool f[N];
bool check(int ql,int qr) {
    rep(i,ql,qr) {
        if(p[i]<0&&r[-p[i]]!=-1&&r[-p[i]]>qr)
            return false;
        if(p[i]>0&&l[p[i]]!=-1&&l[p[i]]<ql)
            return false;
    }
    int len=(qr-ql+1)>>1;
    rep(i,ql,ql+len-1) {
        if(p[i]>0)
            return false;
        if(p[i+len]<0)
            return false;
        if(p[i]!=0&&p[i+len]!=0&&p[i]+p[i+len]!=0)
            return false;
    }
    return true;
}
signed main() {
    scanf("%d",&n);
    rep(i,1,n) {
        scanf("%d%d",&l[i],&r[i]);
        if(l[i]!=-1&&r[i]!=-1&&l[i]>=r[i])
            return 0&puts("No");
        if(l[i]!=-1)
            p[l[i]]=-i,++cnt[l[i]];
        if(r[i]!=-1)
            p[r[i]]=i,++cnt[r[i]];
    }
	n<<=1;	
    rep(i,1,n) {
        if(cnt[i]>1)
            return 0&puts("No");
    }
    f[0]=true;
    rep(i,1,n) {
        for(int j=(i&1? 2:1);j<=i;j+=2)
            f[i]|=f[j-1]&check(j,i);
    }
    puts(f[n]? "Yes":"No");
    return 0;
}

ARC104D

应该算是比较套路的题。

遇到平均数,我们考虑将所有数字全部减掉 \(x\),则平均数 \(=x\) 就变成了和为 \(0\)。我们定义一个状态 \(f_{i,j}\) 表示考虑只使用第 \(1\sim i\) 这几个数,每个数至多用 \(k\) 次,使得和为 \(j\) 的方案总数。

首先考虑这个东西有什么用。观察一下减 \(x\) 之后得到的数:

\[-(x-1),-(x-2),\cdots,-1,0,1,2,\cdots (n-x) \]

那么如何让这几个数和 \(=0\)?考虑在 \([-(x-1),-1]\) 中选几个数,在 \([1,n-x]\) 选几个数,使得选出来的数的和互为相反数,然后再选若干个 \(0\),那么可以得到平均数 \(=x\) 时的答案:

\[ans=\sum\limits_j f_{x-1,j}\cdot f_{n-x,j}\cdot(k+1)-1 \]

其中最后一个 \(-1\) 是代表什么都不选的情况。

然后再考虑 \(f_{i,j}\) 怎么求。这一个是个典题(?,可以直接容斥做到 \(\Theta(n^2k)\)

点击查看代码
#include<bits/stdc++.h>
//#define int long long
#define ll long long
#define ull unsigned long long
#define ld long double
#define PII pair<int,int>
#define INF 0x3f3f3f3f
#define INFLL 0x3f3f3f3f3f3f3f3f
#define chkmax(a,b) a=max(a,b)
#define chkmin(a,b) a=min(a,b)
#define rep(k,l,r) for(int k=l;k<=r;++k)
#define per(k,r,l) for(int k=r;k>=l;--k)
#define cl(f,x) memset(f,x,sizeof(f))
using namespace std;
const int N=1e2+5,M=1e6+5;
int f[N][M],n,k,MOD;
void add(int &a,int b) {
    a+=b;
    if(a>=MOD)
        a-=MOD;
}
void sub(int &a,int b) {
    a-=b;
    if(a<0)
        a+=MOD;
}
signed main() {
    scanf("%d%d%d",&n,&k,&MOD);
    f[0][0]=1;
    int sum=0;
    rep(i,1,n) {
        rep(j,0,sum)
            f[i][j]=f[i-1][j];
        sum+=i*k;
        rep(j,i,sum)
            add(f[i][j],f[i][j-i]);
        per(j,sum,i*(k+1))
            sub(f[i][j],f[i][j-i*(k+1)]);
    }
    rep(i,1,n) {
        int res=0;
        rep(j,0,sum)
            add(res,1ll*f[i-1][j]*f[n-i][j]%MOD*(k+1)%MOD);
        printf("%d\n",(res-1+MOD)%MOD);
    }
    return 0;
}

ARC104E

说实话感觉这个题质量不是很高,不如与它相似的 P3643 和 CF1295F。

看到 \(n\le 6\),这几乎所有和值域无关的做法都能跑过去,所以给了我们充足的乱搞空间。

首先考虑暴力钦定每个数的相对大小 \(\{rk_n\}\),需要 \(\Theta(n^n)\) 的时间(因为可能有数相等,所以不是 \(\Theta(n!)\))。

然后丢与每个下标,我们取排名等于他的最小值。即 \(a_i=\min\limits_{rk_j=i} A_j\),然后 \(m=\max\limits_{i=1}^n rk_i\)

那么我们就把问题转换成了:

求有多少个序列 \(\{b_m\}\) 满足:

  1. \(b_i\in[1,a_i]\)
  2. \(\forall i\in[1,m-1]\) 满足 \(b_i<b_{i+1}\)

然后这个东西就和 CF1295F 基本上一样啊,只不过前者要求严格单调,后者要求非严格单调。简单来说就是,先把 \(a_i\) 离散化出来,变成若干个值域段,设 \(f_{i,j}\) 为对于前 \(i\) 个数而言,第 \(i\) 个 数在第 \(j\) 段的方案总数。

那么我们考虑去枚举一个 \(k\),让前 \(k-1\) 个数在第 \(1\sim j-1\) 段;第 \(k\sim i\) 个数在第 \(j\) 段。而前 \(k-1\) 个数在第 \(1\sim j-1\) 段的方案总数即为 \(\sum\limits_{x=1}^{j-1} f_{k-1,x}\);第 \(k\sim i\) 个数在第 \(j\) 段的方案总数为 \(len_j \choose i-k+1\)。当然还需要判断的是第 \(k\sim i\) 个是否都可以取到第 \(j\) 段。

对于这个组合数的计算,虽然 \(len_j\) 很大,但是 \(i-k+1\) 确很小,所以每次可以直接 \(\Theta(n)\) 去暴力算。

所以得到方程:

\[f_{i,j}=\sum\limits_{k=1}^i {len_j \choose i-k+1}\sum\limits_{x=1}^{j-1} f_{k-1,x} \]

最终答案即为 \(\sum\limits_{i=1}^{a_m} f_{m,i}\),由于本题数据范围非常小,所以也不需要 P3643 的前缀和优化,直接 \(\Theta(n^5)\) 暴力 dp 即可,最终复杂度是 \(\Theta(n^{n+5})\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define ll long long
#define ull unsigned long long
#define ld long double
#define PII pair<int,int>
#define INF 0x3f3f3f3f
#define INFLL 0x3f3f3f3f3f3f3f3f
#define chkmax(a,b) a=max(a,b)
#define chkmin(a,b) a=min(a,b)
#define rep(k,l,r) for(int k=l;k<=r;++k)
#define per(k,r,l) for(int k=r;k>=l;--k)
#define cl(f,x) memset(f,x,sizeof(f))
using namespace std;
const int N=10,MOD=1e9+7;
int qpow(int a,int b) {
	int res=1,base=a;
	while(b) {
		if(b&1)
			res=res*base%MOD;
		base=base*base%MOD; b>>=1;
	}
	return res;
}
int t[N],len;
int get_id(int x) {
	return lower_bound(t+1,t+len+1,x)-t;
}
void add(int &a,int b) {
	a+=b;
	if(a>=MOD)
		a-=MOD;
}
int C(int n,int m) {
	if(n>m)
		return 0;
	if(n>m-n)
		n=m-n;
	int res=1;
	rep(i,1,n)
		res=res*(m-i+1)%MOD*qpow(i,MOD-2)%MOD;
	return res;
}
int b[N],s[N],g[N],a[N],f[N][N],ans,n;
int solve() {
    cl(a,0x3f);
	int m=0;
    rep(i,1,n) {
        chkmin(a[s[i]],b[i]);
        chkmax(m,s[i]);
    }
	t[++len]=0;
	rep(i,1,m) 
		t[++len]=a[i];
	sort(t+1,t+len+1);
	len=unique(t+1,t+len+1)-t-1;
	rep(i,1,m)
		a[i]=get_id(a[i]);
    cl(f,0);
	f[0][1]=1;
	rep(i,1,m) {
		rep(j,2,a[i]) {
			int res=INFLL;
			per(k,i,1) {
				chkmin(res,a[k]);
				if(j>res)
					break;
				rep(x,1,j-1)
					add(f[i][j],C(i-k+1,t[j]-t[j-1])*f[k-1][x]%MOD);
			}
		}
	}
	int ans=0;
	rep(i,2,len)
		add(ans,f[m][i]);
	return ans;
}
int c[N];
bool check() {
	rep(i,1,n)
		c[i]=s[i];
	sort(c+1,c+n+1);
	rep(i,1,n) {
		if(c[i]-c[i-1]>1)
			return false;
	}
	return true;
}
void dfs(int x) {
    if(x==n+1) {
        if(!check())
			return;
        int res=0;
		cl(g,0);
        rep(i,1,n) {
            rep(j,0,i-1) {
                if(s[i]>s[j])
                    chkmax(g[i],g[j]+1);
            }
            chkmax(res,g[i]);
        }
        add(ans,res*solve()%MOD);
        return;
    }
    rep(i,1,n) {
        s[x]=i; dfs(x+1);
    }
}
signed main() {
    scanf("%lld",&n);
    rep(i,1,n)
        scanf("%lld",&b[i]);
    dfs(1);
    rep(i,1,n)
        ans=ans*qpow(b[i],MOD-2)%MOD;
    printf("%lld\n",ans);
    return 0;
}