AtCoder Grand Contest 047

Preface

8/16:ABC solved


A - Integer Product

简单题。发现小数只有\(9\)位,因此我们把所有数乘上\(10^9\)后满足积是\(10^{18}\)的倍数即可

很显然可以记录\(2,5\)约数的个数然后暴力统计

注意用scanf读入实数会损失精度,因此要手写读入

#include<cstdio>
#include<iostream>
#include<cctype>
#define RI register int
#define CI const int&
using namespace std;
typedef long long LL;
int n,c[100][100],lim2,lim5; LL ans,ret; double x;
int main()
{
	RI i,j,p,q; for (scanf("%d\n",&n),i=1;i<=n;++i)
	{
		char ch; LL v=0; while (isdigit(ch=getchar())) v=v*10+ch-'0';
		if (ch=='\n') v*=(LL)1e9; else
		{
			int p=0; while (isdigit(ch=getchar())) v=v*10+ch-'0',++p;
			while (p<9) v*=10LL,++p;
		}
		int c2=0,c5=0;
		while (v%2==0) v/=2,++c2; while (v%5==0) v/=5,++c5;
		++c[c2][c5]; lim2=max(lim2,c2); lim5=max(lim5,c5);
		//printf("%d %d\n",c2,c5);
	}
	for (i=0;i<=lim2;++i) for (j=0;j<=lim5;++j)
	for (p=0;p<=lim2;++p) for (q=0;q<=lim5;++q)
	if (i+p>=18&&j+q>=18) 
	{
		if (i!=p||j!=q) ans+=1LL*c[i][j]*c[p][q];
		else ret+=(1LL*c[i][j]*(c[i][j]-1)>>1LL);
	}
	return printf("%lld",(ans>>1LL)+ret),0;
}


B - First Second

简单题。考虑两个串配对时一定是长的进行删除,然后我们容易发现配对的充要条件是:

较短串\(S\)的后\(|S|-1\)个字符和较长串的后\(|S|-1\)个字符相同且\(S\)的第一个字符在较长串前面出现过

很容易想到按照相同的后缀进行匹配,我们对后缀建立Trie树,同时记录下此时之前每个字符的出现情况

最后枚举较短串,在Trie树上匹配即可,总复杂度\(O(26\sum|S|)\)

#include<iostream>
#include<string>
#define RI register int
#define CI const int&
using namespace std;
const int N=1000005;
int n,len[N],c[26]; string s[N]; long long ans;
class Trie
{
	private:
		struct Trie_Node
		{
			int ch[26],ct[26];
		}node[N]; int rt,tot=1;
		#define next(x,y) node[x].ch[y]
		#define C(x,y) node[x].ct[y]
	public:
		inline void reroot(void)
		{
			rt=1; for (RI i=0;i<26;++i) if (c[i]) ++C(rt,i);
		}
		inline void insert(const char& ch)
		{
			if (next(rt,ch-'a')) rt=next(rt,ch-'a'); else rt=next(rt,ch-'a')=++tot;
			for (RI i=0;i<26;++i) if (c[i]) ++C(rt,i);
		}
		inline int query(const string& s,CI len)
		{
			rt=1; for (RI i=len-1;i;--i)
			if (next(rt,s[i]-'a')) rt=next(rt,s[i]-'a'); else return 0;
			return C(rt,s[0]-'a');
		}
		#undef next
		#undef C
}T;
int main()
{
	RI i,j; ios::sync_with_stdio(0); for (cin>>n,i=1;i<=n;++i)
	{
		for (cin>>s[i],len[i]=s[i].size(),j=0;j<len[i];++j) ++c[s[i][j]-'a'];
		for (T.reroot(),j=len[i]-1;~j;--j) --c[s[i][j]-'a'],T.insert(s[i][j]);
	}
	for (i=1;i<=n;++i) ans+=T.query(s[i],len[i]); return cout<<ans-n,0;
}


C - Product Modulo

挺套路的一道题。首先我们很容易发现乘法不好处理,我们考虑把它化成加法

\(P\)原根\(g\),我们设\(b_i=\log_g a_i\)(模意义下),假设我们枚举乘积\(x\),则:

\[a_i\times a_j=x\\ \Leftrightarrow g^{b_i}\times g^{b_j}=x\\ \Leftrightarrow g^{b_i+b_j}=g^{\log_g x}\\ \Leftrightarrow b_i+b_j=\log_g x\\ \]

