Atcoder Grand Contest 003

特别是 \(\tt AGC\) 的题,一定要保证二次思考,即在读懂题解并且写完代码之后的再次思考,二次思考的意义是理清思路;补充思维链中空白的部分;提炼上层方法;对自己有帮助的地方。然后再写题解,一定不要急于求成,我可以做的慢。

003D Anticube

题目描述

点此看题

解法

首先考虑我们甚至不能对这些数质因数分解,因为我们无法承受 \(O(n\sqrt {s_{max}})\) 的复杂度。

解决质因数问题的常见优化思路是只考虑较大的质因数,由于本题的限制是完全立方数的特点,所以我们只分解 \(\leq\sqrt[3]{s_{max}}\) 的质因数,记 \(m=\sqrt[3]{s_{max}}\),我们按这几种情况依次分类:为 \(1\);为 \(p^2\);为 \(p(p\leq \sqrt {s_{max}})\);为 \(p_1\cdot p_2\) 或较大的质数。

显然第四类的数一定不会和其他数乘积为立方,所以可以直接计入答案。

第一类数和第二、三类不会产生关联,所以可以单独考虑。我们设数 \(x=p_1^{a}p_2^{b}...\) 的对立数是 \(y=p_1^{3-a}p_2^{3-b}...\)(注意指数是模 \(3\) 意义下),那么显然它们不能同时选取,一组对立数取数字多的那个即可。

第二、三类数之间会产生关联,在 \(p\) 相同的情况下,我们按照第一类数的方法做即可。

细节:如果原本就是完全立方数需要特判。时间复杂度 \(O(n\sqrt[3]{s_{max}})\)

总结

质因数分解的题目可以考虑讨论较大的质因数,较大 如何界定要根据题目分析。

#include <cstdio>
#include <iostream>
#include <cmath>
#include <map>
using namespace std;
const int M = 100005;
#define int long long
#define pii pair<int,int>
#define mp make_pair
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,cnt,ans,vis[M],p[M];
map<pii,int> mp1,mp2;
void init(int n)
{
	for(int i=2;i<=n;i++)
	{
		if(!vis[i]) p[++cnt]=i;
		for(int j=1;j<=cnt && i*p[j]<=n;j++)
		{
			vis[i*p[j]]=1;
			if(i%p[j]==0) break;
		}
	}
}
int f(int x)
{
	int c=1;
	for(int j=1;p[j]<=2200;j++)
	{
		int cnt=0;
		while(x%p[j]==0) x/=p[j],cnt++;
		if(cnt==2) c*=p[j];
		if(cnt==1) c*=p[j]*p[j];
	}
	return c;
}
signed main()
{
	n=read();init(100000);
	for(int i=1;i<=n;i++)
	{
		int x=read(),c=1;
		for(int j=1;p[j]<=2200;j++)
		{
			int cnt=0;
			while(x%p[j]==0) x/=p[j],cnt++;
			cnt%=3;
			if(cnt==1) c*=p[j];
			if(cnt==2) c*=p[j]*p[j];
		}
		int t=sqrt(x);
		if(x==1) mp1[mp(c,1)]++;
		else if(x==t*t) mp1[mp(c,t)]++;
		else if(x>1e5) ans++;
		else mp2[mp(c,x)]++;
	}
	for(auto x:mp1)
	{
		int A=x.first.first,B=x.first.second;
		int C=x.second,D=f(A);
		if(B==1 && A==1)
			ans++;
		//error:A,D may not all in the map
		if(B==1 && (A<D || !mp1.count(mp(D,1))))
			ans+=max(C,mp1[mp(D,1)]);
		if(B>1)
			ans+=max(C,mp2[mp(D,B)]),mp2[mp(D,B)]=0;
	}
	for(auto &x:mp2) ans+=x.second;
	printf("%lld\n",ans);
}

003E Sequential operations on Sequence

题目描述

点此看题

解法

首先简化问题,考虑求出原序列一个递增单调栈,那么不在单调栈里面的长度是没用的。因为不在单调栈中意味着后面会被一个长度更小的截掉,那么它就失去价值了,特别低栈中初始应该有 \(n\)

但是直接做也是困难的,我们考虑原序列一定被分成了若干个有规律的段,只是分解的方式十分复杂。那么我们可以考虑求出段 \([1,x]\)\(y\) 次的总贡献,记为二元组 \((x,y)\)

如果 \(x\leq s_1\),那么可以直接得到它的贡献,否则我们需要把它拆分成更小的段,我们找到栈中小于它的最长段 \(y\),那么它可以分解成 \((y,\lfloor\frac{x}{y}\rfloor\cdot k)\)\((x\bmod y,k)\)

那么可以把 \(x\) 当成下标 \(dp\),由于可能的取值只有栈中的元素,或者是某个栈的中元素被取模 \(\tt log\) 次之内的长度,所以总状态数是 \(O(n\log n)\) 的,那么用 \(\tt map\) 暴力算时间复杂度 \(O(n\log^2n)\)

