【AtCoder】AtCoder Grand Contest 047 解题报告($Asim E$)

点此进入比赛

(A):Integer Product(点此看题面

大致题意: 给定(n)个实数(小数长度不超过(9)),问有多少对数乘积为整数。

直接做肯定会爆精度。

考虑到小数长度不超过(9),因此我们把所有数乘上(10^9),然后只要判断两数乘积末尾是否至少含有(18)(0)即可。

但直接乘又会爆(long long)

因此我们记有(i)个因子(2)以及(j)个因子(5)的数的个数为(s_{i,j})

每次枚举(i,j,u,v),若(min(i+u,j+v)ge18),则将答案加上(s_{i,j} imes s_{u,v})(注意特判(i=u,j=v)的情况)。

最终将答案除以(2)即可。

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define LL long long
using namespace std;
int n,s[55][55];
int main()
{
	RI i,j,t,u,v;long long x;char c;for(scanf("%d",&n),i=1;i<=n;++i)//读入每个数
	{
		x=t=0;W(!isdigit(c=getchar()));W(x=(x<<3)+(x<<1)+(c&15),isdigit(c=getchar()));//整数部分
		if(c=='.') W(isdigit(c=getchar())) x=(x<<3)+(x<<1)+(c&15),++t;//小数部分
		u=v=0;W(t<9) ++t,++u,++v;W(!(x%2)) x/=2,++u;W(!(x%5)) x/=5,++v;++s[u][v];//乘上1e9,统计因子2和因子5的个数
	}
	long long ans=0;for(i=0;i<=50;++i) for(j=0;j<=50;++j) for(u=0;u<=50;++u)
		for(v=0;v<=50;++v) min(i+u,j+v)>=18&&(ans+=1LL*s[i][j]*(s[u][v]-(i==u&&j==v)));//暴力统计答案,注意一个数无法与自身计算贡献
	return printf("%lld
",ans>>1),0;//输出答案
}

(B):First Second(点此看题面

大致题意: 给定(n)个字符串,问有多少对字符串,满足其中一个字符串可以通过另一个字符串转化得到。转化的方式是,任意次操作,每次可以删去字符串开头两个字符中的一个。

考虑一对字符串(s1,s2)的转化(设(len(s_1)ge len(s_2))),肯定是(s1)转化为(s2)

由于转化过程中会对(s1)操作(len(s1)-len(s2))次,每次又只操作前两个字符,因此不可能修改到(s1)的最后(len(s2)-1)个字符。

也就是说,(s1)(s2)的最后(len(s2)-1)个字符必须一致,且在(s1)(len(s1)-len(s2)+1)个字符中必须要存在(s2)的第一个字符。

考虑我们对于每个串的倒串建一棵(Trie)树,每个节点维护出子树内包含某个字符的串数各自有多少个(可以在插入同时一并维护)。

最终只要枚举每个串作为较短串,找到它后(len(s2)-1)个字符对应的节点进行询问即可。

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define SZ 1000000
using namespace std;
int n,id[N+5],p[N+5];string s[N+5];
class Trie//Trie树
{
	private:
		int Nt,g[30];struct node {int S[30],C[30];}O[SZ+5];
	public:
		I Trie() {Nt=1;}I int Ins(string s)//插入
		{
			RI i,j,x=1,y=1,t,l=s.length();for(i=0;i^l;++i) ++g[s[i]&31];for(i=l-1;~i;--i)
			{
				for(j=1;j<=26;++j) g[j]&&++O[x].C[j];--g[t=s[i]&31];//更新当前点子树内包含某种字符的串数
				!O[x].S[t]&&(O[x].S[t]=++Nt),y=x,x=O[x].S[t];//跳到子节点
			}return y;//返回上一个字符所在的位置用于询问
		}
		I int Qry(CI x,CI v) {return O[x].C[v]-1;}//询问,除去自身贡献
}T;
int main()
{
	RI i,x;ios::sync_with_stdio(false),cin>>n;for(i=1;i<=n;++i) cin>>s[i],p[i]=T.Ins(s[i]);//插入
	long long t=0;for(i=1;i<=n;++i) t+=T.Qry(p[i],s[i][0]&31);return printf("%lld
",t),0;//枚举较短串统计答案
}

(C):Product Modulo(点此看题面

大致题意: 给定(n)个数,求(sum_{i=1}^nsum_{j=i+1}^n((a_i imes a_j) mod P))的值((P=200003),求和时不取模)。

由于模数很小,考虑枚举每一个余数(x),去求((a_i imes a_j) mod P=x)(i,j)对数。

一个套路的转化,乘法变加法。

求出模(P)意义下的原根(g),令(Lg(x)=log_gx),根据原根的性质,(x=1sim P-1)(Lg(x))各不相同。

显然有一个变形:

[(a_i imes a_j) mod P=xLeftrightarrow (Lg(a_i)+Lg(a_j)) mod (P-1)=Lg(x) ]

容易发现这就是一个循环卷积的形式,不取模的话直接(FFT)就可以了。

还要注意减去每个数自身的贡献。

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
#define X 200003
#define PR 2
#define LL long long
using namespace std;
int n,a[N+5],s[X+5],Lg[X+5];LL p[X+5];
class FastIO
{
	private:
		#define FS 100000
		#define tc() (A==B&&(B=(A=FI)+fread(FI,1,FS,stdin),A==B)?EOF:*A++)
		#define D isdigit(c=tc())
		char c,*A,*B,FI[FS];
	public:
		I FastIO() {A=B=FI;}
		Tp I void read(Ty& x) {x=0;W(!D);W(x=(x<<3)+(x<<1)+(c&15),D);}
}F;
namespace Poly
{
	#define DB double
	#define Pi acos(-1)
	struct node//复数
	{
		DB x,y;I node(Con DB& a=0,Con DB& b=0):x(a),y(b){}
		I node operator + (Con node& o) Con {return node(x+o.x,y+o.y);}
		I node operator - (Con node& o) Con {return node(x-o.x,y-o.y);}
		I node operator * (Con node& o) Con {return node(x*o.x-y*o.y,x*o.y+y*o.x);}
	}A[4*X+5];int P,L,R[4*X+5];
	I void FFT(node *s,CI op)//FFT
	{
		RI i,j,k;node x,y,U,S;for(i=0;i^P;++i) i<R[i]&&(x=s[i],s[i]=s[R[i]],s[R[i]]=x,0);
		for(i=1;i^P;i<<=1) for(U=node(cos(Pi/i),op*sin(Pi/i)),j=0;j^P;j+=i<<1)
			for(S=1,k=0;k^i;++k,S=S*U) s[j+k]=(x=s[j+k])+(y=S*s[i+j+k]),s[i+j+k]=x-y;
	}
	I void Sqr(int *a,long long *res)//序列自我卷积
	{
		RI i;for(i=0;i<=X-2;++i) A[i]=a[i];
		P=1,L=0;W(P<=2*(X-2)) P<<=1,++L;for(i=0;i^P;++i) R[i]=(R[i>>1]>>1)|((i&1)<<L-1);//预处理
		for(FFT(A,1),i=0;i^P;++i) A[i]=A[i]*A[i];//平方
		for(FFT(A,-1),i=0;i<=2*(X-2);++i) res[i%(X-1)]+=A[i].x/P+0.1;//循环卷积下标取模
	}
}
I void Init() {RI i,x=1;for(i=0;i<X-1;++i) Lg[x]=i,x=1LL*x*PR%X;}//初始化Lg
int main()
{
	RI i;for(Init(),F.read(n),i=1;i<=n;++i) F.read(a[i]),a[i]&&++s[Lg[a[i]]];//读入,注意忽略a[i]=0
	for(Poly::Sqr(s,p),i=0;i<X-1;++i) p[(i<<1)%(X-1)]-=s[i];//除去自身贡献
	LL ans=0;for(i=1;i^X;++i) ans+=1LL*i*p[Lg[i]];return printf("%lld
",ans>>1),0;//枚举余数统计答案
}

(D):Twin Binary Trees(点此看题面

大致题意: 给定两棵完全相同的完全二叉树以及一个排列(p),在两棵二叉树最底层,从第一棵树第(i)个点向第二棵树第(p_i)个点连边(称作“特殊边”)。每对特殊边的价值是它们构成的环上所有点编号的乘积,求所有特殊边对的价值总和。

构成环上所有点的编号乘积,显然和(LCA)有着密切关系。

于是,容易想到枚举第一棵树中的(LCA)(设其为(x))去乱搞。

由于是(LCA),因此此时选择的两点必然在(x)的不同子树中,所以我们可以处理左子树中点的信息,然后询问右子树中点的答案,具体如下:

  • 枚举左子树中的点,找到它在另一棵树中的对应点,然后暴力上跳枚举到根路径上的每一个点(y),给(y)的权值加上(x)(y)路径上编号乘积。
  • 枚举右子树中的点,找到它在另一棵树中的对应点,然后暴力上跳枚举到根路径上的每一个点(y),给答案加上(x)右儿子到(y)路径上编号乘积(右儿子是为了避免重复计算(x)( imes)(y)的兄弟节点的权值( imes)(y)父节点的编号
  • 清除修改。

其实这应该也算一个比较常见的套路。

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N (1<<18)
#define X 1000000007
#define LT rt<<1,d+1
#define RT rt<<1|1,d+1
#define Inc(x,y) ((x+=(y))>=X&&(x-=X))
using namespace std;
int H,L,ans,p[N+5],V[N+5];
I void Work(CI rt,CI d,RI v)//处理左子树中点的信息
{
	if(v=1LL*v*rt%X,d==H) {RI x=p[rt-L]+L;W(x) v=1LL*v*x%X,Inc(V[x],v),x>>=1;}//更新权值
	else Work(LT,v),Work(RT,v);
}
I void Calc(CI rt,CI d,RI v)//询问右子树中点的答案
{
	if(v=1LL*v*rt%X,d==H) {RI x=p[rt-L]+L;W(x^1) v=1LL*v*x%X,ans=(1LL*v*(x>>1)%X*V[x^1]+ans)%X,x>>=1;}//更新答案
	else Calc(LT,v),Calc(RT,v);
}
I void Travel(CI rt=1,CI d=1)//枚举第一棵树上的LCA
{
	if(d==H) return;Work(LT,rt),Calc(RT,1),Work(LT,X-rt),Travel(LT),Travel(RT);
}
int main()
{
	RI i;for(scanf("%d",&H),L=1<<H-1,i=0;i^L;++i) scanf("%d",p+i),--p[i];//读入,方便起见将p减1
	return Travel(),printf("%d
",ans),0;
}

(E):Product Simulation(点此看题面

大致题意: 纯输出题。一个长度为(2 imes10^5)的数组,初始时(a_0=A,a_1=B)(A,Ble10^9),这两个数的值不告诉你)。你只可以通过+ i j k(a_k=a_i+a_j))和< i j k(a_k=[a_i<a_j]))两种操作,要求总操作次数在(2 imes 10^5)步以内,并使得对于任意的(A,B),都能得到(a_2=A imes B)

对于这种问题,自然容易想到倍增

首先考虑我们如何造出一个(1)来,若(A=B=0)则无论怎么操作序列都全为(0),否则(0<A+B),只要随便取一位和(A+B)比大小就可以得到(1)了。

然后考虑怎么求出(a[x] imes 2^i)(i)已知),不难发现只要执行(i)+ x x x,每次(a[x])都会乘上(2)(i)次之后就得到了所需值。

我们需要记录一个(ans),以及一个(now\_mul)表示倍增过程中当前的乘数。(即,(ans=now\_mul imes B),最后的(now\_mul)将会等于(A)

注意这些变量具体实现时都需要转化为某个(a[x]),这里只是为了方便阅读。

从大到小枚举(i),表示当前尝试将乘数(now\_mul)加上(2^i)(2^i)可以预处理),得到一个(new\_mul)

再新开一个变量(check=[new\_mul<A+1]),表示是否可以加。

注意到我们无法实现if语句,因此只能通过直接调用(check)的值来达成我们的目的。

考虑当(check=1)时,我们需要将(now\_mul)加上(2^i),将(ans)加上(B imes 2^i)

其实也就是将(now\_mul)加上(check imes 2^i),将(ans)加上((check imes B) imes 2^i)

如何求出一个数乘(2^i)的值在先前已经介绍过了,那么现在的问题就是如何求出(check imes B)

或许你会说,我们原本就是要实现两个数相乘,问题并没有得到丝毫简化。

但实际上,由于(check=0/1),此时其实已经非常可做了。

我们再次根据(B)来倍增,记录一个(res)和一个(now'),从大到小枚举(i)(这里的(i)和前面的(i)不同),尝试将(now')加上(2^i)得到(new')

(check'=[new\_mul<B+1]),我们要将(now')加上(check' imes 2^i),将(ans)加上((check imes check') imes 2^i)

问题又来了,(check imes check')怎么求?

由于这是两个(0/1)变量,它们相乘值为(1)等价于相加值为(2>1),即(check imes check'=[1<check+check'])

这样一来就真正做完了,实现起来可能有些细节,具体详见代码。

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define pb push_back
#define LN 30
#define P(x) (100+(x))//2^x所在位置
using namespace std;
vector<string> ans;
I void Pow(CI x,CI p)//a[9]=a[x]*(2^i)
{
	cout<<"+ 1000 "<<x<<" 9
";for(int i=1;i<=p;++i) puts("+ 9 9 9");//i次自加
}
I void Mul()//a[10]=a[8]*B (a[x]=0/1)
{
	//11:now_mul 12:new_mul 13:check 14:tmp
	puts("+ 1000 1000 10"),puts("+ 1000 1000 11");for(RI i=LN;~i;--i)//注意先清空
		cout<<"+ 11 "<<P(i)<<" 12
",puts("< 12 5 13"),Pow(13,i),puts("+ 11 9 11"),//尝试加上2^i并更新now_mul
		puts("+ 8 13 14"),puts("< 3 14 14"),Pow(14,i),puts("+ 10 9 10");//做0/1变量的乘法,然后更新结果
}
int main()
{
	//0:A 1:B 3:1 4:A+1 5:B+1 1000:0
	RI i;puts("37731"),puts("+ 0 1 3"),puts("< 1000 3 3"),puts("+ 0 3 4"),puts("+ 1 3 5");//先求出1,然后预处理出A+1和B+1
	for(puts("+ 3 100 100"),i=1;i<=LN;++i) cout<<"+ "<<P(i)-1<<" "<<P(i)-1<<" "<<P(i)<<endl;//初始化2的幂
	//2:ans 6:now_mul 7:new_mul 8:check 9,10:tmp
	for(i=LN;~i;--i) cout<<"+ 6 "<<P(i)<<" 7
",puts("< 7 4 8"),//尝试加上2^i
		Pow(8,i),puts("+ 6 9 6"),Mul(),Pow(10,i),puts("+ 2 9 2");//更新now_mul和答案
	return 0;
}

友情附赠一份检验答案的代码,非常简单:

#include<bits/stdc++.h>
#define Tp template<typename Ty>
#define Ts template<typename Ty,typename... Ar>
#define Reg register
#define RI Reg int
#define Con const
#define CI Con int&
#define I inline
#define W while
#define N 200000
using namespace std;
int n;long long a[N+5];
int main()
{
	scanf("%lld%lld",a,a+1),freopen("a.txt","r",stdin);
	RI x,y,z;char c;scanf("%d",&n);W(n--) cin>>c>>x>>y>>z,c=='+'?(a[z]=a[x]+a[y]):(a[z]=a[x]<a[y]);
	return cout<<"Expect: "<<a[0]*a[1]<<"
Find:   "<<a[2]<<endl,0;
}
原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC047.html