很显然此时我们要求的就是一个和为定值的模意义下卷积,直接暴力把\(2P\)范围内的和暴力FFT出来然后把后面超过\(P-1\)的加到前面去即可

复杂度\(O(P\log P)\)

#include<cstdio>
#include<cmath>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,P=200003;
struct Complex
{
	double x,y;
	inline Complex(const double& X=0,const double& Y=0) { x=X; y=Y; }
	friend inline Complex operator + (const Complex& A,const Complex& B)
	{
		return Complex(A.x+B.x,A.y+B.y);
	}
	friend inline Complex operator - (const Complex& A,const Complex& B)
	{
		return Complex(A.x-B.x,A.y-B.y);
	}
	friend inline Complex operator * (const Complex& A,const Complex& B)
	{
		return Complex(A.x*B.x-A.y*B.y,A.x*B.y+A.y*B.x);
	}
	inline void operator /= (const double& p)
	{
		x/=p; y/=p;
	}
}b[P<<2]; int n,a[N],g,lg[N],rg[P],cnt,cur; long long ans;
inline int quick_pow(int x,int p,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%P) if (p&1) mul=1LL*mul*x%P; return mul;
}
inline int getroot(CI x=P)
{
	RI i,j; for (i=2;i<=x-2;++i) if ((x-1)%i==0) rg[++cnt]=i;
	for (i=2;;++i)
	{
		bool flag=1; for (j=1;j<=cnt;++j)
		if (quick_pow(i,rg[j])==1) { flag=0; break; }
		if (flag) return i;
	}
}
namespace Poly
{
	const double pi=acos(-1);
	int lim,p,rev[P<<2];
	inline void init(CI n)
	{
		for (lim=1,p=0;lim<=n;lim<<=1,++p);
		for (RI i=0;i<lim;++i) rev[i]=(rev[i>>1]>>1)|((i&1)<<p-1);
	}
	inline void FFT(Complex *f,CI opt)
	{
		RI i,j,k; for (i=0;i<lim;++i) if (i<rev[i]) swap(f[i],f[rev[i]]);
		for (i=1;i<lim;i<<=1)
		{
			Complex D(cos(pi/i),sin(pi/i)*opt);
			for (j=0;j<lim;j+=(i<<1))
			{
				Complex W(1,0); for (k=0;k<i;++k,W=W*D)
				{
					Complex x=f[j+k],y=f[i+j+k]*W;
					f[j+k]=x+y; f[i+j+k]=x-y;
				}
			}
		}
		if (!~opt) for (i=0;i<lim;++i) f[i]/=lim;
	}
};
int main()
{
	RI i; for (g=getroot(P),cur=1,i=0;i<P-1;++i,(cur*=g)%=P) lg[cur]=i;
	for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]),a[i]&&(b[lg[a[i]]].x+=1.0);
	for (Poly::init(P<<1),Poly::FFT(b,1),i=0;i<Poly::lim;++i) b[i]=b[i]*b[i];
	for (Poly::FFT(b,-1),i=1;i<P;++i) ans+=1LL*i*(long long)(b[lg[i]].x+b[lg[i]+P-1].x+0.5);
	for (i=1;i<=n;++i) ans-=1LL*a[i]*a[i]%P; return printf("%lld",ans>>1LL),0;
}


D - Twin Binary Trees

没想到完全二叉树可以暴力走路,被吊打了

我们发现可以在一棵树中枚举走到的\(\operatorname{LCA}\),然后两个端点一定在子树内

由于是二叉树我们发现此时两个点一定一个在左子树另一个在右子树

我们可以直接暴力枚举左子树内的每个点,然后找出子树内到达另一棵树的到根路径,将当前的路径点权之积打上标记

然后暴力枚举右子树内的每个点,在向另一棵树上向根走,根据标记来计算贡献即可

由于每个点只有枚举到它的祖先时才会去跳另一棵树,因此复杂度是有保证的

总复杂度\(O(nH^2)\)

