多校联考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;
}