把博客园图标替换成自己的图标
把博客园图标替换成自己的图标end

【AtCoder】AtCoder Grand Contest 047 解题报告(A~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}\times 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\n",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\n",t),0;//枚举较短串统计答案
}

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

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

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

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

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

显然有一个变形:

\[(a_i\times a_j)\ mod\ P=x\Leftrightarrow (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\n",ans>>1),0;//枚举余数统计答案
}

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

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

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

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

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

  • 枚举左子树中的点,找到它在另一棵树中的对应点,然后暴力上跳枚举到根路径上的每一个点\(y\),给\(y\)的权值加上\(x\)\(y\)路径上编号乘积。
  • 枚举右子树中的点,找到它在另一棵树中的对应点,然后暴力上跳枚举到根路径上的每一个点\(y\),给答案加上\(x\)右儿子到\(y\)路径上编号乘积(右儿子是为了避免重复计算\(x\)\(\times\)\(y\)的兄弟节点的权值\(\times\)\(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\n",ans),0;
}

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

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

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

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

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

我们需要记录一个\(ans\),以及一个\(now\_mul\)表示倍增过程中当前的乘数。(即,\(ans=now\_mul\times 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\times 2^i\)

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

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

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

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

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

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

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

由于这是两个\(0/1\)变量,它们相乘值为\(1\)等价于相加值为\(2>1\),即\(check\times 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\n";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\n",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\n",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]<<"\nFind:   "<<a[2]<<endl,0;
}
posted @ 2020-08-16 12:42  TheLostWeak  阅读(249)  评论(0编辑  收藏  举报