Atcoder Regular Contest 166

发布时间 2023-10-09 17:51:15作者: 樱雪喵

只打了半场。

A. Replace C or Swap AB

首先如果存在某个 \(i\),使得 \(Y_i\)C\(X_i\) 不是,那么显然是不合法的,可以直接判掉。
那么除去上述情况 \(Y\) 中为字符 C 的位置 \(X\) 也只能是 C。它们把字符串分成了若干段,可以把每一段分开单独考虑。
对于只含 A/B 的一段 \(Y\),我们可以根据个数得出 \(X\) 在这段中应该把多少 C 替换成 A,剩下的换成 B
发现题意只能从 AB 换成 BA,等价于把某个 A 向后移一个位置。
那我们希望我们放置的 A 尽量靠前,使最后能成功匹配的概率最大。因此根据已知数量,贪心地把靠前的 C 都替换成 A
判断当前 \(X\) 中是否每个对应的 A 所在位置都不大于它在 \(Y\) 中的目标位置即可。

Code
const int N=2e5+5;
int T,n;
char s[N],t[N];
vector<int> x,y;
bool solve()
{
	n=read();
	scanf("%s%s",s+1,t+1);
	int lst=0,cnt=0;
	s[n+1]=t[n+1]='C'; 
	for(int i=1;i<=n+1;i++)
	{
		if(t[i]=='C')
		{
			if(s[i]!='C') return 0;
			int cnta=0,cntb=0;
			int sa=0,sb=0;
			x.clear(),y.clear();
			for(int j=lst+1;j<i;j++) 
			{	
				if(t[j]=='A') y.push_back(j);
				if(t[j]=='A') cnta++; else cntb++;
				if(s[j]=='A') sa++;
				else if(s[j]=='B') sb++;
				if(t[j]=='C'&&t[i]!='C') return 0; 
			}
			if(sa>cnta||sb>cntb) return 0;
			for(int j=lst+1;j<i;j++)
			{
				if(s[j]!='C')
				{
					if(s[j]=='A') x.push_back(j);
					continue;
				}
				if(sa<cnta) sa++,x.push_back(j);
			}
			for(int j=0;j<x.size();j++) if(x[j]>y[j]) return 0;
			lst=i;
		}
	}
	return 1;
}
int main()
{
	T=read();
	while(T--)
	{
		int res=solve();
		if(res) printf("Yes\n");
		else printf("No\n");
	}
	return 0;
}

B. Make Multiples

Solution 1

容易知道令 \(x\) 成为 \(y\) 的倍数,所需的最小操作次数为 \((y-(x\bmod y))\bmod y\)
我们分别令 \(x=a,b,c,ab,bc,abc\),求每个数成为 \(x\) 的倍数所需的最小次数,并升序排序。那么当前 \(x\) 下只有操作次数最小的三个 \(a_i\) 可能被计入答案。
这样我们一共获得了至多 \(X=3\times 6=18\)\(a_i\),暴力在它们之间 \(O(X^3)\) 枚举哪三个数成为 \(a,b,c\) 的倍数,统计最小值即可。

Solution 2

考虑状压。设 \(f_{i,j}\) 表示考虑了前 \(i\) 个数,已经存在倍数的数为集合 \(j(j\in[0,2^3))\)
枚举当前数要修改成哪些数的倍数,那么有:

\[f_{i,j\cup k}\gets f_{i-1,j}+w(i,k) \]

直接转移,令 \(M=3\),时间复杂度 \(O(n 2^{2M})\)

Code
#define int long long
const int N=2e5+5;
int n,a[N],vis[N];
struct node{int id,x;} c[N];
int b[3],L[N];
vector<int> d;
il bool cmp(node x,node y) {return x.x<y.x;}
il int lcm(int a,int b) {return a*b/__gcd(a,b);} 
signed main()
{
	n=read(); 
	for(int i=0;i<3;i++) b[i]=read();
	for(int i=1;i<=n;i++) a[i]=read();
	for(int s=1;s<8;s++)
	{
		int lc=1;
		for(int i=0;i<3;i++) if((s>>i)&1) lc=lcm(lc,b[i]);
		for(int i=1;i<=n;i++) c[i].id=i,c[i].x=a[i]%lc?lc-a[i]%lc:0;
		sort(c+1,c+n+1,cmp);
		for(int i=1;i<=min(n,3ll);i++) 
			if(!vis[c[i].id]) vis[c[i].id]=1,d.push_back(a[c[i].id]);
	}
	int ans=3e18;
	for(int i=0;i<d.size();i++)
	{
		for(int j=0;j<d.size();j++)
		{
			for(int k=0;k<d.size();k++)
			{
				int res=0;
				for(int l=0;l<d.size();l++) L[l]=1;
				L[i]=lcm(L[i],b[0]),L[j]=lcm(L[j],b[1]),L[k]=lcm(L[k],b[2]);
				for(int l=0;l<d.size();l++) res+=d[l]%L[l]?L[l]-d[l]%L[l]:0;
				ans=min(ans,res);
			}
		}
	}
	printf("%lld\n",ans);
	return 0;
}

C. LU / RD Marking

把少打的半场时间补上大概能过。

Description

给一个 \(n\)\(m\) 列的网格,那么它的所有网格线上共有 \(n(m+1)\) 条竖边,\((n+1)m\) 条横边。

