zoukankan      html  css  js  c++  java
  • WFLS Day1 Solution

    批注 2020-07-28 134101.png

    T1 linkstart

    Subtask 1

    显然,答案为

    [sum_{i=0}^{n-1}b^i mod k ]

    此时暴力枚举 (i) 的取值,运用快速幂求 (b^i) 即可。

    Subtask 2

    观察到 (k)是质数,想到运用等比数列求和公式解决。

    [S=sum_{i=0}^{n-1}b^i (1) ]

    则有:

    [b cdot S=sum_{i=1}^{n}b^i (2) ]

    ((2)-(1))可得:

    [(b-1)S=sum_{i=1}^{n}b^i-sum_{i=0}^{n-1}b^i=b^n-b^0 ]

    即:

    [S=frac{b^n-1}{b-1} ]

    快速幂求出 (b^n-1) ,并求 (b-1) 的逆元即可。

    Subtask 3

    可能很多人会受到等比数列求和公式的误导,直接想到 (Subtask 2) 的做法,而没有发现陷阱在于 (k) 并非质数,无法用逆元解决分母的 (b-1)

    这里提供两种时间复杂度为 (mathcal{O}(log n)) 的方法。

    法1:递推

    [p_i=b^{(2^i)} ]

    显然

    [p_i=p_{i-1}^2 ]

    上式可以递推出 (p_i)

    [sum_i=sum ^{2^i-1}_{k=0}b^k ]

    显然

    [sum_i=(p_{i-1}+1) imes sum_{i-1} ]

    上式可以递推出 (sum_i)

    对于答案,我们可以对 (n) 进行二进制分解,进行分段求和。定义 (base) 为目前做到哪一位。将 (i) 从高位往低位枚举,若 (n) 在第 (i) 位非空,则将 (base * sum_i) 计入答案,并更新此时做到的位数 (base) ,即 (base := base * p_i)

    下为xiong_6的代码。

    #include<bits/stdc++.h>
    using namespace std;
    namespace IO {
    #define _CCF_ 8388608
    #define getchar() (p1==p2&&(p2=(p1=Ibuf)+fread(Ibuf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
    #define putchar(c) (O-Obuf<_CCF_)?(*O++=c):(fwrite(Obuf,O-Obuf,1,stdout),O=Obuf,*O++=c)
    	char Ibuf[_CCF_],*p1=Ibuf,*p2=Ibuf,Obuf[_CCF_],*O=Obuf;
    	int f,ch,Onum[32],Ohd; long long k;
    	template<typename T>inline void read(T&x){
    		x=f=ch=0; while(!isdigit(ch)) f|=(ch=='-'),ch=getchar();
    		while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^'0'),ch=getchar();
    		f&&(x=-x);}
    	template<typename T>inline void write(T x){
    		if(x==0) return putchar('0'),void(); if(x<0) putchar('-'),x=-x;
    		while(x>0) k=x/10,Onum[++Ohd]=(x-(k<<1)-(k<<3))^'0',x=k;
    		while(Ohd>0) putchar(Onum[Ohd]),--Ohd;}
    	inline void _Exit0() {fwrite(Obuf,O-Obuf,1,stdout),exit(0);}
    } using namespace IO;
    long long n,b,K,t;
    long long bpow[100];//b^(2^i)
    long long bsum[100];//sigma(b^0,b^(2^i-1))
    long long base=1,ans;
    int main(){
    	read(t);
    	while(t--){
    		base=1,ans=0;
    		read(n);
    		read(b);
    		read(K);
    		b%=K;
    		bpow[0]=b;
    		for(long long i=1;i<=63;i++){
    			bpow[i]=bpow[i-1]*bpow[i-1];
    			bpow[i]%=K;
    		}
    		bsum[0]=1;
    		for(long long i=1;i<=63;i++){
    			bsum[i]=bsum[i-1]*(bpow[i-1]+1);
    			bsum[i]%=K;
    		}
    		for(long long i=63;i>=0;i--){
    			if((1ll<<i)&n){
    				ans+=base*bsum[i];
    				ans%=K;
    				base*=bpow[i];
    				base%=K;
    			}
    		}
    		write(ans);
    		putchar('
    ');
    	}
    	IO::_Exit0();
    }
    

    法2:矩阵快速幂

    验题人 fz 的方法,本来由于常数过大而超时,我觉得这个方法很不错,说服了 zzy 将时限改成了 (1.5s)

    (f_i) 为第 (i) 层节点数,(g_i) 为前 (i) 层节点数。

    则有递推式:

    [f_i=b * f_{i-1} (1) ]

    [g_i=f_i + g_{i-1} (2) ]

    ((1) (2)) 构造矩阵

    [left[ egin{matrix}b & 0\1 & 1\end{matrix} ight] *left[ egin{matrix}f_i\g_{i-1}\end{matrix} ight] =left[ egin{matrix}f_{i+1}\g_{i}\end{matrix} ight] ]

    所以由 (g_1)(f_2) 可矩阵快速幂得到答案。
    下为fz的代码。

    #include<cstdio>
    #include<cctype>
    using namespace std;
    typedef long long ll;
    inline ll readint(){
    	ll x=0;
    	char c=getchar();
    	bool f=0;
    	while(!isdigit(c)&&c!='-') c=getchar();
    	if(c=='-'){
    		f=1;
    		c=getchar();
    	}
    	while(isdigit(c)){
    		x=x*10+c-'0';
    		c=getchar();
    	}
    	return f?-x:x;
    }
    ll p;
    struct matrix{
    	ll a[2][2];
    	matrix operator *(matrix b){
    		matrix c;
    		for(int i=0;i<2;i++) for(int j=0;j<2;j++){
    			c.a[i][j]=0;
    			for(int k=0;k<2;k++)
    				c.a[i][j]=(c.a[i][j]+a[i][k]*b.a[k][j]%p)%p;
    		}
    		return c;
    	}
    };
    matrix ksm(matrix a,ll b){
    	matrix ans;
    	for(int i=0;i<2;i++) for(int j=0;j<2;j++) ans.a[i][j]=0;
    	for(int i=0;i<2;i++) ans.a[i][i]=1%p;
    	while(b>0){
    		if(b%2==1) ans=ans*a;
    		a=a*a;
    		b/=2;
    	}
    	return ans;
    }
    int main(){
    	int t=readint();
    	while(t--){
    		ll n,b;
    		n=readint();
    		b=readint();
    		p=readint();
    		matrix a;
    		a.a[0][0]=b%p;
    		a.a[0][1]=0;
    		a.a[1][0]=a.a[1][1]=1%p;
    		a=ksm(a,n);
    		printf("%lld
    ",a.a[1][0]);
    	}
    	return 0;
    }
    

    T2 dispute

    Subtask 1

    随便暴力。

    Subtask 2

    预处理出每个数的 (k) 次前驱(即迭代 “此数前一个出现的位置” (k) 次),然后查询时在 ([l,r]) 中枚举之。

    Subtask 3

    (last_i) 记录每个数上一次出现的位置。维护 (max_i) 表示 (last_1)(last_i) 的最大值。注意到,当 (L leq max_R) 时,区间内一定存在两个相同的数,而当 (max_R<L) 时,一定不存在。可以据此判断答案。

    Subtask 4

    做法多样。这里给出两种出题人认为较为简单的做法。

    1.可以选择优化 (Subtask 2) 的方法:

    即:找到每个数的 (k) 次前驱后,就变成了 (RMQ) 问题,在线用 (ST) 表解决即可。

    2.可以选择分块:

    看到 (5e4) 应该想到良心出题人(xiong_6)想让你们用分块过这个子任务。区间众数,分块,没啥好讲的吧。

    Subtask 5

    有的人可能看到 Subtask 3 就已经理解了。没错这题就这么水。结合 Subtask 2 和 Subtask 3 的方法即可得到正解。即维护每个数的 (k) 次前驱 (pre_i)(即迭代 “此数前一个出现的位置” (k) 次),维护 (maxp_i) 表示 (pre_1)(pre_i) 的最大值。然后查询时比较 (maxp_R)(L) 的大小即可。线性。

    下为xiong_6的代码。

    #include<bits/stdc++.h>
    using namespace std;
    namespace IO {
    #define _CCF_ 8388608
    #define getchar() (p1==p2&&(p2=(p1=Ibuf)+fread(Ibuf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
    #define putchar(c) (O-Obuf<_CCF_)?(*O++=c):(fwrite(Obuf,O-Obuf,1,stdout),O=Obuf,*O++=c)
    	char Ibuf[_CCF_],*p1=Ibuf,*p2=Ibuf,Obuf[_CCF_],*O=Obuf;
    	int f,ch,Onum[32],Ohd; long long k;
    	template<typename T>inline void read(T&x){
    		x=f=ch=0; while(!isdigit(ch)) f|=(ch=='-'),ch=getchar();
    		while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^'0'),ch=getchar();
    		f&&(x=-x);}
    	template<typename T>inline void write(T x){
    		if(x==0) return putchar('0'),void(); if(x<0) putchar('-'),x=-x;
    		while(x>0) k=x/10,Onum[++Ohd]=(x-(k<<1)-(k<<3))^'0',x=k;
    		while(Ohd>0) putchar(Onum[Ohd]),--Ohd;}
    	inline void _Exit0() {fwrite(Obuf,O-Obuf,1,stdout),exit(0);}
    } using namespace IO;
    long long n,q,K,l,r;
    long long sumans;
    long long a[2000011];
    vector<long long>pla[2000011];
    long long last[2000011];
    long long max_last[2000011];
    int main(){
    	read(n);
    	read(K);
    	read(q);
    	for(long long i=1;i<=n;i++){
    		read(a[i]);
    		pla[a[i]].push_back(i);
    	}
    	for(long long i=1;i<=n;i++){
    		if(pla[i].size()>=K){
    			for(long long j=K-1;j<pla[i].size();j++){
    				last[pla[i][j]]=pla[i][j-K+1];
    			}
    		}
    	}
    	for(long long i=1;i<=n;i++){
    		max_last[i]=max(max_last[i-1],last[i]);
    	}
    	while(q--){
    		read(l);
    		read(r);
    		l=(l+K*sumans-1)%n+1;
    		r=(r+K*sumans-1)%n+1;
    		sumans%=n;
    		if(max_last[r]>=l){
    			write(2);
    			putchar('
    ');
    			sumans+=2;
    		}
    		else{
    			write(1);
    			putchar('
    ');
    			sumans+=1;
    		}
    	}
    	IO::_Exit0();
    }
    

    T3 transport

    Subtask 1

    (LCA) , 暴力枚举。

    Subtask 2

    菊花图。分“从根到叶子”和“从叶子到叶子”的路径两种情况讨论。

    Subtask 3

    链。可以分别计算点和边的贡献:一个点会被其左边到右边的路径、从其自身出发的路径经过,而一个边只会被从其左边点到其右边点的路径经过。

    Subtask 4

    所有点权为0即可计算边的贡献:一条边会且只会被这样的两个点经过:一个在子树内,一个在子树外。这样就可以通过 子树内点数乘以子树外点数乘以边权 算出边的贡献。

    记录出入子树时间戳,或者直接回溯过程中求子树大小皆可。

    Subtask 5

    思路一样,分别计算点和边对答案的贡献。边的贡献在 Subtask4 中。对于一个点,他的贡献分三种:

    1.被从子树外到子树内的点经过

    这种情况答案为 子树外点数*子树内点数*点权

    2.此点为子树内两点的 (LCA)

    显然,这种情况会且只会在这两个点分别在其不同的儿子的子树内出现。令点 (i) 的子树大小为 (siz_i) ,令点 (i) 的儿子集合为 (S_i) ,那么点 (x) 的贡献为:

    [sum_{i∈S_x} sum_{j∈S_x and j<i} siz_i * siz_j ]

    由于需要线性算法,所以我们记录其每一个儿子子树大小的 和的平方 与 平方的和 然后利用这个公式得出答案:

    [(sum_{i=1}^nA_i)^2=sum_{i=1}^nA_i^2+2*sum_{i=1}^nsum_{j=1}^{i-1}A_iA_j ]

    3.由此点出发的路径

    一共有 ((n-1)) 条。

    这样就求出了答案。

    下为imzzy的代码。

    #include<bits/stdc++.h>
    #define rgi register int
    #define ll long long
    namespace IO {
    #define _CCF_ 8388608
    #define getchar() (p1==p2&&(p2=(p1=Ibuf)+fread(Ibuf,1,1<<21,stdin),p1==p2)?EOF:*p1++)
    #define putchar(c) (O-Obuf<_CCF_)?(*O++=c):(fwrite(Obuf,O-Obuf,1,stdout),O=Obuf,*O++=c)
    	char Ibuf[_CCF_],*p1=Ibuf,*p2=Ibuf,Obuf[_CCF_],*O=Obuf;
    	int f,ch,Onum[32],Ohd; ll k;
    	template<typename T>inline void read(T&x){
    		x=f=ch=0; while(!isdigit(ch)) f|=(ch=='-'),ch=getchar();
    		while(isdigit(ch)) x=(x<<1)+(x<<3)+(ch^'0'),ch=getchar();
    		f&&(x=-x);}
    	template<typename T>inline void write(T x){
    		if(x==0) return putchar('0'),void(); if(x<0) putchar('-'),x=-x;
    		while(x>0) k=x/10,Onum[++Ohd]=(x-(k<<1)-(k<<3))^'0',x=k;
    		while(Ohd>0) putchar(Onum[Ohd]),--Ohd;}
    	inline void _Exit0() {fwrite(Obuf,O-Obuf,1,stdout),exit(0);}
    } using namespace IO;
    
    const int mod=998244353,maxn=2000004;
    int n,a[maxn];
    struct EDGE{int v,w,nxt;}e[maxn<<1];
    int first[maxn],cnte;
    inline void addedge(int u,int v,int w) {e[++cnte]=(EDGE){v,w,first[u]},first[u]=cnte;}
    int ans,siz[maxn],sum[maxn];
    void dfs(int p,int f,int w) {int tmp=0;
    	for(rgi i=first[p];i;i=e[i].nxt) if(e[i].v!=f)
    		dfs(e[i].v,p,e[i].w),siz[p]+=siz[e[i].v],tmp=(tmp+(ll)siz[e[i].v]*siz[e[i].v])%mod;
    	tmp=((ll)siz[p]*siz[p]-tmp+mod)%mod*499122177%mod;
    	tmp=(tmp+(ll)siz[p]*(n-siz[p]-1)+n-1)%mod;
    	ans=(ans+(ll)tmp*a[p])%mod;
    	++siz[p];
    	ans=(ans+(ll)w*siz[p]%mod*(n-siz[p]))%mod;
    }
    signed main(){
    	int u,v,w; read(n);
    	for(rgi i=1;i<=n;++i) read(a[i]);
    	for(rgi i=1;i<n;++i) read(u),read(v),read(w),addedge(u,v,w),addedge(v,u,w);
    	dfs(1,0,0),write(ans);
    	_Exit0();
    }
    
  • 相关阅读:
    <记录> axios 模拟表单提交数据
    PHP 设计模式(一)
    CSS3中translate、transform和translation的区别和联系
    微信小程序 支付功能 服务器端(TP5.1)实现
    微信小程序 用户登录 服务器端(TP5.1)实现
    <记录> curl 封装函数
    <记录>TP5 关联模型使用(嵌套关联、动态排序以及隐藏字段)
    PHP/TP5 接口设计中异常处理
    TP5 自定义验证器
    高并发和大流量解决方案--数据库缓存
  • 原文地址:https://www.cnblogs.com/xiong-6/p/13387765.html
Copyright © 2011-2022 走看看