#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)\) 表示对应的方案数(参见上面的讨论),那么最终答案为:
仔细观察就能发现只有 \(O(n)\) 种本质不同的 \(f(a,b)\),因为这东西不好算所以把它提到外面来,我们枚举 \(k=i-j\):
发现后面是范德蒙德卷积的形式,可以看成 \(x+y\) 个数中选取 \(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);
}