#19 CF713E & CF1081G & CF794G

Sonya Partymaker

题目描述

点此看题

解法

可以二分答案 \(x\),那么就只需要判断每个人朝着特定方向走 \(x\) 步是否能覆盖完整个环。

先考虑链的情况怎么做,很容易联系到 lanterns 那题,然后 \(\tt DNA\) 就来反应了。设 \(dp[i]\) 表示考虑前 \(i\) 个点能覆盖到的最远前缀是多少,转移讨论点 \(i\) 向左覆盖还是向右覆盖:

  • 如果 \(dp[i-1]\geq b[i]\),则 \(i\) 可以向右覆盖,\(dp[i]=b[i]+x\)
  • 如果 \(dp[i-1]\geq b[i]-x-1\),则 \(i\) 向左覆盖之后前缀是可以到 \(b[i]\) 的;
  • 还要考虑这个覆盖对前面状态的影响,由于线段长度都是 \(x\),我们只需要看看 \(i-1\) 能不能向右倒,那么如果 \(dp[i-2]\geq b[i]-x-1\),则 \(i\) 向左覆盖,\(i-1\) 向右覆盖,前缀可以到达 \(b[i-1]+x\)

上面的部分都是很容易想到的,对环的处理方式才是本题的重点。

我们旋转环,最大化 \(n\rightarrow 1\) 之间的距离,由于答案小于最大距离,所以关键的环边 \(n\rightarrow 1\),它们是不能相互覆盖的。这就大大简化了问题,现在我们再简单讨论 \(1\) 的状态即可转化成链的问题(令 \(b[1]=0\)):

  • \(1\) 向左倒,\(dp[1]=0\) 来跑链,如果 \(dp[n]\geq m-1-x\) 则合法。
  • \(1\) 向右倒,那么 \(2\) 就必须要向左倒,所以 \(dp[1]=\min(b[2],x)\),如果 \(dp[n]\geq\min(m-1,m-1+b[2]-x)\) 则合法。

所以每次检查的时候跑两遍 \(dp\) 即可,时间复杂度 \(O(n\log n)\)

总结

处理环形 \(dp\) 问题时,先考虑链如何做。通过断开环边可以将问题转化到链的情况,这一步的关键是:断开哪一条环边(我们想要环边具有怎样的特殊性质);断开环边局部的状态是怎样的(简单讨论可以解决)

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 200005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int m,n,a[M],b[M],dp[M];
int check(int x)
{
	//1->left
	dp[1]=0;
	for(int i=2;i<=n;i++)
	{
		dp[i]=dp[i-1];
		//i->right
		if(dp[i-1]>=b[i]-1)
			dp[i]=max(dp[i],b[i]+x);
		//i->left
		if(dp[i-1]>=b[i]-x-1)
			dp[i]=max(dp[i],b[i]);
		//i->left and relieve i-1
		if(i>2 && dp[i-2]>=b[i]-x-1)
			dp[i]=max(dp[i],b[i-1]+x);
	}
	if(dp[n]>=m-1-x) return 1;
	//1->right
	dp[2]=max(b[2],x);
	for(int i=3;i<=n;i++)
	{
		dp[i]=dp[i-1];
		if(dp[i-1]>=b[i]-1)
			dp[i]=max(dp[i],b[i]+x);
		if(dp[i-1]>=b[i]-x-1)
			dp[i]=max(dp[i],b[i]);
		if(dp[i-2]>=b[i]-x-1)
			dp[i]=max(dp[i],b[i-1]+x);
	}
	return dp[n]>=min(m-1,m-1+b[2]-x);
}
signed main()
{
	m=read();n=read();
	for(int i=1;i<=n;i++)
		a[i]=read(),a[i+n]=a[i]+m;
	if(m==1) {printf("%d\n",m-1);return 0;}
	int p=1;
	for(int i=2;i<=n;i++)
		if(a[i+1]-a[i]>a[p+1]-a[p]) p=i;
	int l=0,r=a[p+1]-a[p]-1,ans=0;
	for(int i=1;i<=n;i++) b[i]=a[i+p]-a[p+1];
	while(l<=r)
	{
		int mid=(l+r)>>1;
		if(check(mid)) ans=mid,r=mid-1;
		else l=mid+1;
	}
	printf("%d\n",ans);
}

Mergesort Strikes Back

