2022.11.06 题解(简化了题面

一测 \(166pts\),又是 T2 炸了(怎么总是 T2 炸)

T1:预测黄(1200)

T2:预测蓝(2000)

T3:预测蓝(2100)

T4:AT_agc039_c [AGC039C] Division by Two with Something(紫)


T1:

给定一棵大小为 \(n\) 的满二叉树,\(q\) 次询问,每个询问给定一个数 \(x\),求问广搜遍历节点编号为 \(x\) 的深搜遍历。

\(n\le 10^{18},q\le 10^5\)


解:

这里用了个比较蠢的做法。

对于每一个询问,判断节点是这棵子树的左儿子还是右儿子,如果是右儿子,将自己的兄弟节点向下遍历,看以自己兄弟节点为根的子树大小是多少,然后将节点往上跳。

向下走就一直对节点编号 \(\times 2\),找到最后一个编号小于等于 \(n\) 的节点,就可以算出底层节点数,而上面是满二叉树,直接记录深度计算即可。

向上走只需要一直对节点 \(\div 2\) 即可。

时间复杂度:\(O(n\times\log^2_2(n))\)

\(O(n\log(n))\) 的做法,但考场上懒得想了。

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
int n,m;
signed main()
{
	freopen("erchatree.in","r",stdin);
	freopen("erchatree.out","w",stdout);
	scanf("%lld%lld",&n,&m);
	for(int i=1;i<=m;i++)
	{
		int x;
		scanf("%lld",&x);
		int ans=0;
		while(x)
		{
			if(x%2==1&&x!=1)
			{
				int t=x-1,dep=0;
				while(t*2<=n)t*=2,dep++;
				if((1ll<<dep)+t-1>n)ans-=(1ll<<dep)+t-1-n;
				ans+=(1ll<<(dep+1))-1;
			}
			x/=2;
			ans++;
		}
		printf("%lld\n",ans);
	}
	return 0;
}

T2:

\[\prod_{i=a}^{b}\prod_{j=c}^{d}\gcd(x^i,y^j) \]

\(a,b,c,d\le 3\times10^6,x,y\le 10^9\)


解:

本题难点在于想到二维前缀求解。

首先先转换为二维前缀求解,即转换为 \((b,d),(b,c),(a,d),(a,c)\) 四个点的矩阵。

那么答案即为

\[\prod_{i=1}^{b}\prod_{j=1}^{d}\gcd(x^i,y^j)\div\prod_{i=1}^{b}\prod_{j=1}^{c-1}\gcd(x^i,y^j)\div\prod_{i=1}^{a-1}\prod_{j=1}^{d}\gcd(x^i,y^j)\times\prod_{i=1}^{a-1}\prod_{j=1}^{c-1}\gcd(x^i,y^j) \]

对于每一个分别求解,首先先对 \(\gcd(x,y)\) 分解质因数,然后对每个质因数分别讨论,可以发现其实类似于一个单调队列的问题,枚举 \(j\),那么对当 \(i\) 乘上 \(x\) 所含有这个质因子的数量小于 \(j\) 乘上 \(y\) 所含有这个质因子的数量时就是前者造成贡献,否则为后者造成贡献,用双指针找到这个分界点,前面的用前缀积处理,后面的用快速幂处理即可。

时间复杂度:\(O(d\times\log(\max(x,y))^2_2)\)

提前稍微预处理一下快速幂就卡常卡过了。

\(n\times\log(n)\) 做法,但评讲时划了划水,没听。

#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
const int mod=998244353;
const int N=3000005;
int a,b,c,d,x,y,q[N],r,q1[N],q2[N],t[14][N];
inline int quick_pow(int x,int y)
{
	int sum=1,num=x;
	while(y)
	{
		if(y&1)sum*=num,sum%=mod;
		num*=num;
		num%=mod;
		y>>=1;
	}
	return sum;
}
int gcd(int x,int y)
{
	if(y==0)return x;
	return gcd(y,x%y);
}
void prepare(int x,int y)
{
	int num=gcd(x,y);
	int lim=num;
	for(int i=2;i*i<=num;i++)
	{
		if(lim%i==0)
		{
			q[++r]=i;
			while(lim%i==0)lim/=i;
		}
	}
	if(lim!=1)q[++r]=lim;
	for(int i=1;i<=r;i++)
	{
		while(x%q[i]==0)
		{
			x/=q[i];
			q1[i]++;
		}
		while(y%q[i]==0)
		{
			y/=q[i];
			q2[i]++;
		}
		t[i][0]=1;
		for(int j=1;j<=N-5;j++)t[i][j]=t[i][j-1]*q[i],t[i][j]%=mod;
	}
}
int work(int id,int num,int num1,int num2,int t1,int t2)
{
	int l=1,res=1,sum=1;
	for(int i=1;i<=t2;i++)
	{
		while(l<=t1&&num1*l<=num2*i)sum*=quick_pow(t[id][l],num1),l++,sum%=mod;
		res*=quick_pow(t[id][i],num2*(t1-l+1));
		res%=mod;
		res*=sum;
		res%=mod;
	}
	return res;
}
int clac(int x,int y)
{
	if(x<=0||y<=0)return 1;
	int sum=1;
	for(int i=1;i<=r;i++)sum*=work(i,q[i],q1[i],q2[i],x,y),sum%=mod;
	//cout<<x<<" "<<y<<' '<<sum<<endl;
	return sum;
}
signed main()
{
	freopen("gcd.in","r",stdin);
	freopen("gcd.out","w",stdout);
	scanf("%lld%lld%lld%lld%lld%lld",&a,&b,&c,&d,&x,&y);
	prepare(x,y);
	int ans=clac(b,d)*quick_pow(clac(a-1,d),mod-2)%mod*quick_pow(clac(b,c-1),mod-2)%mod*clac(a-1,c-1)%mod;
	printf("%lld",ans);
	return 0;
}
/*
0 3 1 4 2 4
1 4 0 3 4 2
*/

T3:

给定一个 \(n\times m\) 的矩阵,求有多少个子矩阵和为 \(k\)

\(n\times m\le2\times 10^5,a_{i,j}\ge0\)


解:

考场时想了个分类加二分,结果一分没多骗到,结果用双指针就过了。

对于每一行维护一个双指针,枚举每一个点时,跑这个双指针就行了。

对于如何处理 \(a_{i,j}=0\),再用个指针维护就行了,具体可以看看代码。

然后由于要枚举行数,所以如果 \(n>m\) 那么交换一下即可。

时间复杂度:\(O(n\times m\times\min(n,m))\)

二分复杂度:\(O(n\times m\times\min(n,m)\times\log(\max(n,m)))\)。(就多了个 log 而已啊)。

如果有 \(a_{i,j}<0\) 那估计只能每行一个 BST 了。

#include<iostream>
#include<cstdio>
#include<vector>
#define int long long
using namespace std;
const int N=200005;
int n,m,q[N],t[N];
vector<int>sum[N];
inline int clac(int x,int y,int a,int b)
{
	return sum[x][y]-sum[x][b-1]-sum[a-1][y]+sum[a-1][b-1];
}
signed main()
{
	freopen("lotus.in","r",stdin);
	freopen("lotus.out","w",stdout);
	int T;
	scanf("%lld",&T);
	while(T--)
	{
		scanf("%lld%lld",&n,&m);
		for(int i=0;i<=n;i++)sum[i].clear(),sum[i].reserve(m+1);
		for(int i=0;i<=m;i++)sum[0].push_back(0);
		for(int i=1;i<=n;i++)sum[i].push_back(0);
		for(int i=1;i<=n;i++)
		{
			for(int j=1;j<=m;j++)
			{
				int x;
				scanf("%lld",&x);
				sum[i].push_back(x);
			}
		}
		int num,ans=0;
		scanf("%lld",&num);
		if(n<=m)
		{
			for(int i=1;i<=n;i++)
			{
				for(int j=1;j<=i;j++)q[j]=1,t[j]=1;
				for(int j=1;j<=m;j++)
				{
					sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
					for(int k=1;k<=i;k++)
					{
						while(q[k]<=j&&clac(i,j,k,q[k])>num)q[k]++;
						while(t[k]<=j&&clac(i,j,k,t[k])>=num)t[k]++;
						ans+=t[k]-q[k];
					}
				}
			}
		}
		else
		{
			for(int j=1;j<=m;j++)
			{
				for(int i=1;i<=j;i++)q[i]=1,t[i]=1;
				for(int i=1;i<=n;i++)
				{
					sum[i][j]+=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
					for(int k=1;k<=j;k++)
					{
						while(q[k]<=i&&clac(i,j,q[k],k)>num)q[k]++;
						while(t[k]<=i&&clac(i,j,t[k],k)>=num)t[k]++;
						ans+=t[k]-q[k];
					}
				}
			}
		}
		printf("%lld\n",ans);
	}
	return 0;
}

T4:

传送门

xdm 等我 1s 以后再回来补

posted @ 2023-02-24 13:58  Gmt丶Fu9ture  阅读(28)  评论(0)    收藏  举报