[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:为了易懂,本代码会稍有些冗长。大家可以自行优化~

posted @ 2021-05-23 15:00  栾竹清影  阅读(186)  评论(0编辑  收藏  举报