多校联考8

rank 55 mark170我果然退步了

T1:辗转相减法适用于多数相减

T2:线段树维护区间min max/cdq分治

T3:状态压缩DP

T4:数论+推式子

T2:给你一段序列,让你求sigma(maxi*mini)(i是所有的子序列)

T50%:线段树直接维护答案,每个叶子节点代表当前val--R的maxmin,单调队列线性维护区间max,min的修改,然后在线段树上逆元*改变后的值就行,不知道为什么我T了

int n;//没有必要把数存起来吧?
int val[500000+10],inv[500000+10];
struct TREE
{
	int ls,rs;
	ll sum,lz;
}t[2000000];
int smi[500000+10],tmi,smx[500000+10],tmx,root,tot;
#define lson t[rt].ls
#define rson t[rt].rs
inline int qpow(int x,int y)
{
	int ans=1;
	while(y)
	{
		if(y&1)
		{
			ans=(ll)ans*x%MOD;
		}
		y>>=1;
		x=(ll)x*x%MOD;
	}
	return ans;
}
inline int Build(int rt,int l ,int r)
{
	if(!rt)rt=++tot;
	if(l==r)
	{
		t[rt].sum=0;//真的需要赋值1!
		t[rt].lz=1;return rt;
	}
	int mid=(l+r)>>1;
	lson=Build(lson,l,mid);
	rson=Build(rson,mid+1,r);
	t[rt].lz=1;
	return rt;
}
inline void Pushup(int rt)
{
	t[rt].sum=(t[lson].sum+t[rson].sum)%MOD;
}
inline void Pushdown(int rt)
{
	if(t[rt].lz!=1)
	{
		t[lson].lz=t[lson].lz*t[rt].lz%MOD;
		t[rson].lz=t[rson].lz*t[rt].lz%MOD;
		t[lson].sum=t[lson].sum*t[rt].lz%MOD;
		t[rson].sum=t[rson].sum*t[rt].lz%MOD;
		t[rt].lz=1;
	}
}
inline void Add(int rt,int l,int r,int L,int R,ll vl)
{
	if(L<=l&&r<=R)
	{
		t[rt].sum=t[rt].sum*vl%MOD;
		t[rt].lz=t[rt].lz*vl%MOD;return;
	}
	Pushdown(rt);
	int mid=(l+r)>>1;
	if(L<=mid)Add(lson,l,mid,L,R,vl);
	if(R>mid)Add(rson,mid+1,r,L,R,vl);
	Pushup(rt);
}
inline ll Query(int rt,int l,int r,int L,int R)
{
	if(L<=l&&r<=R)
	{
		return t[rt].sum;
	}
	int mid=(l+r)>>1;
	ll sm=0;
	if(L<=mid)sm+=Query(lson,l,mid,L,R);
	if(R>mid)sm+=Query(rson,mid+1,r,L,R);
	return (sm%MOD);//
}
inline void Update(int rt,int l,int r,int pos,ll vl)
{
	if(l==r)
	{
		t[rt].sum=vl;return;
	}
	Pushdown(rt);
	int mid=(l+r)>>1;
	if(pos<=mid)Update(lson,l,mid,pos,vl);
	else if(pos>mid)Update(rson,mid+1,r,pos,vl);
	Pushup(rt);
}
int main()
{
   //freopen("shuju.in","r",stdin);
   // freopen("dfs.txt","w",stdout);
  n=re();
  ll ans=0;
  _f(i,1,n)
  {
	  val[i]=re();
	  inv[i]=qpow(val[i],MOD-2);
	//  if(val[i-1]>val[i]&&(i!=1))op=0;
  }
root=Build(root,1,n);
  _f(i,1,n)
  {
	  while(tmi&&val[smi[tmi]]>val[i])//单调增
	  {
		  Add(root,1,n,smi[tmi-1]+1,smi[tmi],(ll)inv[smi[tmi]]*val[i]%MOD);
		  --tmi;
	  }
	  smi[++tmi]=i;
	  while(tmx&&val[smx[tmx]]<val[i])//这个就严格吧,不然有相等的会不会重复?倒也没事
	  {
		  Add(root,1,n,smx[tmx-1]+1,smx[tmx],(ll)inv[smx[tmx]]*val[i]%MOD);
		  --tmx;
	  }
	  smx[++tmx]=i;
	Update(root,1,n,i,(ll)val[i]*val[i]%MOD);
	  ans=(ans+Query(root,1,n,1,i))%MOD;
  }
  chu("%lld",ans);
    return 0;
}

