题解 夜莺与玫瑰(留坑)

传送门

留坑:填欧拉函数的坑!

题意杀……题面里除了那个「line」还有哪里暗示要求的是直线了?!

如果是直线的话……
考虑枚举斜率,则要求斜率式中a,b互质
但是对于一个斜率\(k=\frac{b}{a}\),怎么知道它的贡献呢?
我不会,但题解里面给出了求总贡献的柿子:

\[\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{m}[gcd(i,j)=1]\ (n-a)*(m-b)-max(n-2a, 0)*max(m-2b, 0) \]

考虑在一个\(n*m\)的网格中,一个\(a*b\)的矩形可以确定一条直线
但两个对角相接的矩形会重复计算贡献,怎么去重呢?
通过研究题解的表达式,发现这其实是个构造题
我们令其中一条直线经过了\(k\)\(a*b\)的矩形,则其实际贡献为\(k\),但我们希望贡献为1
尝试构造一个等于一的表达式
发现这条直线会经过\(k-1\)\(2a*2b\)的矩形,如图
WMYvA1.png
所以两者相减,贡献就为1

另外这还是个卡空间的毒瘤题,常规二维前缀和需要开三(四?)个二维数组,但这里只能开下两个

  • 关于二维前缀和的空间优化

发现柿子拆开是\(nm-mi-nj+ij\)(后面那部分类似,省掉了)
我们要枚举\(n,m\)并存到答案表里
发现这四部分的贡献是分开的
所以我们可以先离线,再分四次每次分别处理每项的二维前缀和,累加到对应的(如果询问了这个\(n,m\))答案表里,空间就可以卡过了

  • \(O(nm)\)处理出\((1,1)\)\((n,m\))间所有数的gcd:%%%@JYFHYX这里,是利用了gcd(a, b)=gcd(b, a%b)

Code:

#include <bits/stdc++.h>
using namespace std;
#define INF 0x3f3f3f3f
#define N 4010
#define ll long long 
#define ld long double
#define usd unsigned
#define reg register int
#define ull unsigned long long
//#define int long long 

#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf, 1, 1<<21, stdin)), p1==p2?EOF:*p1++)
char buf[1<<21], *p1=buf, *p2=buf;
inline ll read() {
	ll ans=0, f=1; char c=getchar();
	while (!isdigit(c)) {if (c=='-') f=-f; c=getchar();}
	while (isdigit(c)) {ans=(ans<<3)+(ans<<1)+(c^48); c=getchar();}
	return ans*f;
}

int n, m;
int sum[N][N], ans[10010];
unsigned short tab[N][N];
short qn[10010], qm[10010];
bool vis[N][N];
const ll p=1ll<<30;
int gcd(int a, int b) {return !b?a:gcd(b, a%b);}

signed main()
{
	#ifdef DEBUG
	freopen("1.in", "r", stdin);
	#endif
	int T;
	
	//cout<<double(sizeof(sum)+sizeof(ans)+sizeof(tab)+sizeof(vis)+sizeof(qn)*2)/1024/1024<<endl;
	T=read();
	for (int i=1; i<=T; ++i) {
		n=read(); m=read();
		qn[i]=n; qm[i]=m;
		if (!tab[n][m]) tab[n][m]=i;
		//printf("tab[%d][%d]=%d\n", n, m, i);
	}
	
	const int lim=4005;
	for (reg i=1; i<=lim; ++i) for (reg j=1; j<=lim; ++j) if (!(((i>>1)<<1)==i && ((j>>1)<<1)==j) && gcd(i, j)==1) vis[i][j]=1;
	for (reg i=1; i<=lim; ++i) for (reg j=1; j<=lim; ++j) if (vis[i][j]) sum[i][j]=1;
	for (reg i=1; i<=lim; ++i) for (reg j=1; j<=lim; ++j) {
		sum[i][j]=(1ll*sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+sum[i][j])%p;
		if (tab[i][j]) ans[tab[i][j]]+=(1ll*(sum[i][j]-sum[i>>1][j>>1])*i%p*j%p)%p, ans[tab[i][j]]%=p;
	}
	memset(sum, 0, sizeof(sum));
	
	for (reg i=1; i<=lim; ++i) for (reg j=1; j<=lim; ++j) if (vis[i][j]) sum[i][j]=i;
	for (reg i=1; i<=lim; ++i) for (reg j=1; j<=lim; ++j) {
		sum[i][j]=(1ll*sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+sum[i][j])%p;
		if (tab[i][j]) ans[tab[i][j]]-=(sum[i][j]-2ll*sum[i>>1][j>>1])*j%p, ans[tab[i][j]]%=p;
	}
	memset(sum, 0, sizeof(sum));
	
	for (reg i=1; i<=lim; ++i) for (reg j=1; j<=lim; ++j) if (vis[i][j]) sum[i][j]=j;
	for (reg i=1; i<=lim; ++i) for (reg j=1; j<=lim; ++j) {
		sum[i][j]=(1ll*sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+sum[i][j])%p;
		if (tab[i][j]) ans[tab[i][j]]-=(sum[i][j]-2ll*sum[i>>1][j>>1])*i%p, ans[tab[i][j]]%=p;
	}
	memset(sum, 0, sizeof(sum));
	
	for (reg i=1; i<=lim; ++i) for (reg j=1; j<=lim; ++j) if (vis[i][j]) sum[i][j]=i*j%p;
	for (reg i=1; i<=lim; ++i) for (reg j=1; j<=lim; ++j) {
		sum[i][j]=(1ll*sum[i][j-1]+sum[i-1][j]-sum[i-1][j-1]+sum[i][j])%p;
		if (tab[i][j]) ans[tab[i][j]]+=(sum[i][j]-4ll*sum[i>>1][j>>1])%p, ans[tab[i][j]]%=p;
	}
	
	for (int i=1; i<=T; ++i) printf("%lld\n", ((2ll*ans[tab[qn[i]][qm[i]]]+qn[i]+qm[i])%p+p)%p);
	
	return 0;
}
posted @ 2021-07-16 17:41  Administrator-09  阅读(13)  评论(0编辑  收藏  举报