1. 程式人生 > 實用技巧 >「Luogu P6452 [COCI2008-2009#4] TREZOR」

「Luogu P6452 [COCI2008-2009#4] TREZOR」

題目大意

給出一個 \((a+b)\times (l-1)\) 的矩形,端點為 \((0,0)\)\((l-1,a+b)\),在 \((-1,0)\)\((-1,a+b)\) 的位置有一個特殊點,問又多少點滿足和其中一個特殊點之間的連線不經過其他點,和兩個特殊點的連線不經過其他點,以及兩個特殊點和它的連線都會經過其他點.

分析

對於兩線之後不經過其他點的點對 \((x_1,y_1),(x_2,y_2)\) 必定滿足 \(\gcd(|x_1-x_2|,|y_1-y_2|)=1\)(具體可以看看 P2158 的題解區,這裡不作多解釋),可以發現 \(a+b\) 的範圍很小,所以考慮列舉所在的行,對於每一行計算答案.

\(f(l,x)\) 表示 \(\sum\limits_{i=1}^{l}[\gcd(i,x)=1]\).即計算 \(1\sim l\) 中與 \(x\) 互質的數的個數.

那麼對於第 \(i\) 行第一個特殊點可以看見的點的個數 \(=f(l,i-1)\),第二個特殊點可以看見的點的個數 \(=f(l,a+b+1-i)\).

考慮 \(f\) 的本質,\(f(l,x)\) 的計算可以把 \(x\) 拆成 \(p_1^{k_1}p_{2}^{k_2}p_{3}^{k_3}\dots\).那麼 \(f(l,x)=l-\lfloor\frac{l}{p_1}\rfloor-\lfloor\frac{l}{p_2}\rfloor\dots+\lfloor\frac{l}{p_1p_2}\rfloor+\lfloor\frac{l}{p_2p_3}\rfloor+\lfloor\frac{l}{p_1p_3}\rfloor\dots-\lfloor\frac{l}{p_1p_2p_3}\rfloor-\lfloor\frac{l}{p_1p_2p_4}\rfloor-\lfloor\frac{l}{p_1p_3p_4}\rfloor\dots\dots\)

變成了一個簡單的容斥,\(f(l,x)=\)\(0\) 個數的乘積在 \(1\sim m\) 中的倍數個數 \(-\)\(1\) 個數的乘積在 \(1\sim m\) 中的倍數的個數 \(+\)\(2\) 個數的乘積在 \(1\sim m\) 中倍數的個數 \(\dots\)

同時被兩個點看到的方案數 \(=f(l,(i-1)(a+b+1-i))\)(可以理解為需要得到將兩個數分解之後所得的質數集合的並集,\((i-1)(a+b+1-i)\) 換成 \(\operatorname{lcm}(i-1,a+b+1,i)\) 也是可以的)

恰好只會被第一個特殊點看到的方案數 \(=f(l,i-1)-f(l,(i-1)(a+b+1-i))\)

.第二個特殊點同理.

程式碼

#include<bits/stdc++.h>
#define REP(i,first,last) for(int i=first;i<=last;++i)
#define DOW(i,first,last) for(int i=first;i>=last;--i)
using namespace std;
const int PRIME[303]={2,3,5,7,11,13,....};//2000 以內的質數表
int a,b,l;
long long answer1,answer2;
long long sum;
int cnt=0;
int num[100];
void DFS(int now=1,int use=0,long long add=1)
{
	if(now==cnt+1)
	{
		sum+=1ll*(use&1?-1:1)*(l/add);//選了偶數個是加上,選了奇數個是減
		return;
	}
	DFS(now+1,use,add);
	DFS(now+1,use+1,add*num[now]);
}
long long Calc(int a)
{
	if(!a)
	{
		return 1;
	}
	sum=0;
	cnt=0;
	REP(i,0,302)//將數分解為若干質數的冪次的乘積的形式
	{
		if(a%PRIME[i]==0)
		{
			num[++cnt]=PRIME[i];
			while(a%PRIME[i]==0)
			{
				a/=PRIME[i];
			}
		}
	}
	if(a^1)
	{
		num[++cnt]=a;
	}
	DFS();//暴力 DFS 容斥
	return sum;
}
int main()
{
	scanf("%d%d%d",&a,&b,&l);
	int len=a+b+1;
	REP(i,1,len)
	{
		a=i-1;
		b=len-i;
		long long ua=Calc(a);//第一個特殊點可以看見的點的數量
		long long ub=Calc(b);//第二個特殊點可以看見的點的數量
		long long uab=Calc(a*b);//第一個和第二個特殊點可以同時看見的點的數量
		answer2+=uab;//記錄答案
		answer1+=ua-uab+ub-uab;
	}
	printf("%lld\n%lld\n%lld\n",1ll*len*l-answer1-answer2,answer1,answer2);
	return 0;
}