郭yu哲学长口谕:不要写逆元,有可能不存在,直接算贡献就行

还有一种cdq分治的做法:把区间分为两个部分,每次查询两个区间加起来的贡献,其实非常好理解,4种情况,maxn[]minn[]提前处理好从mid边界到pos位置连续区间的最值,比如:假如min,max都来自左区间,我就从mid开始固定左边界,每次从右区间mid+1开始找到恰好满足max<max[lnow],min>min[lnow]的位置,一定是单调的,然后直接计算maxminlenth(当前右区间的长度就行);假如min来自左区间,max来自右区间,我固定右区间,把左区间的max从mid开始拓展到恰好满足max<maxnowr,但是我左区间的min值可能会大于右区间,所以再双指针把min从mid调到恰好min<minnowr

细节:(1)L,R的起始位置都是默认不合法(2)删除的时候先sum-再delete

const int MAX=5e5;ll mod=998244353;
ll A[MAX+5],maxn[MAX+5],minn[MAX+5],ans;
int n;
void cdq_solve(int l,int r)
{
    if(l==r)
    {
        ans=(ans+A[l]*A[l]%mod)%mod;return ;
    }
    int mid=(l+r)>>1;
    cdq_solve(l,mid),cdq_solve(mid+1,r);
    maxn[mid]=minn[mid]=A[mid];
    for(rint i=mid-1;i>=l;i--)
    {
        maxn[i]=max(maxn[i+1],A[i]);
        minn[i]=min(minn[i+1],A[i]);
    }
    //左区间555,是
    maxn[mid+1]=minn[mid+1]=A[mid+1];
    for(int i=mid+2;i<=r;++i)
    {
        maxn[i]=max(maxn[i-1],A[i]);
        minn[i]=min(minn[i-1],A[i]);
    }
    int L,R;
    ll sum;
    R=mid;
    for(int i=mid;i>=l;i--)//max min in L,固定左,拓展右边
    {
        while(R+1<=r&&maxn[i]>=maxn[R+1]&&minn[i]<=minn[R+1])R++;
        ans=(ans+(maxn[i]*minn[i]%mod)*(R-mid)%mod)%mod;//R-(mid+1)+1
    }
    L=mid+1;
    for(int i=mid+1;i<=r;++i)//max min in R
    {
        while((L-1)>=l&&maxn[i]>maxn[L-1]&&minn[i]<minn[L-1])L--;
        ans=(ans+(maxn[i]*minn[i]%mod)*(mid-L+1)%mod)%mod;
    }
    L=mid+1,R=mid,sum=0;
    for(rint i=mid;i>=l;i--)//最大值在左区间,最小值在右区间,固定左区间
    //算右区间确定的一段满足要求的区间
    {
        while(R+1<=r&&maxn[i]>=maxn[R+1])R++,sum=(sum+minn[R])%mod;
        while(L<=R&&minn[i]<=minn[L])sum=(sum+mod-minn[L])%mod,L++;
        ans=(ans+sum*maxn[i]%mod)%mod;
    }
    L=mid+1,R=mid,sum=0;
    for(rint i=mid+1;i<=r;++i)//最大值在有区间,最小在左区间,固定右区间
    {
        while(L-1>=l&&maxn[i]>maxn[L-1])L--,sum=(sum+minn[L])%mod;
        while(R>=L&&minn[i]<minn[R])sum=(sum-minn[R]+mod)%mod,R--;
        ans=(ans+sum*maxn[i]%mod)%mod;
    }
    return;
 
}
int main()
{
   //freopen("shuju.in","r",stdin);
   // freopen("dfs.txt","w",stdout);
    scanf("%d",&n);
    for(rint i=1;i<=n;++i)
    scanf("%lld",&A[i]);
    cdq_solve(1,n);
    chu("%lld",ans);
    return 0;
}

