zoukankan      html  css  js  c++  java
  • 9.10vp总结

    爆0了。。。。
    先看B,这道题是我比较擅长的。
    尝试质因子分解失败,发现根据gcd连续段不超过log以后影魔即可。
    但发现线段树直接下传标记后多个log,想怎么去掉log。
    虽然线段树本质不同的长度只有log个,可以预处理。
    但是尝试失败了。标记无法合并。
    最后写了80分,挂了。。。。
    暴力没时间打了。。。
    题解:
    B
    最简单的一道题。
    根据经典结论,以一个点x为右端点,[1,x-1]为左端点的gcd值最多只有log种(新年的复读机)
    原因是如果gcd值在添加一个数后变化,则新的gcd值一定是原gcd值的约数,至少会/2
    实际上,可以把每一个右端点个相同gcd区间提取出来,问题转化成求把矩形[l,r][x,x]处乘上某个值v,求一个矩形内的点数。
    如果直接树套树会tle。考虑扫描线(影魔)。
    如果扫到[l,r]单点+,则以后这个矩形的贡献可能变化,不可做。
    如果扫到x区间+,且把询问[l,r]拆成前缀[1,r]-[1,l-1]后区间查[l,r],则可以做。
    可以使用bits简单维护。
    被卡常的代码:

    #include<bits/stdc++.h>
    using namespace std;
    #define mo 998244353
    #define N 200010
    struct no{
    	int x,i,t;
    };
    struct nn{
    	int x,y,i;
    }st[N],ss[N],qv[N*20];
    int n,q,a[N],tp,tt,ans[N],id[N],ii,b1[N],b2[N];
    vector<no>v[N],vv[N];
    int qp(int x,int y){
    	int r=1;
    	for(;y;y>>=1,x=1ll*x*x%mo)
    		if(y&1)r=1ll*r*x%mo;
    	return r;
    }
    void ad(int x,int y){
    	int va=qp(y,x);
    	for(int i=x;i<=n;i+=i&-i){
    		b1[i]=1ll*b1[i]*y%mo;
    		b2[i]=1ll*b2[i]*va%mo;
    	}
    }
    int qu(int x){
    	int ans=1,v1=1,v2=1;
    	for(int i=x;i;i-=i&-i){
    		v1=1ll*v1*b1[i]%mo;
    		v2=1ll*v2*b2[i]%mo;
    	}
    	v1=qp(v1,x+1);
    	v2=qp(v2,mo-2);
    	return 1ll*v1*v2%mo;
    }
    inline char nc(){
        return getchar();
    }
    inline int read(){
        char ch=nc();int sum=0;
        while(!(ch>='0'&&ch<='9'))ch=nc();
        while(ch>='0'&&ch<='9')sum=sum*10+ch-48,ch=nc();
        return sum;
    }
    signed main(){
    	freopen("easy.in","r",stdin);
    	freopen("easy.out","w",stdout);
    	scanf("%d%d",&n,&q);
    	for(int i=1;i<=n;i++){
    		a[i]=read();
    		b1[i]=b2[i]=1;
    	}
    	b1[0]=b2[0]=1;
    	for(int i=1;i<=q;i++){
    		int l=read(),r=read();
    		v[r].push_back((no){l,r,i});
    		v[l-1].push_back((no){l,r,-i});
    		ans[i]=1;
    	}
    	for(int i=1;i<=n;i++){
    		tt=0;
    		for(int j=1;j<=tp;j++)
    			st[j].i=__gcd(st[j].i,a[i]);
    		st[++tp]=(nn){i,i,a[i]};
    		int la=0;
    		for(int j=1;j<tp;j++)
    			if(st[j].i!=st[j+1].i){
    				ss[++tt]=(nn){la+1,st[j].y,st[j].i};
    				la=st[j].y;
    			}
    		ss[++tt]=(nn){la+1,i,a[i]};
    		tp=tt;
    		for(int j=1;j<=tp;j++){
    			st[j]=ss[j];
    			vv[i].push_back((no){st[j].x,st[j].y,st[j].i});
    		}
    	}
    	for(int i=1;i<=n;i++){
    		for(no x:vv[i]){
    			ad(x.x,x.t);
    			ad(x.i+1,qp(x.t,mo-2));
    		}
    		for(no x:v[i]){
    			if(x.t>=0)
    				ans[x.t]=1ll*(1ll*qu(x.i)*qp(qu(x.x-1),mo-2)%mo)*ans[x.t]%mo;
    			else
    				ans[-x.t]=1ll*(1ll*qp(1ll*qu(x.i)*qp(qu(x.x-1),mo-2)%mo,mo-2))*ans[-x.t]%mo;
    	 	}
    	}
    	for(int i=1;i<=q;i++)
    		printf("%d
    ",ans[i]);
    }
    

    A
    dp of dp
    有时,一些题目中,判定过程比较复杂,但是状态数较少,并且是个自动机。
    可以dp of dp,把状态压成一个数,然后在转移的时候顺便判定。
    这样子,只要知道是否达到终态,即可知道是否合法。
    这种类型的题目比如zjoi 麻将,codechef maxdigittree等。
    此题也可以dp of dp。
    首先考虑怎么判断合法。
    注意到每次我们选择的分界点必须是单调递增的,因为分解点前面的字符串长度会变为1。
    所以题目中给的过程相当于维护一个栈。每次向栈中插入两个字符。
    插入后判断是否清空整个栈。
    如果清空,则把第一个字符和前面的全部合并再插入第二个字符。
    可以使用dp解决。
    (f_{i,0/1,0/1})表示插入0/1后变为0/1是否可能。
    可以简单转移。
    对于计数,dp of dp。
    (tr_{s,0/1,0/1})表示s状态在插入0/1,0/1后能走到的状态。
    每次统计走到的状态即可。

    C
    看到题目可以想到dp。
    (f_i)表示前i是否可以被合法划分。
    显然可以得到(f_i|=f_j),其中(j+1...i)可以被合法划分。
    看数据范围十分大,所以不能直接dp。要发掘dp的性质。
    维护序列的前缀xor数组(t)
    引理1:如果把划分出的每一段作前缀xor,则每一段都由集合s的数线性组合而成。
    引理2:最后的序列段数(<=2^k)
    证明可以考虑每段值的前缀xor和pre。
    如果pre有两个位置(i,j)的值是相同的,则(i~j)的部分全部可以合并。
    引理3:如果两个位置(j<i),且(dp_j=1,pre_i=pre_j)
    显然j能更新到的,i也能更新到。
    所以对于每一个pre只需要保留编号最小的。
    (g_i)表示值为i且标号最小的位置。则显然g只有最多(2^{card(s)})个元素。
    可以使用bfs更新g。
    引理4:对于每种值,只需要取出编号最小的更新。
    更新操作相当于寻找后继,并且更新后继的g。
    编号更小的值找到的后继显然更小,可以更好的更新后面的g。
    所以可以使用优先队列进行更新。
    每个值只会被访问一次。所以时间复杂度正确。
    考虑分整个序列的xor和(s)讨论。
    如果(s>0),则bfs一下,看(g_s)是否存在。
    如果(s=0),则只需要查询(t)数组是否存在集合中的数。
    这是因为划分出的第一段的和肯定要是s内的数。
    由于(s xor s=0),所以后面的段的xor和也是(s),符合条件
    接下来的问题是如何进行区间xor/查询位置(x)后第一个值为(v)的数。
    分块。每个块维护bitset bt,(bt_x)表示(x)这个值是否出现。
    再维护两个标记(t1,t2)(t1)表示整个块被打的标记,(t2)为散块被打的标记
    在查询时顺序扫描一个数(x)后的每个块,如果在bitset查询出(x),则在内部暴力扫描,查看是否有(x)
    一个块的真实值可以通过它的标记(t1,t2)得知。
    否则需要进行区间更新。
    在区间更新的时候,(t1,t2)可以方便的计算。

    总结:
    题目都不是非常难,但是有挑战性。
    C的idea十分不错。

  • 相关阅读:
    STL之vector
    STL之string
    STL之map
    STL之queue
    STL之set
    Ubuntu 12.04 输入法托盘图标消失
    Hibernate:No row with the given identifier exists
    Java 数组
    Oracle 简单备份 批处理(BAT)
    Oracle DataBase Link
  • 原文地址:https://www.cnblogs.com/ctmlpfs/p/13644870.html
Copyright © 2011-2022 走看看