总结

具有大量重复段的统计问题可以把段当成状态设计 \(dp\)

本题也可以看成从后往前倒推,从前往后是困难的,所以注意考察问题的顺序吧。

#include <cstdio>
#include <iostream>
#include <map>
using namespace std;
const int M = 100005;
#define int long long 
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,a[M],s[M];map<int,int> mp;
signed main()
{
	n=read();m=read();s[++k]=n;
	for(int i=1;i<=m;i++)
	{
		int x=read();
		while(k && s[k]>=x) k--;
		s[++k]=x;
	}
	mp[s[k]]=1;
	while(!mp.empty())
	{
		auto it=mp.end();it--;
		if(it->first<=s[1])
			a[it->first]+=it->second;
		else
		{
			int t=s[lower_bound(s+1,s+1+k,it->first)-s-1];
			mp[t]+=(it->second)*(it->first/t);
			mp[it->first%t]+=it->second;
		}
		mp.erase(it);
	}
	for(int i=n;i>=1;i--) a[i]+=a[i+1];
	for(int i=1;i<=n;i++) printf("%lld\n",a[i]);
}

003F Fraction of Fractal

题目描述

点此看题

解法

首先有一个基本的观察:答案是和分形的具体图像无关的,我们在乎的只有满足某种条件的点或者是满足某种条件的点对,虽然我们暂时不知道到底要求出什么。

考虑从最基本的情况开始分析,即一级分形的左右拼接和一级分形的上下拼接,如果左右拼接和上下拼接都形成了初级分形间的黑格连通块,那么任何时刻黑格连通块个数都一定是 \(1\);如果都没有形成,那么黑格连通块个数是 \(cnt^{k-1}\)

那么剩下的情况只有左右联通和上下联通了,我们这里讨论左右联通的情况。

\(cnt\) 表示初级分形黑格总数,\(tot\) 表示初级分形左右的相邻黑格对数,\(side\) 表示左右拼接之间的连通块个数,\(uside\) 表示初级分形左右拼接之间的左右相邻黑格对数,\(ans\) 表示连通块个数,我们考虑从 \(i-1\) 级分形递推到 \(i\) 级分形。

因为只有初级分形的结构已知,便于思考的方式是将初级分形的每个黑格替换成 \(k-1\) 级分形。对于 \(ans\) 的转移:\(ans'=ans\cdot cnt-side\cdot tot\),也就是总数减去相邻黑格产生的连通块个数;对于 \(side\) 的转移:\(side'=side\cdot uside\),因为从初级分形的角度思考,相邻的 \(k-1\) 级分形有 \(uside\) 对,而且每对之间由于不上下联通所以互不干扰,我们可以直接把它们乘起来。

直接矩阵加速即可,时间复杂度 \(O(2^3\log n)\)

#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1005;
const int MOD = 1e9+7;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,k,cnt,tot[2],us[2];char s[M][M];
struct Mat
{
	int a[2][2];
	Mat() {a[0][0]=a[0][1]=a[1][0]=a[1][1]=0;}
	Mat operator * (const Mat &b) const
	{
		Mat r;
		for(int i=0;i<2;i++) for(int j=0;j<2;j++)
			for(int k=0;k<2;k++)
				r.a[i][k]=(r.a[i][k]+a[i][j]*b.a[j][k])%MOD;
		return r;
	}
}A;
Mat qkpow(Mat a,int b)
{
	Mat r;
	for(int i=0;i<2;i++) r.a[i][i]=1;
	while(b>0)
	{
		if(b&1) r=r*a;
		a=a*a;
		b>>=1;
	}
	return r;
}
int zxy(int a,int b)
{
	int r=1;
	while(b>0)
	{
		if(b&1) r=r*a%MOD;
		a=a*a%MOD;
		b>>=1;
	}
	return r;
}
signed main()
{
	n=read();m=read();k=read();
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s[i]+1);
		for(int j=1;j<=m;j++) if(s[i][j]=='#')
		{
			cnt++;
			if(j>1) tot[0]+=(s[i][j]==s[i][j-1]);
			if(i>1) tot[1]+=(s[i][j]==s[i-1][j]);
		}
	}
	for(int i=1;i<=n;i++)
		us[0]+=(s[i][1]=='#' && s[i][m]=='#');
	for(int i=1;i<=m;i++)
		us[1]+=(s[1][i]=='#' && s[n][i]=='#');
	if(us[0] && us[1])
		{puts("1");return 0;}
	if(!us[0] && !us[1])
		{printf("%d\n",zxy(cnt,k-1));return 0;}
	int z=us[0]?0:1;
	A.a[0][0]=cnt;A.a[0][1]=-tot[z];A.a[1][1]=us[z];
	A=qkpow(A,k-1);
	printf("%lld\n",(A.a[0][0]+A.a[0][1]+MOD)%MOD);
}
posted @ 2022-01-24 10:33  C202044zxy  阅读(91)  评论(4编辑  收藏  举报