[USACO21FEB] Count the Cows G 题解
题意转化
题目的要求看起来很复杂,但是,如果我们尝试打一个表,输出前面矩阵放置奶牛的情况,我们不难发现,题目的矩阵就是如下矩阵:
1 0 1
0 1 0
1 0 1
对于这个基本矩阵,按照如上的摆放方式生成更大的矩阵,依次类推。
于是题目就转化为,对于这个递归式的矩阵,求出 \((x,y)\) 到 \((x+d,y+d)\) 对角线上 \(1\) 的个数。
注:容易发现,上方图形沿对角线对称,所以我们假设 \(x\leq y\)。
问题转化
看到有 \(y=0\) 的部分分,于是考虑将所有数据转化成 \(y=0\) 的情况。
设 \(f_{i,j,n}\) 表示从 \((0,0)\) 到 \((i,j)\) 对角线上 \(1\) 的个数,其中, \(n\) 是左上角为原点、包含整条对角线的正方形的边长。
那么,\((x,y)\) 到 \((x+d,y+d)\) 对角线上 \(1\) 的个数就可以转化为 \(f_{x+d,y+d,n}-f_{x-1,y-1,n'}\)(前缀和思想)。
于是,现在问题就转化为求 \(f_{i,j,n}\) 的值了。
问题求解
以下图为例:
设现在要求的是 \(f_{x,y,n}\),如图是边长为 \(n\)、左上角为 \((0,0)\) 的正方形,编号为子矩阵编号。
另外地,为了表示简便,再设 \(g_{i,n}\) 表示从 \((0,i)\) 出发的对角线在左上角为 \((0,0)\)、边长为 \(n\) 的正方形中 \(1\) 的个数。
设 \(z=x-y\)(即线段延长至 \(y=0\) 时的 \(x\) 坐标),\(m=n/3\)(即子矩阵边长)。
在以下情况下,\(f_{x,y,n}\) 分别等于:
-
线段从矩阵 \(1\) 出发(\(z<m\))
-
到矩阵 \(1\) 结束(\(x<m,y<m\)):\(f_{x,y,m}\)
-
到矩阵 \(4\) 结束(\(m\leq x<2m,y<m\)):\(g_{z,m}\)
-
到矩阵 \(5\) 结束(\(m\leq x< 2m,m\leq y<2m\)):\(f_{x-m,y-m,m}+g_{z,m}\)
-
到矩阵 \(8\) 结束(\(x≥2m,m\leq y< 2m\)):\(2\times g_{z,m}\)
-
到矩阵 \(9\) 结束(\(x≥2m,y≥2m\)):\(2\times g_{z,m}+f_{x-2m,y-2m,m}\)
-
-
线段从矩阵 \(4\) 出发 (\(m\leq z<2m\))
-
到矩阵 \(4\) 结束(\(m\leq x<2m,y<m\)):\(0\)
-
到矩阵 \(8\) 结束(\(x≥2m,m\leq y<2m\))
- 未经过矩阵 \(7\)(\(z=m\)):\(0\)
- 经过矩阵 \(7\)(\(m<z<2m\)):\(g_{2m-z,m}\)
-
到矩阵 \(7\) 结束 (\(x≥2m,y<m\)):\(f_{x-2m,y,m}\)
-
-
线段从矩阵 \(7\) 出发(\(z≥2m\))
- 所有情况都与从矩阵 \(1\) 出发对称,返回 \(f_{x-2m,y,m}\)
(由于 \(y\leq x\),所以 \(2,3,6\) 号矩阵不可能经过)
边界条件:当 \(n=3\) 时特判答案。
到这里,这道题基本就做完了,唯一的问题就是 \(g\) 的转移。其实,由于对角线一定穿过对应正方形,所以,分别从 \(1,4,7\) 号矩阵出发的对角线,一定会分别在 \(9,8,7\) 号矩阵结束。于是就可以按照 \(f\) 数组转移中的三种情况来转移 \(g\) 数组了。
代码
具体看注释趴
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=41;
inline ll read()
{
register ll x=0;
register char c=getchar();
for(;!(c>='0'&&c<='9');c=getchar());
for(;c>='0'&&c<='9';c=getchar())
x=(x<<1)+(x<<3)+c-'0';
return x;
}
const int biao[3][3]={{1,0,1},
{0,2,0},
{1,0,3}};
//n=3 时的特判
int q;
ll G(ll z,ll n)//g 的转移
{
register ll m=n/3;
if(n==3)
return biao[2][2-z];
//边界条件(注意,本题都是从 0 计数)
if(z<m)
return 3*G(z,m);
else if(z==m)
return 0;
else if(z<(m<<1))
return G(m*2-z,m);
else
return G(z-m*2,m);
//按照方程分情况转移
}
ll F(ll x,ll y,ll n)
{
if(n==3)
return biao[x][y];
if(y>x)
swap(x,y);
register ll m=n/3,z=x-y;
if(z<m)
{
if(x<m&&y<m)
return F(x,y,m);
if(y<m&&x>=m&&x<2*m)
return G(z,m);
if(x>=m&&x<2*m&&y>=m&&y<2*m)
return G(z,m)+F(x-m,y-m,m);
if(x>=2*m&&y>=m&&y<2*m)
return G(z,m)*2;
if(x>=2*m&&y>=2*m)
return G(z,m)*2+F(x-m*2,y-m*2,m);
}
if(z>=m&&z<2*m)
{
if(x<2*m&&x>=m&&y<m)
return 0;
if(y>=m&&y<2*m&&x>=2*m)
{
if(z==m)
return 0;
if(z>m&&z<2*m)
return G(2*m-z,m);
}
if(x>=2*m&&y<m)
return F(x-2*m,y,m);
}
if(z>=2*m)
return F(x-2*m,y,m);
//按照方程分情况转移
}
ll Work(ll x,ll y)
{
if(x<0||y<0) return 0;
if(y>x) swap(x,y);
register ll sum=1;
while(sum<=x)
sum*=3;//取 n 的值(第一个大于 max{x,y} 的3的整数次幂)
return F(x,y,sum);
}
int main()
{
register ll d,x,y;
q=read();
while(q--)
{
d=read(),x=read(),y=read();
printf("%lld\n",Work(x+d,y+d)-Work(x-1,y-1));
}
return 0;
}
Tipes:为了易懂,本代码会稍有些冗长。大家可以自行优化~