• 【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;
    }
    
  • 相关阅读:
    [AWS] Export and Import Data from DynamoDB Table 从 DynamoDB 数据库中导入和导出数据
    [LeetCode] 944. Delete Columns to Make Sorted 删除列使其有序
    [LeetCode] 943. Find the Shortest Superstring 找到最短的超级字符串
    [LeetCode] 942. DI String Match 增减DI字符串匹配
    [LeetCode] 941. Valid Mountain Array 验证山形数组
    [LeetCode] 940. Distinct Subsequences II 不同的子序列之二
    [LeetCode] 939. Minimum Area Rectangle 面积最小的矩形
    [LeetCode] 938. Range Sum of BST 二叉搜索树的区间和
    [LeetCode] 937. Reorder Data in Log Files 日志文件的重新排序
    [LeetCode] 936. Stamping The Sequence 戳印序列
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/AtCoderAGC047.html
Copyright © 2020-2023  润新知