T3:你是一个快递员,给你一个有向图,有边权,代表路途时间,给你q个快递任务,要求从a点出发拿快递到达b点送快递,必须在l时间以后拿快递,r时间以前送快递才算完成一个任务,求最多完成多少任务(任务<=10,点<=20,时间<=1000000)

一个快递有没拿,拿了,送了3种状态表示,至少3^10的dp一位度表示,在哪个点也很必要,时间也是,但是空间不够,考虑把一维度放进dp的内容里,发现时间是可以的,如果定义f[i][j]是到达i状态在j点的最小时间,那么考虑拿快递,只要tim_spend<=r[qi]那就可以拿(在那等着),注意最短时间要是l[qi]和time_spend取max;送快递,只要tim_spend>=l[i]&&<=r[i],必须先拿再送,如果最短时间都赶不到,那肯定是送不到;

3进制状态压缩:(1)x数3进制第k位的数字:x/(3^k)%3,其实就和10进制完全一样,想不通就类比10进制(2)x k位0->1 +pow[k]

int n,m,q,mx;
int g[22][22],l[12],r[12],st[12],ed[12],dp[1000100][22];
int poow[12];
inline int Findans()
{
    int wei=0;
    f_(i,mx,0)//枚举所有可能达到的状态
    {
        _f(j,1,n)//枚举现在在哪个点
        {
            if(dp[i][j]<0x3f3f3f3f)
            {
                int dfs=0,clo=0;bool op=0;
               f_(k,q-1,0)
                {
                   if((i/(poow[k])%3)==2)++dfs;
                    if((i/(poow[k]))&&(!op))clo=k,op=1;//第一个有数位    
                }
                wei=max(wei,dfs);
                if(wei>=clo)return wei;
            }
        }
    }
    return wei;
}
int main()
{
   // freopen("shuju.in","r",stdin);
    memset(g,0x3f,sizeof(g));
    n=re(),m=re(),q=re();
    _f(i,1,m)
    {
        int ui=re(),vi=re(),ci=re();
        g[ui][vi]=min(g[ui][vi],ci);
    }
    _f(i,1,q)
    {
        st[i]=re(),ed[i]=re(),l[i]=re(),r[i]=re();
    }
    _f(i,1,n)g[i][i]=0;
    _f(k,1,n)
    {
        _f(i,1,n)
        {
            _f(j,1,n)
            {
                if(g[i][k]+g[k][j]<g[i][j])
                {
                    g[i][j]=g[i][k]+g[k][j];
                }
            }
        }
    }
    // _f(i,1,n)
    // {
    //     _f(j,1,n)chu("(%d->%d)%d\n",i,j,g[i][j]);
    // }
    // return 0;
    poow[0]=1;
    _f(i,1,q)poow[i]=poow[i-1]*3;
    memset(dp,0x3f,sizeof(dp));
      mx=poow[q]-1;
    dp[0][1]=0;//不用时间
  //  chu("mx:%d\n",mx);
    _f(j,0,mx)//枚举当前的状态,这个就可以保证我当前的状态是被更小的完全更新过得
    {
       // if(k==1) chu("当前是%d状态\n",j);
        _f(i,1,n)//点
        {
            //if(k==1) chu("在%d位置\n",i);
            _f(k,1,q)//枚举我要干哪个快递
            {
                int zt=j/poow[k-1]%3;
               // if(k==1)chu("try:%d\n",k);
               // if(k==1) chu("%d+%d %d\n",dp[j][i],g[i][st[k]],r[k]);
                if(!zt && (dp[j][i]+g[i][st[k]])<=r[k])//如果还没有这个快递,那我可以更新去拿它的,但是当前时间必<=send,我可以等者
                {
                   // max(dp[j][i]+g[i][st[k]],l[k])
                    dp[j+poow[k-1]][st[k]]=min(dp[j+poow[k-1]][st[k]], max(dp[j][i]+g[i][st[k]],l[k]));
                  //  chu("l:%d g[i][st[k]]:%d\n ",l[k],g[i][st[k]]);
                 //  if(k==1&&(j+poow[k-1])==1)  chu("去拿(req;%d)dp[%d][%d]%d<---dp[%d][%d];%d\n",k,j+poow[k-1],st[k],dp[j+poow[k-1]][st[k]],j,i,dp[j][i]);
                }
                else if(zt==1 && dp[j][i]+g[i][ed[k]]>=l[k]&&dp[j][i]+g[i][ed[k]]<=r[k])//那就送它,但是必须先拿到
                //而且还必须在截止之前赶到
                {
                    dp[j+poow[k-1]][ed[k]]=min(dp[j+poow[k-1]][ed[k]],dp[j][i]+g[i][ed[k]]);
                  //   if(k==2&&j==4) chu("送给(req;%d)dp[%d][%d]%d<---dp[%d][%d];%d\n",k,j+poow[k-1],ed[k],dp[j+poow[k-1]][ed[k]],j,i,dp[j][i]);
                }
            }
        }
    }
 
  chu("%d",Findans());
    return 0;
}