有如下两种操作:

  • 选一个上面和左面的网格线都没被涂黑的格子,并涂黑这两条线;
  • 选一个下面和右面的网格线都没被涂黑的格子,并涂黑这两条线。

求执行两种操作若干次(可以为 \(0\)),可能得到不同的涂黑边集数量。

\(T\le 2\times 10^5,\ n,m\le 10^6\)

Solution

在网格线上考虑问题,发现操作一次只会对该折线左下 / 右上相邻的折线产生影响。也就是说,我们把网格线斜向拆成若干条连续的折线,它们之间贡献独立。借用一下官方题解的图:

想到这一步就很好做了。

现在我们要求的就是对于每一条折线,在上面选若干长度为 \(2\) 的区间,且两两之间不能有交的方案数。

考虑 dp,设 \(f_n\) 表示长度为 \(n\) 的折线的答案。那么如果不选 \([n-1,n]\) 这条折线,\([n-2,n-1]\) 及以前的都可以选,方案数为 \(f_{n-1}\);否则只能选 \([n-3,n-2]\) 及以前的,方案数为 \(f_{n-2}\)。发现是斐波那契,预处理一下就好了。

统计一对 \((n,m)\) 的答案,这里设 \(n<m\),不满足就 \(\text{swap}\) 一下。
那么分成从横/竖边出发,把每条折线的贡献乘起来,则答案为

\[ans=(f_n)^{m-n}\times \prod_{i=1}^n (f_{2i-1})^2 \]

前面一半快速幂,后面一半预处理。

点击查看代码
#define int long long
const int N=2e6+5,mod=998244353;
int T,n,m;
int f[N],sum[N];
void init(int mx)
{
    f[0]=1,f[1]=2;
    for(int i=2;i<=mx;i++) f[i]=(f[i-1]+f[i-2])%mod;
    sum[0]=1;
    for(int i=1;i<=(mx>>1);i++) sum[i]=sum[i-1]*f[2*i-1]%mod*f[2*i-1]%mod;
}
il int qpow(int n,int k)
{
    int res=1;
    for(;k;n=n*n%mod,k>>=1) if(k&1) res=res*n%mod;
    return res;
}
signed main()
{
    init(2e6);  
    T=read();
    while(T--)
    {
        n=read(),m=read();
        if(n>m) swap(n,m);
        int res=sum[n];
        res=res*qpow(f[2*n],m-n)%mod;
        printf("%lld\n",res);
    }
    return 0;
}

D. Interval Counts

这不比 C 简单。

Description

给定正整数 \(n\) 和长度为 \(n\) 的序列 \(x_i,y_i\),保证 \(x_i\) 单调递增。你要构造 \(m\) 个区间 \([L_i,R_i]\)\(m\) 由你指定),使每个 \(x_i\) 恰好被 \(y_i\) 个区间包含。

求所有构造方案中 \(\min_{i=1}^m \{R_i-L_i\}\) 的最大值。

\(N\le 2\times 10^5,\ 1\le x_i,y_i\le 10^9\)

Solution

看到最小值最大试图进行二分,然而不太可做。因此考虑直接贪心构造。

\(x_i\) 从小到大的顺序依次构造区间。
假设当前构造的区间已经满足 \(x_1,\dots,x_{i-1}\) 的限制。那么根据当前 \(y_i\)\(y_{i-1}\) 的大小关系分为三种情况。

  • \(y_i=y_{i-1}\) 是容易处理的,因为我们什么也不用做,把上一次的区间都延长过来就可以。

  • \(y_i<y_{i-1}\),我们不能把所有未确定右端点的区间都接过来,必须要断掉恰好 \(d=y_{i-1}-y_i\) 条边。根据想让最短区间最长的目标,断掉左端点前 \(d\) 小的线段是最优的。所以把左端点前 \(d\) 小的线段右端点设为 \(x_i-1\),把它们移出未确定右端点的区间集合并更新答案。

  • \(y_i>y_{i-1}\),那么缺 \(d=y_i-y_{i-1}\) 个区间。令它们左端点最小,则新增 \(d\) 个左端点为 \(x_{i-1}+1\),右端点未确定的区间,加入集合。

由于 \(y\le 10^9\)\(m\) 很大,暴力维护所有左端点不可行。但发现每次我们加入集合的左端点都单调递增,用一个队列存储二元组 \((x,y)\) 表示插入了 \(y\) 条左端点为 \(x\) 的区间。删除的时候弹队首即可,时间复杂度 \(O(n)\)

点击查看代码
const int N=2e5+5,inf=1e9;
int n,x[N],y[N];
pii q[N];
int st=1,ed=0,ans=inf;
int main()
{
    n=read();
    for(int i=1;i<=n;i++) x[i]=read();
    for(int i=1;i<=n;i++) y[i]=read();
    x[0]=-inf;
    for(int i=1;i<=n;i++)
    {
        if(y[i]==y[i-1]) continue;
        if(y[i]>y[i-1]) q[++ed]=pii(x[i-1],y[i]-y[i-1]);
        else
        {
            int now=y[i-1]-y[i];
            while(st<=ed&&q[st].se<=now)
            {
                ans=min(ans,x[i]-q[st].fi-1);
                now-=q[st].se,st++;
            }
            if(st<=ed&&now&&q[st].se>now) q[st].se-=now,ans=min(ans,x[i]-q[st].fi-1);
        }
    }
    if(ans==inf) printf("-1\n");
    else printf("%d\n",ans-1);
    return 0;
}