• #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);
    }
    
  • 相关阅读:
    H5 移动端相册拍照 录音 录像,然后上传后台
    h5 移动端调用相机 摄像机 录音机获取文件,并下载到本地
    Java 判断字符串是否含有 数字 字母 特殊字符
    java 以0开头的数字计算,保留开头0
    Spring 与hibernate 整合,测试数据连接
    http://blog.csdn.net/basycia/article/details/52205916
    MySQL数据库基础知识002
    数据库基础知识001
    数组排序
    输出杨辉三角
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16303607.html
Copyright © 2020-2023  润新知