感覺這樣的題真的稱得上是鬼斧神工啊,\(\text{OI}\)中能多一些這樣的題目就太好了。
題意:
有一個二維的三角座標系,大概如圖所示(圖是從atcoder裏偷下來的):
座標系上的每個整點處都有一盞燈,初始時只有一盞燈亮着。每次可以選擇三個整點\((x,y)\),\((x,y+1)\),\((x+1,y)\)(即一個底邊向下的正三角形),將這三盞燈的開關狀態反轉。現在給出若干次反轉操作後所有亮着的\(n\)盞燈的座標,求最初亮着的是哪一盞燈。\(n\leq 10^4\),\(-10^{17}\leq 座標\leq 10^{17}\)。
題解:
我們考慮將從某一盞燈開始,不斷通過向下反轉將在\(y=c\)上的燈轉移到\(y=c-1\)上。可以發現如果我們從\((0,0)\)開始向下轉移\(c\)次,每一行的亮燈狀態就是帕斯卡三角在模\(2\)意義下的結果,結合盧卡斯定理,可以發現\(y=-c\)上只有滿足\(0\leq x\leq c\)且\(x\subseteq c\)的\((x,-c)\)是亮的。那麼不難發現此時\((0,-c)\)和\((c,-c)\)一定是亮的,於是我們只要將所有亮着的燈向下轉移到同一條足夠低的直線上,再計算出這條直線上亮着的燈中最左端和最右端的點,就可以知道原來的點是什麼。
說的好聽,怎麼計算左右端點呢?
我們先來研究一下帕斯卡三角在模\(2\)意義下有什麼性質。如果我們將模\(2\)餘\(1\)的點看作黑點,否則看作白點,那麼帕斯卡三角是長這樣的(偷自維基百科):
可以發現這是一個類似分形的結構,同時每一個三角形的邊長都是\(2^k\)。那麼考慮從這一行的任意一個亮着的\((x,y)\)開始向右移動,對於\(k=60,59,\dots,0\),如果\((x,y+2^k)\)是亮着的,那麼移動到\((x,y+2^k)\),這相當於移動至右邊的一個同構三角形的等價位置上。可以證明這一定可以移動到右端點。左端點也是類似的。判斷一個點是否是亮着的也很簡單,只需要\(O(n)\)掃描每個點即可。
於是我們得到了一個優秀的\(O(n\log MAX)\)的做法,其中\(MAX\)表示座標範圍。假的。
上面這個做法的問題在於我們無法快速找到一行中的一個會亮着的點!那麼怎麼高效的找到一個呢?
再考慮帕斯卡三角形的一個性質:對於第\(n\)行,對於\(k=0,1,2\),至少存在一個\(k\)使得\(\sum_{x\equiv k(\bmod\ 3)}{n\choose x}\equiv 1(\bmod \ 2)\),由歸納法保留上一行模\(3\)的和數組向下轉移即可證明。假設我們可以在很快的時間裏計算對於\(l\leq x\leq r\)且\(x\equiv k(\bmod\ 3)\)的\(x\),\((x,-c)\)是亮着的點數是奇數還是偶數,那麼就可以通過二分的方法,每次選擇點數是奇數的那一半繼續做即可。由於左右兩邊的總點數是奇數因此這一半是一定存在的。
考慮怎麼解決這個問題,顯然在模\(2\)下每個點可以分開計算。那麼對每個點考慮數位\(dp\),狀態只需要記已經確定了幾位,是否受數位的限制以及當前數在模\(3\)意義下餘多少即可,結合盧卡斯定理可以設計轉移。這麼做複雜度是\(O(n\log MAX)\)的,結合二分,我們可以在\(O(n\log^2 MAX)\)的時間內找到一個亮着的點。於是我們就在\(O(n\log^2 MAX)\)的時間內解決了問題。
代碼:
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<utility>
using std::max;
using std::min;
using std::pair;
using std::make_pair;
typedef long long ll;
const int N=1e4+5;
int n;
ll x[N],y[N];
int res[3];
int dp[64][2][3];
inline void calc(ll c,ll x,ll y,ll r)
{
ll n=x-c;
register int i,j,k;
r-=y;
if(r<0)
return res[0]=res[1]=res[2]=0,void();
memset(dp,0,sizeof(dp));
dp[63][1][0]=1;
for(i=63;i>0;i--)
if(!(n>>(i-1)&1))
{
for(j=0;j<3;j++)
{
if(dp[i][0][j])
dp[i-1][0][(j*2)%3]^=1;
if(dp[i][1][j])
{
if(r>>(i-1)&1)
dp[i-1][0][(j*2)%3]^=1;
else
dp[i-1][1][(j*2)%3]^=1;
}
}
}
else
{
for(j=0;j<3;j++)
{
if(dp[i][0][j])
{
dp[i-1][0][(j*2)%3]^=1;
dp[i-1][0][(j*2+1)%3]^=1;
}
if(dp[i][1][j])
{
if(r>>(i-1)&1)
dp[i-1][0][(j*2)%3]^=1,dp[i-1][1][(j*2+1)%3]^=1;
else
dp[i-1][1][(j*2)%3]^=1;
}
}
}
y=(y%3+3)%3;
res[y]=dp[0][0][0]^dp[0][1][0];
res[(y+1)%3]=dp[0][0][1]^dp[0][1][1];
res[(y+2)%3]=dp[0][0][2]^dp[0][1][2];
return;
}
int lres[3],rres[3],mres[3];
inline ll epc(ll c)
{
int k;
ll l=-1000000000000000000ll,r=1000000000000000000ll,mid;
register int i;
rres[0]=rres[1]=rres[2]=0;
for(i=1;i<=n;i++)
{
calc(c,x[i],y[i],r);
rres[0]^=res[0];rres[1]^=res[1];rres[2]^=res[2];
}
lres[0]=lres[1]=lres[2]=0;
for(i=0;i<3;i++)
if(rres[i])
{
k=i;
break;
}
while(l<r)
{
mid=(l+r)>>1;
mres[0]=mres[1]=mres[2]=0;
for(i=1;i<=n;i++)
{
calc(c,x[i],y[i],mid);
mres[0]^=res[0];mres[1]^=res[1];mres[2]^=res[2];
}
if(mres[k]^lres[k])
r=mid,memcpy(rres,mres,sizeof(int)*3);
else
l=mid+1,memcpy(lres,mres,sizeof(int)*3);
}
return l;
}
inline bool light(ll x,ll y)
{
int res=0;
ll xx,yy,n,m;
register int i;
for(i=1;i<=::n;i++)
{
xx=::x[i];yy=::y[i];
n=xx-x;m=y-yy;
if(m<0||m>n)
continue;
res^=((n|m)==n);
}
return res;
}
inline void solve(ll c,pair<ll,ll> &a)
{
ll k=epc(c),lk=k,rk=k;
// fprintf(stderr,"%lld\n",k);
register ll i;
for(i=1ll<<62;i;i>>=1)
if(light(c,lk-i))
lk-=i;
for(i=1ll<<62;i;i>>=1)
if(light(c,rk+i))
rk+=i;
a.first=c+(rk-lk);a.second=lk;
return;
}
signed main()
{
pair<ll,ll> a;
register int i;
scanf("%d",&n);
for(i=1;i<=n;i++)
scanf("%lld%lld",&x[i],&y[i]);
solve(-100000000000000000,a);
printf("%lld %lld\n",a.first,a.second);
return 0;
}