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);
}