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:
求
\(a,b,c,d\le 3\times10^6,x,y\le 10^9\)。
解:
本题难点在于想到二维前缀求解。
首先先转换为二维前缀求解,即转换为 \((b,d),(b,c),(a,d),(a,c)\) 四个点的矩阵。
那么答案即为
对于每一个分别求解,首先先对 \(\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 以后再回来补

浙公网安备 33010602011771号