#include<cstdio>
#define RI register int
#define CI const int&
using namespace std;
const int N=1<<18,mod=1e9+7;
int h,n,p[N],tag[N],ans,stk[N],top;
inline void travel2(CI x,int prod,CI opt)
{
	if (x==1) return; prod=1LL*prod*x%mod;
	if (opt) (tag[x]+=prod)%=mod; else (ans+=1LL*(x>>1)*tag[x^1]%mod*prod%mod)%=mod;
	travel2(x>>1,prod,opt);
}
inline void travel1(CI x,int prod,CI opt)
{
	prod=1LL*prod*x%mod;
	if (x>=n) return stk[++top]=n+p[x-n]-1,travel2(n+p[x-n]-1,prod,opt);
	travel1(x<<1,prod,opt); travel1(x<<1|1,prod,opt);
}
int main()
{
	RI i; for (scanf("%d",&h),n=1<<h-1,i=0;i<n;++i)
	scanf("%d",&p[i]); for (i=1;i<n;++i)
	{
		travel1(i<<1,i,1); travel1(i<<1|1,1,0);
		while (top) for (int x=stk[top--];x;x>>=1) tag[x]=0;
	}
	return printf("%d",ans),0;
}


E - Product Simulation

造计算机题。在陈指导一次次地指导下终于搞懂了

首先大体思路倍增,利用加法来实现乘法,然后用小于操作来实现if的功能,接下来我们直接讲具体实现:

\(a_2\)\(ans\)表示答案,\(a_3\)\(mul\)表示当前的倍增指数为多少(一定是正确的),\(a_4\)\(new\)表示进行尝试的倍增指数是多少(不一定是正确的),\(a_5=a_0+1\),然后预处理出\(2^i\)

容易发现我们可以用一个小于操作得到当前的\(new\)\(a_0+1\)的关系,记作\(c_1\)

然后我们现在要完成的就是把\(mul\)加上\(c_1\times 2^i\)

容易发现我们可以很容易地实现\(x\times 2^i\),具体地,直接加自己重复\(i\)次即可

那么我们接下来要完成的就是把\(ans\)加上\(c_1\times a_1\times 2^i\),我们发现我们不能直接得出\(c_1\times a_1\)

因此我们考虑如法炮制构造出\(a_1\times 2^i\),具体地,我们设\(tar=a_1\times 2^i+1\)

还是记\(a_6\)\(mul'\)表示当前的倍增指数为多少(一定是正确的),\(a_7\)\(new’\)表示进行尝试的倍增指数是多少(不一定是正确的)

同理我们可以比较\(new'\)\(tar\)的关系\(c_2\),那么此时我们只需要把答案加上\((c_1\operatorname{and} c_2)\times 2^j\)

容易发现\(\operatorname{and}\)操作可以先加起来再判断和\(2\)的大小,又转化成\(x\times 2^i\)的形式了

综上完成了本题,使用了\(73224\)次操作(被陈指导吊打了)

#include<cstdio>
#define RI register int
#define CI const int&
/*  ans:a[2]
	mul:a[3]
	new:a[4]
	a+1:a[5]
	mul':a[6]
	new':a[7]
	pw2[x]:a[8+x]*/
using namespace std;
int cnt=100,rb=100000;
inline void add(CI x,CI y,CI z) { printf("+ %d %d %d\n",x,y,z); }
inline void lower(CI x,CI y,CI z) { printf("< %d %d %d\n",x,y,z); }
inline void mul(CI x,CI y,CI z) // a[z]=a[x]*2^y
{
	++rb; add(rb,x,rb); for (RI i=1;i<=y;++i) add(rb,rb,rb+1),++rb; add(rb+1,rb,z);
}
int main()
{
	puts("73224"); RI i,j; add(0,1,++cnt); lower(cnt+1,cnt,cnt+1);
	++cnt; add(cnt,cnt+1,8); ++cnt;
	for (i=1;i<=60;++i) add(8+i-1,8+i-1,8+i);
	for (add(0,8,5),i=30;~i;--i)
	{
		add(4,8+i,4); lower(4,5,++cnt); int c1=cnt;
		mul(cnt,i,cnt+1); ++cnt; add(3,cnt,3);
		add(3,cnt+1,4); mul(1,i,cnt+1); ++cnt;
		add(cnt,8,cnt+1); ++cnt; int tar=cnt;
		add(cnt+1,cnt+1,6); add(cnt+1,cnt+1,7);
		for (j=60;~j;--j)
		{
			add(7,8+j,7); lower(7,tar,++cnt); int c2=cnt;
			add(c1,c2,++cnt); lower(8,cnt,cnt+1); ++cnt;
			mul(cnt,j,cnt+1); ++cnt; add(cnt,6,6); add(6,cnt+1,7);
		}
		add(2,6,2);
	}
	return 0;
}

Postscript

这场题目不错,如果比赛时打的话可能可以出4题左右吧

posted @ 2020-08-17 16:32  空気力学の詩  阅读(195)  评论(0编辑  收藏  举报