T4:矩阵乘法对次方式子的优化,还可以预处理出来base[]矩阵的二次幂作为多次询问的进一步优化,题面http://www.accoders.com/problem.php?cid=4079&pid=3

TLE 44

int n,q;
ll p,a[30],f[N];
struct node{
    ll c[21][21];
    node(){memset(c,0,sizeof(c));}
    void clear(){memset(c,0,sizeof(c));}
    void build()
    {
        clear();
        for(rint i=1;i<=n;++i)
        {
            c[i][1]=(a[i]<=0)?-a[i]:(p-a[i]);
            if(i<n)c[i][i+1]=1;
        }
        // for(rint i=2;i<=n;++i)
        // c[i-1][i]=1;
    }
    node operator*(const node &x)const
    {
        node y;
        for(rint i=1;i<=n;++i)
        for(rint j=1;j<=n;++j)
        for(rint k=1;k<=n;++k)
        {
            y.c[i][j]=(y.c[i][j]+c[i][k]*x.c[k][j]%p)%p;
        }
        return y;
    }
}A[33],ans,B;
int wen[N];
int main()
{
   //freopen("qiandao4.in","r",stdin);
   // freopen("dfs.txt","w",stdout);
    n=re(),q=re(),p=re();
    _f(i,1,n)a[i]=re();
    f[1]=(a[1]<=0)?(-a[1]):(p-a[1]);
    f[0]=a[0]=1;
    _f(i,1,q)wen[i]=re();
    _f(i,2,n)
    _f(j,1,i)
    f[i]=(f[i]-a[j]*f[i-j]%p+p)%p;
    _f(i,1,n)B.c[1][i]=f[n-i+1];//B:状态矩阵
    A[0].build();//A是转移矩阵,先弄好
    _f(i,1,32)A[i]=A[i-1]*A[i-1];//A[i]:A[1]^(2^i)
    for(rint i=1;i<=q;++i)
    {
        int x=wen[i];
        if(x<=n)
        {
            chu("%lld\n",f[x]);continue;
        }
        x-=n;
        ans=B;
        int base=0;
        while(x)
        {
            if(x&1)ans=ans*A[base];
            x>>=1;
            ++base;
        }
        chu("%lld\n",ans.c[1][1]);
    }
    return 0;
}
posted on 2022-07-30 20:57  HZOI-曹蓉  阅读(22)  评论(0编辑  收藏  举报