题目描述

点此看题

解法

从归并排序的底层开始思考,它会将序列划分成 \(c=2^{k-1}\) 个段,每个段的长度要不然是 \(d=\lfloor\frac{n}{c}\rfloor\),要不然是 \(d+1\)

首先考虑段内的逆序对贡献,归并排序不影响段内的相对顺序,所以长为 \(L\) 的段的期望逆序对数是 \(\frac{L(L-1)}{4}\)

然后考虑段间的逆序对贡献,设点 \(i\) 的值是 \(a_i\)(不确定),在段中的位置是 \(p_i\)(确定的),神奇的操作是:如果 \(a_i\) 是段中的前缀最大值,那么把 \(i\)\(i-1\) 切开,段会被划分成若干个小段

此时我们再研究归并排序的效果,发现如果小段的第一个元素(小段头)被放入合并数组之后,小段的其他元素会立即被放入合并数组,这说明每个小段都是作为整体来排序的,归并的效果只是按照小段头的大小关系把小段排序

那么考虑两个不同段中的点 \(i,j\),它们产生逆序对的条件是:

  • \(a_i>a_j\)
  • \(i\) 的小段头 \(<\) \(j\) 的小段头。

第二个条件可以转化成:一共有 \(p_i+p_j\) 个数,要求最大值出现在特定的 \(p_j-1\) 个数中,那么概率是 \(\frac{p_j-1}{p_i+p_j}\),结合第一个条件,概率就是 \(\frac{p_j-1}{2(p_i+p_j)}\);如果我们考虑的是无序的 \((i,j)\),那么概率是 \(\frac{p_i+p_j-2}{2(p_i+p_j)}\)

所以我们枚举 \(p_i+p_j\),然后分三类计算即可:\(p_i=p_j=d+1\);其中之一是 \(d+1\)\(p_i,p_j\leq d\),时间复杂度 \(O(n)\)

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1000005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,k,c,d,r,MOD,ans,inv[M];
void add(int &x,int y) {x=(x+y)%MOD;}
int f(int x) {return min(x-1,d)-max(1ll,x-d)+1;}
signed main()
{
	n=read();k=read();MOD=read();
	inv[0]=inv[1]=c=1;
	for(int i=2;i<=1e6;i++)
		inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<k;i++) c=min(n,c*2);
	d=n/c;r=n%c;
	//inside the seg
	ans=(d*(d-1)*(c-r)+(d+1)*d*r)*inv[4]%MOD;
	//d+1 - d+1
	add(ans,r*(r-1)*d%MOD*inv[4*d+4]);
	//[1,d] - [1,d]
	for(int i=2;i<=2*d;i++)
		add(ans,c*(c-1)*(i-2)%MOD*f(i)%MOD*inv[4*i]);
	//[1,d] - d+1
	for(int i=d+2;i<=2*d+1;i++)
		add(ans,(c-1)*r*(i-2)%MOD*inv[2*i]);
	printf("%lld\n",ans);
}

Replace All

题目描述

点此看题

解法

结论自己看出来了,虽然推式子的能力还是有待提升,但能有点思路已经很开心了

首先考虑没有问号怎么做,特判掉对应位置 A/B 相等的情况。通过讨论第一个 A/B 不对应相等的位置,可以发现能对齐的必要条件是:A,B 存在一个大小为 \(\gcd(|A|,|B|)\) 的共同循环节。(原谅我难以用文字证明清楚)

此外在要求上下串的总长应该相等,我们从这个条件为切入口计数。设 \(a\) 为上面 A 的个数减去下面 A 的个数,\(b\) 为下面 B 的个数减去上面 B 的个数。那么满足关系式 \(a|A|=b|B|\),简单讨论一下:

  • \(a=b=0\),两者的长度都可以任取,只需要计数共同的循环节即可:\(\sum_{i=1}^n\sum_{j=1}^n 2^{\gcd(i,j)}\)
  • \(a\cdot b\leq 0\),显然无解了。
  • \(a\cdot b>0\),我们先令 \(a\leftarrow \frac{a}{\gcd(a,b)},b\leftarrow \frac{b}{\gcd(a,b)}\),然后就可以推出 \(a=\frac{|B|}{\gcd(|A|,|B|)},b=\frac{|A|}{\gcd(|A|,|B|)}\),所以循环节的长度应该 \(\leq \frac{n}{\max(a,b)}=up\),那么对应的方案数是 \(\sum_{i=1}^{up}2^i\)

考虑解决有问号的情况,发现我们只需要关心 A/B 的个数,设上面的问号数量为 \(x\),下面的问号数量为 \(y\),设 \(f(a,b)\) 表示对应的方案数(参见上面的讨论),那么最终答案为:

\[\sum_{i=0}^x\sum_{j=0}^yf(a+i-j,b+(y-j)-(x-i))\cdot{x\choose i}\cdot{y\choose j} \]

仔细观察就能发现只有 \(O(n)\) 种本质不同的 \(f(a,b)\),因为这东西不好算所以把它提到外面来,我们枚举 \(k=i-j\)

\[\sum_{k=-y}^{x}f(a+k,b+y-x+k)\cdot\sum_{j} {x\choose k+j}{y\choose y-j} \]

发现后面是范德蒙德卷积的形式,可以看成 \(x+y\) 个数中选取 \(k+y\) 个,那么可以化简为:

\[\sum_{k=-y}^xf(a+k,b+y-x+k)\cdot{x+y\choose k+y} \]

至于求解 \(\sum_{i=1}^n\sum_{j=1}^n2^{\gcd(i,j)}\) 是平凡的,简单欧拉反演即可,时间复杂度 \(O(n)\)

#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int M = 1000005;
const int MOD = 1e9+7;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,x,y,a,b,fac[M],inv[M];char s[M],t[M];
int cnt,res,ans,p[M],vis[M],phi[M],pw[M];
void add(int &x,int y) {x=(x+y)%MOD;}
void init(int n)
{
	inv[0]=inv[1]=fac[0]=pw[0]=1;
	for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=2;i<=n;i++) inv[i]=inv[i-1]*inv[i]%MOD;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
	for(int i=1;i<=n;i++) pw[i]=pw[i-1]*2%MOD;
	phi[1]=1;
	for(int i=2;i<=n;i++)
	{
		if(!vis[i]) phi[i]=i-1,p[++cnt]=i;
		for(int j=1;j<=cnt && i*p[j]<=n;j++)
		{
			vis[i*p[j]]=1;
			if(i%p[j]==0)
			{
				phi[i*p[j]]=phi[i]*p[j];
				break;
			}
			phi[i*p[j]]=phi[i]*(p[j]-1);
		}
	}
	for(int i=1;i<=n;i++)
		phi[i]=(phi[i-1]+phi[i])%MOD;
}
int C(int n,int m)
{
	if(n<m || m<0) return 0;
	return fac[n]*inv[m]%MOD*inv[n-m]%MOD;
}
int gcd(int a,int b)
{
	return !b?a:gcd(b,a%b);
}
int f(int a,int b)
{
	if(a==0 && b==0) return res;
	if(a*b<=0) return 0;
	int g=gcd(a,b);a/=g;b/=g;
	int up=k/max(a,b);
	return pw[up+1]-2;
}
signed main()
{
	scanf("%s",s+1);n=strlen(s+1);
	scanf("%s",t+1);m=strlen(t+1);
	init(1000000);k=read();
	for(int i=1;i<=k;i++)
		add(res,pw[i]*(2*phi[k/i]-1));
	for(int i=1;i<=n;i++)
	{
		if(s[i]=='A') a++;
		if(s[i]=='B') b--;
		if(s[i]=='?') x++;
	}
	for(int i=1;i<=m;i++)
	{
		if(t[i]=='A') a--;
		if(t[i]=='B') b++;
		if(t[i]=='?') y++;
	}
	for(int i=-y;i<=x;i++)
		add(ans,f(a+i,b+y-x+i)*C(x+y,y+i));
	if(n==m)
	{
		int f=1,cnt=0;
		for(int i=1;i<=n;i++)
		{
			if(s[i]==t[i] && s[i]=='?') cnt++;
			if(s[i]!='?' && t[i]!='?' && s[i]!=t[i])
				{f=0;break;}
		}
		if(f) add(ans,pw[cnt]*((pw[k+1]-2)*(pw[k+1]-2)%MOD-res));
	}
	printf("%lld\n",(ans+MOD)%MOD);
}
posted @ 2022-05-23 22:14  C202044zxy  阅读(151)  评论(0编辑  收藏  举报