2024.10&11 总结
图论
【Luogu P8428】 Pastiri
题目描述
给定一棵
这些牧羊人很懒,只会看管离他最近的羊。当然如果有多个离他最近的羊,那么他会都看管。
当然,牧羊人可以和羊在同一个点上,但这样牧羊人只会看管这一个点上的那个羊。
求一种牧羊人的分配方案使得牧羊人总数最小,
解题思路
首先这题用树形
我们可以考虑贪心:将所有羊按深度从大到小排序,每次选取一个深度最大的羊,在它的祖先中选取一个能够管到它的节点,在该节点上放牧羊人。
这个贪心的正确性是显然的,因为放的越高能照顾到的也越多,且由于深度最大,无需放到别的子树去管别的节点。
我们得到了一个
我们每次都需要花
考虑优化查找,我们可以使用边定向,先求出每个节点
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
int n,m,t[500005],dis[500005],deep[500005],f[500005],t1[500005],k,s;
bool v1[500005],v[500005];
vector<int> a[500005],a1[500005];
bool cmp1(int q,int w){return deep[q]>deep[w];}
void dfs1(int x,int y)
{
if(!v1[x])dis[x]=n+1;
deep[x]=deep[y]+1;
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
dfs1(a[x][i],x);
dis[x]=min(dis[x],dis[a[x][i]]+1);
}
return;
}
void dfs2(int x,int y,int z)
{
dis[x]=min(dis[x],z);
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
dfs2(a[x][i],x,dis[x]+1);
}
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
if(dis[a[x][i]]==dis[x]+1)a1[a[x][i]].push_back(x);
if(dis[x]==dis[a[x][i]]+1)a1[x].push_back(a[x][i]);
}
return;
}
void dfs4(int x,int y,int z)
{
f[x]=z;
for(int i=0;i<a1[x].size();i++)
{
if(a1[x][i]==y)continue;
dfs4(a1[x][i],x,z);
}
return;
}
void dfs3(int x,int y)
{
if(f[x]==n+1)dfs4(x,y,x);
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
dfs3(a[x][i],x);
}
return;
}
void dfs5(int x,int y)
{
v[x]=1;
for(int i=0;i<a1[x].size();i++)
{
if(a1[x][i]==y||v[a1[x][i]])continue;
dfs5(a1[x][i],x);
}
return;
}
int main()
{
int x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
a[x].push_back(y),a[y].push_back(x);
}
for(int i=1;i<=m;i++)scanf("%d",&t[i]),v1[t[i]]=1;
for(int i=1;i<=n;i++)f[i]=n+1;
dfs1(1,0),dfs2(1,0,n+1),dfs3(1,0);
sort(t+1,t+m+1,cmp1);
for(int i=1;i<=m;i++)
{
if(v[t[i]])continue;
s++,t1[++k]=f[t[i]];
dfs5(f[t[i]],0);
}
sort(t1+1,t1+k+1);
printf("%d\n",s);
for(int i=1;i<=k;i++)printf("%d ",t1[i]);
return 0;
}
【Luogu P3976】 旅游
题目描述
为了提高智商,ZJY 准备去往一个新世界去旅游。这个世界的城市布局像一棵树,每两座城市之间只有一条路径可以互达。
每座城市都有一种宝石,有一定的价格。ZJY 为了赚取最高利益,她会选择从 A 城市买入再转手卖到 B 城市。
由于ZJY买宝石时经常卖萌,因而凡是 ZJY 路过的城市,这座城市的宝石价格会上涨。让我们来算算 ZJY 旅游完之后能够赚取的最大利润。(如 A 城市宝石价格为
解题思路
由于这题有对一条链加值的操作,所以肯定要用到树链剖分。
我们考虑如何维护题目所要求的的东西,用树链剖分将链划分为
考虑如何维护每一段的权值,这个东西很好做,我们只需维护一段的最大值和最小值,合并时用最大值减去最小值即可。
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
int maxx,minn,v1,v2;
}f[200005],t1[10005],t2[10005];
int n,m,v[50005],siz[50005],son[50005],deep[50005],fa[50005],top[50005],dfn[50005],num,d[200005],k1,k2,re[50005];
vector<int> a[50005];
void galaxy(int x,int v)
{
f[x].maxx+=v,f[x].minn+=v,d[x]+=v;
return;
}
void pushdown(int x)
{
if(!d[x])return;
int lc=(x<<1),rc=(x<<1)|1;
galaxy(lc,d[x]),galaxy(rc,d[x]),d[x]=0;
return;
}
void up(int x)
{
int lc=(x<<1),rc=(x<<1)|1;
f[x].maxx=max(f[lc].maxx,f[rc].maxx),f[x].minn=min(f[lc].minn,f[rc].minn);
f[x].v1=max(max(f[lc].v1,f[rc].v1),f[rc].maxx-f[lc].minn);
f[x].v2=max(max(f[lc].v2,f[rc].v2),f[lc].maxx-f[rc].minn);
return;
}
void build(int x,int l,int r)
{
if(l==r){f[x].maxx=f[x].minn=v[re[l]],f[x].v1=f[x].v2=0;return;}
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
build(lc,l,mid),build(rc,mid+1,r);
up(x);
return;
}
void modify(int x,int l,int r,int ql,int qr,int v)
{
if(ql<=l&&r<=qr){galaxy(x,v);return;}
pushdown(x);
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
if(ql<=mid)modify(lc,l,mid,ql,qr,v);
if(qr>mid)modify(rc,mid+1,r,ql,qr,v);
up(x);
return;
}
datay query(int x,int l,int r,int ql,int qr)
{
if(ql<=l&&r<=qr)return f[x];
pushdown(x);
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
datay h,g;
if(qr<=mid)h=query(lc,l,mid,ql,qr);
else if(ql>mid)h=query(rc,mid+1,r,ql,qr);
else
{
h=query(lc,l,mid,ql,qr);
g=query(rc,mid+1,r,ql,qr);
h.v1=max(max(h.v1,g.v1),g.maxx-h.minn);
h.v2=max(max(h.v2,g.v2),h.maxx-g.minn);
h.maxx=max(h.maxx,g.maxx);
h.minn=min(h.minn,g.minn);
}
return h;
}
void dfs1(int x,int y)
{
deep[x]=deep[y]+1,fa[x]=y,siz[x]=1;
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
dfs1(a[x][i],x);
siz[x]+=siz[a[x][i]];
if(siz[a[x][i]]>siz[son[x]])son[x]=a[x][i];
}
return;
}
void dfs2(int x,int y)
{
dfn[x]=++num,re[num]=x;
if(son[x]==0)return;
top[son[x]]=top[x],dfs2(son[x],x);
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y||a[x][i]==son[x])continue;
top[a[x][i]]=a[x][i],dfs2(a[x][i],x);
}
return;
}
void update(int x,int y,int z)
{
while(top[x]!=top[y])
{
if(deep[top[x]]<deep[top[y]])swap(x,y);
modify(1,1,n,dfn[top[x]],dfn[x],z);
x=fa[top[x]];
}
if(deep[x]>deep[y])swap(x,y);
modify(1,1,n,dfn[x],dfn[y],z);
return;
}
int ask(int x,int y)
{
int q=0,minn=1e9;k1=k2=q=0;
while(top[x]!=top[y])
{
if(deep[top[x]]<deep[top[y]]){swap(x,y),q^=1;}
if(!q)t1[++k1]=query(1,1,n,dfn[top[x]],dfn[x]);
else t2[++k2]=query(1,1,n,dfn[top[x]],dfn[x]);
x=fa[top[x]];
}
if(deep[x]>deep[y])swap(x,y),q^=1;
if(!q)t2[++k2]=query(1,1,n,dfn[x],dfn[y]);
else t1[++k1]=query(1,1,n,dfn[x],dfn[y]);
q=0;
for(int i=1;i<=k1;i++)q=max(q,max(t1[i].v2,t1[i].maxx-minn)),minn=min(minn,t1[i].minn);
for(int i=k2;i>=1;i--)q=max(q,max(t2[i].v1,t2[i].maxx-minn)),minn=min(minn,t2[i].minn);
return q;
}
int main()
{
int x,y,z;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&v[i]);
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
a[x].push_back(y);
a[y].push_back(x);
}
dfs1(1,0),top[1]=1,dfs2(1,0);
build(1,1,n);
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&z);
printf("%d\n",ask(x,y));
update(x,y,z);
}
return 0;
}
【Luogu P7897】 spxmcq
题目描述
给定一颗
在这个树上支持一种询问:
- 给定节点
和参数 ,假如 所有节点点权加 ,在这种情况下,求: 对于所有完全在 子树内并包含 的连通点集,权值之和最大可能为多少?
解题思路
先不管询问,我们来考虑怎么做。
我们可以发现得到一个这样的
考虑优化,由于
继续转化,设
那我们现在只需要维护一条边什么时候是连着的,随着
对于一条边
设
不能从叶子开始做,我们再开一个优先队列维护需要连上的边,每次把发生变化的未连边加进优先队列中,按
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
int x,p;
long long v,v1;
}t[1000005],d[1000005];
int n,m,dfn[1000005],deep[1000005],num,out[1000005],fa[1000005],v[1000005],f1[1000005],to[1000005];
long long minn=1e8,f2[1000005];
bool v1[1000005];
vector<int> a[1000005];
bool operator<(const datay &q,const datay &w)
{
if(q.v1!=w.v1)return q.v1>w.v1;
return q.p<w.p;
}
priority_queue<datay> l;
bool cmp1(datay q,datay w){return q.v<w.v;}
bool cmp2(datay q,datay w){return q.p<w.p;}
int lowbit(int x){return x&(-x);}
void modify1(int x,int y)
{
if(x==0)return;
while(x<=n)f1[x]+=y,x+=lowbit(x);
return;
}
void modify2(int x,long long y)
{
if(x==0)return;
while(x<=n)f2[x]+=y,x+=lowbit(x);
return;
}
int query1(int x)
{
int h=0;
while(x)h+=f1[x],x-=lowbit(x);
return h;
}
long long query2(int x)
{
long long h=0;
while(x)h+=f2[x],x-=lowbit(x);
return h;
}
void dfs(int x,int y)
{
deep[x]=deep[y]+1,fa[x]=y,dfn[x]=++num;
for(int i=0;i<a[x].size();i++)dfs(a[x][i],x);
out[x]=num;
return;
}
int search(int x)
{
if(to[x]!=x)to[x]=search(to[x]);
return to[x];
}
int main()
{
datay q;int x,y;
scanf("%d%d",&n,&m);
for(int i=2;i<=n;i++)scanf("%d",&x),a[x].push_back(i);
dfs(1,0);
for(int i=1;i<=n;i++)scanf("%d",&v[i]);
for(int i=1;i<=m;i++)scanf("%d%lld",&t[i].x,&t[i].v),t[i].p=i;
sort(t+1,t+m+1,cmp1);
for(int i=1;i<=n;i++)d[i].x=i,d[i].p=1,d[i].v=v[i],d[i].v1=-v[i],to[i]=i;
for(int i=1;i<=n;i++)modify1(dfn[i],1),modify1(dfn[fa[i]],-1),modify2(dfn[i],v[i]),modify2(dfn[fa[i]],-v[i]);
for(int i=2;i<=n;i++)l.push(d[i]);
for(int i=1;i<=m;i++)
{
while(l.size()!=0&&(l.top()).v1<=t[i].v)
{
if(v1[(l.top()).x]){l.pop();continue;}
q=l.top(),l.pop(),v1[q.x]=1;
x=search(fa[q.x]),y=search(q.x);
modify1(dfn[fa[q.x]],q.p),modify2(dfn[fa[q.x]],q.v);
if(x!=1)modify1(dfn[fa[x]],-q.p),modify2(dfn[fa[x]],-q.v);
to[y]=x;
if(x==1)continue;
q.x=x,q.p=query1(out[q.x])-query1(dfn[q.x]-1),q.v=query2(out[q.x])-query2(dfn[q.x]-1);
if(q.v%q.p==0)q.v1=-q.v/q.p;
else if(q.v<0)q.v1=-q.v/q.p+1;
else q.v1=-q.v/q.p;
l.push(q);
}
t[i].v1=query2(out[t[i].x])-query2(dfn[t[i].x]-1)+t[i].v*(query1(out[t[i].x])-query1(dfn[t[i].x]-1));
}
sort(t+1,t+m+1,cmp2);
for(int i=1;i<=m;i++)printf("%lld\n",t[i].v1);
return 0;
}
【GJOI 2024.11.13 T2】 二叉搜索树
题目描述
给出一棵
解题思路
法一
转换一下题意,按中序遍历给每个节点赋
每个点
设前一个点为
统计有多少个区间
时间复杂度
法二
注意到每次修改一个点的权值使得产生贡献的祖先一定是连续的,说明每次我们只需统计自己节点的哪段祖先发生了变化。
可以只用树状数组维护,每次树上倍增求贡献。
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
struct datay
{
int v,lc,rc;
}a[200005];
int n,m,dfn1[200005],num1,dfn[200005],num,pre[200005],out[200005],siz[200005],son[200005],fa[200005],top[200005],deep[200005],f[800005],maxx[800005],d[800005],re[200005],t[200005];
void dfs1(int x,int y)
{
if(x==0)return;
deep[x]=deep[y]+1,fa[x]=y;
pre[x]=num+1,dfs1(a[x].lc,x),dfn[x]=++num,re[num]=x,dfs1(a[x].rc,x),out[x]=num;
siz[x]=siz[a[x].lc]+siz[a[x].rc]+1;
if(siz[a[x].lc]>siz[a[x].rc])son[x]=a[x].lc;
else son[x]=a[x].rc;
return;
}
void dfs2(int x)
{
if(x==0)return;
dfn1[x]=++num1;
if(son[x]==0)return;
top[son[x]]=top[x],dfs2(son[x]);
if(son[x]==a[x].rc)top[a[x].lc]=a[x].lc,dfs2(a[x].lc);
if(son[x]==a[x].lc)top[a[x].rc]=a[x].rc,dfs2(a[x].rc);
return;
}
void galaxy(int x,int v)
{
maxx[x]+=v,d[x]+=v;
return;
}
void pushdown(int x)
{
if(d[x]==0)return;
int lc=(x<<1),rc=(x<<1)|1;
galaxy(lc,d[x]),galaxy(rc,d[x]),d[x]=0;
return;
}
void up(int x,int lc,int rc)
{
if(maxx[lc]==maxx[rc])maxx[x]=maxx[lc],f[x]=f[lc]+f[rc];
else if(maxx[lc]>maxx[rc])maxx[x]=maxx[lc],f[x]=f[lc];
else maxx[x]=maxx[rc],f[x]=f[rc];
return;
}
void modify(int x,int l,int r,int ql,int qr,int v)
{
if(ql<=l&&r<=qr){galaxy(x,v);return;}
pushdown(x);
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
if(ql<=mid)modify(lc,l,mid,ql,qr,v);
if(qr>mid)modify(rc,mid+1,r,ql,qr,v);
up(x,lc,rc);
return;
}
int LCA(int x,int y)
{
while(top[x]!=top[y])
{
if(deep[top[x]]<deep[top[y]])swap(x,y);
x=fa[top[x]];
}
if(deep[x]>deep[y])swap(x,y);
return x;
}
void update(int x,int z)
{
while(x)
{
modify(1,1,n,dfn1[top[x]],dfn1[x],z);
x=fa[top[x]];
}
return;
}
void build(int x,int l,int r)
{
if(l==r){f[x]=1;return;}
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
build(lc,l,mid),build(rc,mid+1,r);
f[x]=f[lc]+f[rc];
return;
}
int main()
{
int x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%d%d",&a[i].lc,&a[i].rc);
for(int i=1;i<=n;i++)scanf("%d",&a[i].v);
dfs1(1,0),top[1]=1,dfs2(1),build(1,1,n);
for(int i=2;i<=n;i++)t[i]=LCA(re[i-1],re[i]);
for(int i=2;i<=n;i++)
if(i>1&&a[re[i-1]].v>a[re[i]].v)update(t[i],-1);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
x=dfn[x];
if(x>1&&a[re[x-1]].v>a[re[x]].v&&a[re[x-1]].v<=y)update(t[x],1);
if(x>1&&a[re[x-1]].v<=a[re[x]].v&&a[re[x-1]].v>y)update(t[x],-1);
if(x<n&&a[re[x]].v>a[re[x+1]].v&&y<=a[re[x+1]].v)update(t[x+1],1);
if(x<n&&a[re[x]].v<=a[re[x+1]].v&&y>a[re[x+1]].v)update(t[x+1],-1);
a[re[x]].v=y;
if(maxx[1]!=0)printf("0\n");
else printf("%d\n",f[1]);
}
return 0;
}
【GJOI 2024.11.14 T4】 大头问题
题目描述
给出一个
解题思路
首先观察数据范围,注意到
我们考虑把
那我们可以从每种选边方案的贡献数来考虑,我们枚举选的
因为两条边可能选同一个顶点,重点在于如何求出
首先,若选
没有选重时,分类讨论
想一个简单的三元环计数方法:给点赋权,按点权大小给边定向,我们只需要找到自己能到的距离为
瓶颈在于点的度数,我们可以来根号分治。
我们的点权我们赋为点的度数,那么讨论每次枚举的直接到达的节点
讨论完
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
long long n,m,k,p[200005];
bool v[100005];
vector<long long> a[100005],a1[100005];
long long poww(long long x,long long y)
{
long long h=1;
while(y)
{
if(y&1)h=(h*x)%mod;
x=(x*x)%mod,y>>=1;
}
return h;
}
long long solve1(){return m*p[n-2]%mod;}
long long solve2()
{
long long h=m*(m-1)%mod,s=0,q;
for(int i=1;i<=n;i++)
{
q=a[i].size();
h=(h-q*(q-1)%mod+mod)%mod;
if(n>=3)s=(s+((q*(q-1)%mod)*p[n-3])%mod)%mod;
}
if(n>=4)s=(s+h*p[n-4]%mod+mod)%mod;
return s;
}
long long solve3()
{
long long h=0,x,h1=0,s=0,h2=m*(m-1)*(m-2)%mod,h3=0;
for(int i=1;i<=n;i++)
for(int j=0;j<a[i].size();j++)
if(a[i].size()>a[a[i][j]].size()||(a[i].size()==a[a[i][j]].size()&&i<a[i][j]))a1[i].push_back(a[i][j]);
for(int i=1;i<=n;i++)
{
for(int j=0;j<a1[i].size();j++)v[a1[i][j]]=1;
for(int j=0;j<a1[i].size();j++)
{
x=a1[i][j];
for(int u=0;u<a1[x].size();u++)h+=v[a1[x][u]];
}
for(int j=0;j<a1[i].size();j++)v[a1[i][j]]=0;
}
for(int i=1;i<=n;i++)
for(int j=0;j<a[i].size();j++)h1=(h1+((long long)(a[i].size()-1))*(a[a[i][j]].size()-1)%mod)%mod;
h1=h1*poww(2,mod-2)%mod,h1=(h1-(h*3)%mod+mod)%mod;
for(int i=1;i<=n;i++)
for(int j=0;j<a[i].size();j++)
{
x=a[i].size()+a[a[i][j]].size()-1;
h3=(h3+(m-x)*(a[i].size()-1)%mod)%mod;
}
h3=h3*poww(2,mod-2)%mod;
h3=(h3-h1+mod)%mod;
if(n>=3)s=(s+((h*6)%mod)*p[n-3]%mod)%mod;
if(n>=4)s=(s+(h1*6%mod)*p[n-4]%mod)%mod;
if(n>=4)s=(s+(h3*6%mod)*p[n-5]%mod)%mod;
h2=(h2-(h1+h+h3)*6%mod+mod)%mod,h1=0;
for(int i=1;i<=n;i++)
if(a[i].size()>=2)h1=(h1+((long long)(a[i].size())*(a[i].size()-1)%mod)*(a[i].size()-2)%mod)%mod;
if(n>=4)s=(s+h1*p[n-4]%mod)%mod;
h2=(h2-h1+mod)%mod;
if(n>=6)s=(s+h2*p[n-6]%mod)%mod;
return s;
}
int main()
{
long long x,y;
scanf("%lld%lld%lld",&n,&m,&k),p[0]=1;
for(int i=1;i<=200000;i++)p[i]=p[i-1]*2%mod;
for(int i=1;i<=m;i++)
{
scanf("%lld%lld",&x,&y);
a[x].push_back(y),a[y].push_back(x);
}
if(n==1){printf("0");return 0;}
if(k==1){printf("%lld",solve1());return 0;}
if(k==2){printf("%lld",(solve1()+solve2()+mod)%mod);return 0;}
if(k==3){printf("%lld",(solve1()+solve2()*3+solve3()+mod*5)%mod);return 0;}
return 0;
}
【Luogu P11191】 超级演出
题目描述
巡准备了一场超级演出。舞台和候场室可以看作一个包含
舞台为
巡可以对一个候场室
- 如果这个候场室的剧团还没有出场,并且存在一条
的路径上没有其余候场的剧团。那么这个剧团就会沿着这条路径到达舞台进行演出,随后退场。注意:一个剧团退场后不会重新回到候场室。 - 否则,这个命令被认为是无效的。
巡有一个命令序列
注意:每次询问相互独立,也就是说,每次询问之前,每个候场室都恰有一个剧团进行等待,其中
解题思路
我们考虑对每个命令
考虑如何求出
考虑优化,由于每次都会找所有能到达的节点会导致超时,我们可以在做完一个节点后给所有能到达它的节点打个标记,在更新这些节点时可以快速处理,但由于出入度都可能很大,时间复杂度还是
注意边数
每次只用更新一个节点的
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
int l,r,v,p;
}t[400005];
int qwe,n,m,k,m1,block,pre[400005],d[400005],f[400005],tim[400005];
bool v[400005],v1[400005];
vector<int> a[400005],b[400005];
bool cmp1(datay q,datay w){return q.r<w.r;}
bool cmp2(datay q,datay w){return q.p<w.p;}
int lowbit(int x){return x&(-x);}
void modify(int x,int y)
{
x=k+1-x;
if(x==0)return;
while(x<=400000)f[x]+=y,x+=lowbit(x);
return;
}
int query(int x)
{
x=k+1-x;
int h=0;
while(x)h+=f[x],x-=lowbit(x);
return h;
}
void solve1(int x)
{
for(int i=0;i<b[x].size();i++)tim[b[x][i]]=max(tim[b[x][i]],pre[x]);
return;
}
void solve(int x,int y)
{
if(v1[x]){modify(pre[x],-1),pre[x]=y,modify(y,1);solve1(x);}
if(v[x])
{
modify(pre[x],-1);
pre[x]=max(pre[x],tim[x]);
modify(pre[x],1),solve1(x);
return;
}
modify(pre[x],-1);
for(int i=0;i<a[x].size();i++)pre[x]=max(pre[x],pre[a[x][i]]);
modify(pre[x],1);
solve1(x);
return;
}
int main()
{
int x,y;
scanf("%d%d%d%d%d",&qwe,&n,&m,&k,&m1),block=sqrt(n);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y),a[x].push_back(y);
if(y==1)v1[x]=1;
}
for(int i=1;i<=n;i++)
if(a[i].size()>block)v[i]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<a[i].size();j++)
if(v[i])b[a[i][j]].push_back(i);
for(int i=1;i<=k;i++)scanf("%d",&d[i]);
for(int i=1;i<=m1;i++)scanf("%d%d",&t[i].l,&t[i].r),t[i].p=i;
sort(t+1,t+m1+1,cmp1),modify(0,n);
for(int i=1;i<=m1;i++)
{
for(int j=t[i-1].r+1;j<=t[i].r;j++)solve(d[j],j);
t[i].v=query(t[i].l);
}
sort(t+1,t+m1+1,cmp2);
for(int i=1;i<=m1;i++)printf("%d\n",n-t[i].v-1);
return 0;
}
【AGC008F】 Black Radius
题目描述
Snuke 君有一棵
两个状态不同当且仅当存在一个节点,它在两个状态中不同色,同时
解题思路
方案最多有
我们将一种方案通过
先来考虑全部点都是喜欢的点的情况,首先我们不考虑全被染黑的情况,以节点
同时要保证
由于
考虑当
那么此时只是限制了下限,我们最小只能到一个包含了喜欢的点的子树的最大深度
我们只需要求出三个数组即可,使用换根
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
int n,root1,root2,maxx1[200005],maxx2[200005],d[200005],t[200005],f[200005],siz[200005];
long long s=0;
vector<int> a[200005];
string b;
void dfs1(int x,int y)
{
siz[x]=(b[x]=='1');
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
dfs1(a[x][i],x),siz[x]+=siz[a[x][i]];
if(maxx1[x]<=maxx1[a[x][i]]+1)maxx2[x]=maxx1[x],maxx1[x]=maxx1[a[x][i]]+1;
else if(maxx2[x]<maxx1[a[x][i]]+1)maxx2[x]=maxx1[a[x][i]]+1;
if(siz[a[x][i]])d[x]=min(d[x],maxx1[a[x][i]]+1);
else d[x]=min(d[x],d[a[x][i]]+1);
}
return;
}
void dfs2(int x,int y,int z1)
{
if(maxx1[x]<=z1)maxx2[x]=maxx1[x],maxx1[x]=z1;
else if(maxx2[x]<z1)maxx2[x]=z1;
int z;
if(y&&(siz[1]-siz[x]))d[x]=min(d[x],z1);
if(b[x]=='1')s+=min(maxx1[x],maxx2[x]+2);
else if(min(maxx1[x],maxx2[x]+2)>=d[x])s+=min(maxx1[x],maxx2[x]+2)-d[x];
for(int i=a[x].size()-1;i>=0;i--)
t[a[x][i]]=max(((i!=a[x].size()-1)?t[a[x][i+1]]:z1),((a[x][i]!=y)?maxx1[a[x][i]]+1:0));
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
z=maxx1[a[x][i]]+1;
if(i!=a[x].size()-1)dfs2(a[x][i],x,max(z1,t[a[x][i+1]])+1);
else dfs2(a[x][i],x,z1+1);
z1=max(z1,z);
}
return;
}
int main()
{
memset(d,0x3F,sizeof(d));
int x,y;
scanf("%d",&n);
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
a[x].push_back(y),a[y].push_back(x);
}
cin>>b,b=' '+b;
dfs1(1,0);
dfs2(1,0,0);
cout<<s+1;
return 0;
}
【Luogu P7831】 Travelling Merchant
题目描述
一个国家有
第
他希望自己可以永远不停的游走下去,于是他想知道从任意一个城市出发至少需要多少元初始资产,
解题思路
注意到是资产不少于
考虑暴力,每次从点
我们考虑倒着走,这样答案为
我们观察上面我们的
我们发现对于一个大于之前所有边的
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
struct edge
{
long long x,y,z,q;
}b[200005];
struct datay
{
long long pre,to,nex,las,v1,v2;
}a[200005];
long long n,m,num,head[200005],f[200005],d[200005];
bool v[200005];
queue<int> l;
bool cmp1(edge q,edge w){return q.z>w.z;}
void add(int x,int y,int z,int q)
{
a[++num].to=y,a[num].v1=z,a[num].v2=q,a[num].pre=x;
a[head[x]].las=num,a[num].las=0,a[num].nex=head[x],head[x]=num;
return;
}
void del(int x)
{
v[x]=true;
if(a[x].las==0)head[a[x].pre]=a[x].nex,a[a[x].nex].las=0;
else a[a[x].las].nex=a[x].nex,a[a[x].nex].las=a[x].las;
return;
}
int main()
{
int x,y;
memset(f,0x3F,sizeof(f)),memset(v,false,sizeof(v));
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++)scanf("%lld%lld%lld%lld",&b[i].x,&b[i].y,&b[i].z,&b[i].q);
sort(b+1,b+m+1,cmp1);
for(int i=1;i<=m;i++)add(b[i].y,b[i].x,b[i].z,b[i].q),d[b[i].x]++;
for(int i=1;i<=n;i++)if(!d[i])l.push(i);
for(int i=1;i<=m;i++)
{
while(l.size())
{
x=l.front(),l.pop();
for(int j=head[x];j!=0;j=a[j].nex)
{
f[a[j].to]=min(f[a[j].to],max(f[a[j].pre]-a[j].v2,a[j].v1));
d[a[j].to]--;
if(d[a[j].to]==0)l.push(a[j].to);
del(j);
}
}
if(v[i])continue;
f[a[i].to]=min(f[a[i].to],a[i].v1);
del(i),d[a[i].to]--;
if(d[a[i].to]==0)l.push(a[i].to);
}
for(int i=1;i<=n;i++)printf("%lld ",(f[i]>=1e11)?(-1):f[i]);
return 0;
}
动态规划
【Luogu P10681】 奇偶矩阵 Tablica
题目描述
考虑只包含
我们称满足以下条件的矩阵是好的:
, ; , 。
求出
解题思路
法一
由于矩阵只包含
很明显,我们构造出了一个二分图,若这个图满足题目要求有两个条件,每个点不是独立的且每个连通块必须是一条链或一个环。
注意每个连通块在左右两边所占的个数最多只差
法二
我们可以枚举有多少行、列和分别为
我们可以
首先给每行每列安排为
我们看成这样一个问题:有
我们考虑将其排成一个序列,共有
可能会有两种重复,第一种为出现
第二种为出现
总答案
ABC273G 和这题差不多,只是每个点可能为
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
long long n,m,C[3005][3005],p[10005],p1[10005],a,b,c,d,s,f[10005];
long long poww(long long x,long long y)
{
long long h=1;
while(y)
{
if(y&1)h=(h*x)%mod;
x=(x*x)%mod,y>>=1;
}
return h;
}
int main()
{
long long x,s1=0;
scanf("%lld%lld",&n,&m),p[0]=p1[0]=f[0]=1;
for(int i=1;i<=10000;i++)p[i]=p[i-1]*2%mod,p1[i]=poww(p[i],mod-2),f[i]=(f[i-1]*i)%mod;
for(int i=0;i<=3000;i++)C[i][i]=C[i][0]=1;
for(int i=1;i<=3000;i++)
for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
for(int i=0;i<=n;i++)
{
a=i,b=n-i,d=a+2*b-m,c=m-d,x=min(b,d),s1=0;
if(c<0||d<0)continue;
for(int j=0;j<=x;j++)
s1=(s1+((j&1)?(-1):1)*(((C[b][j]*C[d][j]%mod)*f[j]%mod)*(f[c+2*d-2*j]*p1[b+d-j]%mod)%mod)%mod)%mod;
s=(s+s1*(C[n][a]*C[m][c]%mod)%mod)%mod;
}
printf("%lld",(s+mod)%mod);
return 0;
}
【Luogu P9197】摩天大楼
题目描述
将互不相同的
假设排列为
求满足题意的排列的方案数对
解题思路
题目中有绝对值不好处理,我们考虑将它放到平面直角坐标系中看一看,每个点为
对于
对于这个东西我们如何处理呢?我们可以使用插入
先将
若新加进来的
若新加进来的
若新加进来的
但是有一个很重要的一点:处于两端的点不会延伸折线,所以我们还要开两维
核心
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
long long n,m,a[1005],f[2][105][1005][2][2],s;
bool cmp(int q,int w){return q>w;}
inline void dijah(long long &q,long long w)
{
q=(q+w>=mod)?(q+w-mod):(q+w);
return;
}
int main()
{
int q,w,e;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
sort(a+1,a+n+1,cmp);
f[1][1][0][0][0]=f[1][1][0][0][1]=f[1][1][0][1][0]=f[1][1][0][1][1]=1;
for(register int i=1;i<n;i++)
{
e=i&1,q=e^1;
for(register int k=1;k<=i;k++)
for(register int j=0;j<=m;j++)
for(register int q1=0;q1<2;q1++)
for(register int q2=0;q2<2;q2++)
{
if(!f[e][k][j][q1][q2])continue;
w=j+(2*k-q1-q2)*(a[i]-a[i+1]);
if(w>m){f[e][k][j][q1][q2]=0;continue;}
if(k>1)dijah(f[q][k+1][w][q1][q2],(k-1)*f[e][k][j][q1][q2]%mod);
if(!q1)dijah(f[q][k+1][w][q1][q2],f[e][k][j][q1][q2]),dijah(f[q][k+1][w][1][q2],f[e][k][j][q1][q2]);
if(!q2)dijah(f[q][k+1][w][q1][q2],f[e][k][j][q1][q2]),dijah(f[q][k+1][w][q1][1],f[e][k][j][q1][q2]);
if(k>1)dijah(f[q][k][w][q1][q2],2*(k-1)*f[e][k][j][q1][q2]%mod);
if(!q1)dijah(f[q][k][w][q1][q2],f[e][k][j][q1][q2]),dijah(f[q][k][w][1][q2],f[e][k][j][q1][q2]);
if(!q2)dijah(f[q][k][w][q1][q2],f[e][k][j][q1][q2]),dijah(f[q][k][w][q1][1],f[e][k][j][q1][q2]);
if(k>1)dijah(f[q][k-1][w][q1][q2],(k-1)*f[e][k][j][q1][q2]%mod);
f[e][k][j][q1][q2]=0;
}
}
for(int i=0;i<=m;i++)dijah(s,f[(n&1)][1][i][1][1]);
printf("%lld",s);
return 0;
}
【ARC118E】 Avoid Permutations
题目描述
对于一个排列
对于一个
给定一个残缺的排列,对于其所有补全求函数之和,
解题思路
我们先来考虑一个完整的排列我们该如何求和,有两种方法:
由于问题可能有缺项,直接
我们先转换贡献体,将贡献变成一条路径不会经过的排列个数之和,很明显,直接统计还是不好做,但我们套上一个容斥就好做了。
我们把不能经过的点叫做实点,因为是容斥,我们注意一个点:以前经过的实点对之后不会产生影响,那我们可以设计
设
我们不妨再加一维,设
转移不难,正常转移即可,设
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=998244353;
int n,a[205],k,f[205][205][205][2][2];
long long p[1005],s=0;
bool v[205][205],v1[205],v2[205];
int main()
{
scanf("%d",&n),p[0]=1;
for(int i=1;i<=1000;i++)p[i]=p[i-1]*i%mod;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]!=-1)v[i][a[i]]=1,k++,v1[i]=1,v2[a[i]]=1;
}
f[0][0][0][1][1]=1;
for(int i=0;i<=n+1;i++)
for(int j=0;j<=n+1;j++)
for(int u=0;u<=n;u++)
for(int u1=0;u1<=1;u1++)
for(int u2=0;u2<=1;u2++)
{
if(!v[i][j+1])
{
f[i][j+1][u][u1][v2[j+1]]=(f[i][j+1][u][u1][v2[j+1]]+f[i][j][u][u1][u2])%mod;
if((!v2[j+1])&&j<n&&(u1==0))f[i][j+1][u+1][1][1]=(f[i][j+1][u+1][1][1]-f[i][j][u][u1][u2])%mod;
}
if(!v[i+1][j])
{
f[i+1][j][u][v1[i+1]][u2]=(f[i+1][j][u][v1[i+1]][u2]+f[i][j][u][u1][u2])%mod;
if((!v1[i+1])&&i<n&&(u2==0))f[i+1][j][u+1][1][1]=(f[i+1][j][u+1][1][1]-f[i][j][u][u1][u2])%mod;
}
}
for(int i=0;i<=n-k;i++)s=(s+p[n-k-i]*f[n+1][n+1][i][0][0])%mod;
printf("%lld",(s+mod)%mod);
return 0;
}
【Luogu P6047】 丝之割
题目描述
下面这部分题面只是为了帮助你理解题意,并没有详细的解释。更为严谨清晰的叙述见形式化题意。
多弦琴由两根支柱和连接两根支柱的
为了摧毁多弦琴,你可以进行若干次切割操作。在一次切割操作中,你可以选择上方支柱的某一个固定点
形式化题意:有
解题思路
我们能发现一个东西,对于一条弦
那么我们删去一些弦,那么剩余的弦按
据此我们就可以设计
很明显可以用斜率优化,因为有单调关系可优化到
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long x,y;
}v1[1000005],v[1000005];
long long n,mm,m,a[1000005],b[1000005],d[1000005],l,r,f[1000005];
double T(long long x,long long y)
{
if(a[v[x+1].x-1]!=a[v[y+1].x-1])return double(double(double(f[x])-double(f[y]))/double(double(a[v[y+1].x-1])-double(a[v[x+1].x-1])));
else if(f[x]>f[y])return -1e9-5;
else return 1e9+5;
}
bool cmp(datay q,datay w)
{
if(q.x!=w.x)return q.x<w.x;
else return q.y<w.y;
}
int main()
{
scanf("%lld%lld",&n,&mm);
a[0]=1e9+5;
b[n+1]=1e9+5;
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
a[i]=min(a[i-1],a[i]);
}
for(int i=1;i<=n;i++)scanf("%lld",&b[i]);
for(int i=n;i>=1;i--)b[i]=min(b[i+1],b[i]);
for(int i=1;i<=mm;i++)
{
scanf("%lld%lld",&v1[i].x,&v1[i].y);
}
sort(v1+1,v1+mm+1,cmp);
long long x=0;
for(int i=1;i<=mm;i++)
{
if(v1[i].y>x)
{
v[++m]=v1[i];
x=v1[i].y;
}
}
d[++r]=0;
l=1;
for(int i=1;i<=m;i++)
{
while(r-l>=1&&T(d[l],d[l+1])<=b[v[i].y+1])l++;
f[i]=f[d[l]]+a[v[d[l]+1].x-1]*b[v[i].y+1];
while(r-l>=1&&T(d[r-1],d[r])>T(d[r],i))r--;
d[++r]=i;
}
cout<<f[m];
return 0;
}
【Luogu P3349】 小星星
题目描述
小 Y 是一个心灵手巧的女孩子,她喜欢手工制作一些小饰品。她有
有一天她发现,她的饰品被破坏了,很多细线都被拆掉了。这个饰品只剩下了
只有你告诉了她正确的答案,她才会把小饰品做为礼物送给你呢,其中
解题思路
我们很容易想到一个树形
状态转移方程很好推,时间复杂度是
瓶颈在于每次转移时的枚举子集,考虑优化。
枚举子集是无法避免的,考虑将
那我们可以用容斥,用不考虑重复的情况减去有重复的情况,但还是不好做。
我们这样理解:每个原节点都必须出现一遍,那我们可以这样做容斥:每次枚举一个子集
这就是一种二项式反演,每次枚举完
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
int n,m,p,q,d[25];
long long s,f[25][25];
vector<int> a[25],a1[25][150005];
void dfs(int x,int y)
{
long long h=0;
for(int i=1;i<=n;i++)
{
if((!((1<<(i-1))&q))||a[x].size()>d[i])f[x][i]=0;
else f[x][i]=1;
}
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
dfs(a[x][i],x);
for(int j=1;j<=n;j++)
{
if((!((1<<(j-1))&q))||a[x].size()>d[j])continue;
h=0;
for(int u=0;u<a1[j][q].size();u++)h+=f[a[x][i]][a1[j][q][u]];
f[x][j]*=h;
}
}
return;
}
int main()
{
int x,y,w;
scanf("%d%d",&n,&m),p=(1<<n);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y),q=(1<<(x-1)),w=(1<<(y-1)),d[x]++,d[y]++;
for(int j=0;j<p;j++)
if((q&j)&&(w&j))a1[x][j].push_back(y),a1[y][j].push_back(x);
}
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
a[x].push_back(y),a[y].push_back(x);
}
for(int i=0;i<p;i++)
{
w=n;
for(int j=0;j<n;j++)
if(!(i&(1<<j)))w--;
q=i;
dfs(1,0);
for(int j=1;j<=n;j++)
s+=f[1][j]*(((n-w)&1)?(-1):1);
}
printf("%lld",s);
return 0;
}
【Luogu P10175】 Subtree Value
题目描述
给出一棵
解题思路
我们可以很快想出一个
这个
我们之所以要记录预计连通块大小是因为每个点的价值要加上连通块大小,注意到答案要模的位
我们考虑把
由于
对于
考虑这样设计
转移我们可以使用树形背包,时间复杂度为
有个小技巧,转移时可以枚举完在一起取模,这样会快很多。
Code
#include<bits/stdc++.h>
using namespace std;
int n,siz[2005],k;
long long mod,p1,p2,v[2005],t[2005][6],s,f[2005][2005][6];
vector<int> a[2005];
void dfs(int x)
{
f[x][1][1]=1;
f[x][1][0]=(k+v[x])%mod;
siz[x]=1;
for(int i=0;i<a[x].size();i++)
{
dfs(a[x][i]);
memset(t,0,sizeof(t));
for(int j1=0;j1<=siz[x];j1++)
for(int u1=0;u1<p2;u1++)
for(int j2=0;j2<=siz[a[x][i]];j2++)
for(int u2=0;u2<p2;u2++)
if(u1+u2<p2)t[j1+j2][u1+u2]+=f[x][j1][u1]*f[a[x][i]][j2][u2];
siz[x]+=siz[a[x][i]];
for(int j=0;j<=siz[x];j++)
for(int u=0;u<p2;u++)f[x][j][u]=(t[j][u]+f[x][j][u])%mod;
}
return;
}
int main()
{
long long x;
scanf("%d%lld%lld",&n,&p1,&p2),mod=1;
for(int i=1;i<=p2;i++)mod*=p1;
for(int i=2;i<=n;i++)scanf("%lld",&x),a[x].push_back(i);
for(int i=1;i<=n;i++)scanf("%lld",&v[i]);
for(int i=0;i<p1;i++)
{
memset(f,0,sizeof(f)),k=i;
dfs(1);
for(int j=1;j<=n;j++)
for(int u=i;u<=n;u+=p1)
{
x=1;
for(int q=0;q<p2;q++)
s=(s+x*f[j][u][q])%mod,x*=(u/p1)*p1,x%=mod;
}
}
printf("%lld",s);
return 0;
}
【ARC184D】 Erase Balls 2D
题目描述
在二维平面上有
你可以执行任意次以下操作:
- 从剩下的球中选择一个球,记为
。对剩下的每个球 ,若满足「 且 」或「 且 」(译注:即两球坐标间有二维偏序关系),将其移除。
求操作结束后,可能的剩下球的集合的数量,对
解题思路
统计剩下球的集合数量不好做,我们转换贡献体,考虑选择的球的集合
首先集合
但这个
我们把集合
换一种统计方式,我们考虑统计满足再多选一个数剩余球的集合就会变的集合
那这样转移就很好做了,我们只需要设
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
const long long mod=998244353;
struct datay
{
int x,y;
}a[305];
int n;
bool cmp1(datay q,datay w){return q.x<w.x;}
long long f[305];
int d[305];
int main()
{
int q,w;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d%d",&a[i].x,&a[i].y);
sort(a+1,a+n+1,cmp1);
a[0].y=n+1,a[n+1].y=0,f[0]=1;
for(int i=1;i<=n+1;i++)
for(int j=0;j<i;j++)
{
if(a[j].y<a[i].y)continue;
w=n+2,q=0;
for(int u=j+1;u<i;u++)d[u]=0;
for(int u=j+1;u<i;u++)
{
if(a[u].y<a[i].y||a[u].y>a[j].y)continue;
if(w>a[u].y)d[u]=1;
w=min(w,a[u].y);
}
w=-1;
for(int u=i-1;u>j;u--)
{
if(a[u].y<a[i].y||a[u].y>a[j].y)continue;
if(w<a[u].y&&d[u])q=1;
w=max(w,a[u].y);
}
if(q==0)f[i]=(f[j]+f[i])%mod;
}
printf("%lld",f[n+1]);
return 0;
}
【Luogu P8162】 让我们赢得选举 (Let's Win the Election)
题目描述
JOI 共和国有
Rie 将竞选总统,她正计划赢得选举。她决定以发表演讲的方式来提高自己的可靠程度。在她发表演讲后,下列事件可能会发生。
- 如果在第
个州的总演讲时间达到了 小时,她将赢得该州的一张选票。 - 如果在第
个州的总演讲时间达到了 小时,她将获得一名来自该州的协作者。 - 有可能 Rie 在第
个州无法获得协作者。此种情况下, ,否则保证 。
来自第
大选日快到了,Rie 想要尽快得到
给定州的数量和每个州的信息,写一个程序计算得到
解题思路
我们能很容易的想到一个贪心,按
很容易发现这个贪心是不对的,因为有可能有些时候对于某个前
我们按
我们预处理出这个
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
double x,y;
}a[505];
int n,m;
double f[2][505][505],d[505][505];
priority_queue<double> l;
bool cmp1(datay q,datay w)
{
if(q.y==-1&&w.y==-1)return q.x<w.x;
if(q.y==-1)return false;
if(w.y==-1)return true;
if(q.y!=w.y)return q.y<w.y;
return q.x<w.x;
}
int main()
{
double s=0;
int q1,q2;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%lf%lf",&a[i].x,&a[i].y);
sort(a+1,a+n+1,cmp1);
for(int i=1;i<=m;i++)
{
while(l.size())l.pop();
for(int j=n;j>=1;j--)
{
l.push(a[j].x),s+=a[j].x;
while(l.size()>i)s-=l.top(),l.pop();
if(n-j+1>=i)d[j][i]=s;
}
s=0;
}
s=d[1][m];
for(int i=0;i<=1;i++)
for(int j=0;j<=n;j++)
for(int u=0;u<=n;u++)f[i][j][u]=1e9;
for(int i=0;i<=m;i++)f[0][i][0]=0;
for(int i=1;i<=n;i++)
{
q1=(i&1),q2=q1^1;
for(int j=0;j<=n;j++)
{
for(int u=0;u<=m;u++)
{
f[q1][j][u]=f[q2][j][u]+a[i].x/(j+1);
if(u>=1&&a[i].y!=-1)f[q1][j][u]=min(f[q1][j][u],f[q2][j][u-1]+a[i].y/u);
}
}
for(int j=0;j<=m;j++)if(i<=m)s=min(s,f[q1][j][j]+d[i+1][m-i]/(j+1));
if(i==1)
{
for(int j=0;j<=n;j++)
for(int u=0;u<=n;u++)f[0][j][u]=1e9;
}
}
printf("%.9lf",s);
return 0;
}
【ABC134F】 Permutation Oddness
题目描述
定义一个排列
解题思路
我们可以把
因为边可以往回指,这样做很难设
我们改为拆绝对值,按照原思路,每点必有两边连接,也就是对于每个
这样就好
注意往前选时可以有不同的选择,记得乘上系数。
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
const long long mod=1e9+7;
int n,m,maxn;
long long f[55][55][5005];
int main()
{
scanf("%d%d",&n,&m),maxn=n*n;
f[0][0][maxn]=1;
for(int i=1;i<=n;i++)
for(int j=0;j<=n;j++)
for(int u=0;u<=maxn*2;u++)
{
f[i][j][u]=f[i-1][j][u]*(2*j+1)%mod;
if(j>=1&&u+2*i<=maxn*2)f[i][j][u]=(f[i][j][u]+f[i-1][j-1][u+2*i])%mod;
if(j<n&&u-2*i>=0)f[i][j][u]=(f[i][j][u]+f[i-1][j+1][u-2*i]*(j+1)*(j+1)%mod)%mod;
}
printf("%lld",f[n][0][m+maxn]);
return 0;
}
【ARC121E】 Directed Tree
题目描述
给定一棵有根树,根结点为 1,结点
定义一个
对合法排列计数,答案对 998244353 取模。
解题思路
最开始时想到将其
题目要求
考虑要将
我们考虑钦定有
做完
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
const long long mod=998244353;
long long f[2005][2005],s=0,p[2005];
int n,siz[2005];
vector<int> a[2005];
void dfs(int x)
{
siz[x]=1,f[x][0]=1;
for(int i=0;i<a[x].size();i++)
{
dfs(a[x][i]);
for(int j=siz[x];j>=0;j--)
for(int u=siz[a[x][i]];u>=1;u--)f[x][j+u]=(f[x][j+u]+f[x][j]*f[a[x][i]][u])%mod;
siz[x]+=siz[a[x][i]];
}
for(int i=n;i>=1;i--)
{
if(i>=siz[x])f[x][i]=0;
else f[x][i]=(f[x][i]+f[x][i-1]*(siz[x]-1-(i-1))%mod)%mod;
}
return;
}
int main()
{
int x;
scanf("%d",&n),p[0]=1;
for(int i=1;i<=n;i++)p[i]=(p[i-1]*i)%mod;
for(int i=2;i<=n;i++)scanf("%d",&x),a[x].push_back(i);
dfs(1);
for(int i=0;i<=n;i++)
{
if(i&1)s=(s-f[1][i]*p[n-i]%mod)%mod;
else s=(s+f[1][i]*p[n-i]%mod)%mod;
}
printf("%lld",(s+mod)%mod);
return 0;
}
【Luogu P10592】 isn
题目描述
给你一个长度为
问你有多少种操作方案,
解题思路
由于很难处理刚好删到非降的序列的方案个数,我们考虑容斥。
我们考虑用所以删成非降序列的方案数
对于一个操作方案,若它在做完倒数第二步时就已经时非降序列,那么该序列一定不满足题目要求,那我们可以随便找一个删成非降序列的方案,从剩余数中删去一个数,那么删完后的操作方案一定不满足题目要求。
那两个问题便同意了,我们
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
int n,a[2005];
long long f[2005][2005],d[2005][2005],p[2005],s=0;
set<int> l1;
map<int,int> l2;
int lowbit(int x){return x&(-x);}
void modify(int k,int x,long long y)
{
x++;
while(x<=n+2)d[k][x]=(d[k][x]+y)%mod,x+=lowbit(x);
return;
}
long long query(int k,long long x)
{
long long h=0;x++;
while(x)h=(h+d[k][x])%mod,x-=lowbit(x);
return h;
}
int main()
{
int x=0;
scanf("%d",&n),p[0]=1;
for(int i=1;i<=2003;i++)p[i]=p[i-1]*i%mod;
for(int i=1;i<=n;i++)scanf("%d",&a[i]),l1.insert(a[i]);
for(set<int>::iterator q=l1.begin();q!=l1.end();q++)l2[*q]=++x;
for(int i=1;i<=n;i++)a[i]=l2[a[i]];
f[0][0]=1,modify(0,0,1);
a[n+1]=n;
for(int i=1;i<=n+1;i++)
for(int j=i;j>=1;j--)
{
f[i][j]=query(j-1,a[i]);
modify(j,a[i],f[i][j]);
}
for(int i=1;i<=n+1;i++)s=(s+p[n-(i-1)]*f[n+1][i])%mod;
for(int i=2;i<=n+1;i++)s=(s-(p[n-(i-1)]*f[n+1][i]%mod)*(i-1)%mod)%mod;
printf("%lld",(s+mod)%mod);
return 0;
}
【ABC240Ex】 Sequence of Substrings
题目描述
给定一个长度为
解题思路
我们来想一个
注意到这个做法超时原因在于子串太多,我们来观察题目考虑省去不必要的子串。
我们可以通过让每个选择的子串长度最小的调整使得第一个选择的子串长度一定为
这样我们只用处理
注意排序不能直接排字符串,要把所以字符串丢到字典树上处理。
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
struct datay
{
int l,r,v;
}b[8000005];
int n,maxx,num,num1,num2,to[8000005][2],f[250005],d[8000005];
string a;
vector<datay> t[8000005];
int lowbit(int x){return x&(-x);}
void modify(int x,int v)
{
while(x<=8000000)d[x]=max(d[x],v),x+=lowbit(x);
return;
}
int query(int x)
{
int h=0;
while(x)h=max(h,d[x]),x-=lowbit(x);
return h;
}
void dfs(int x)
{
if(x==0)return;
num2++;
for(int i=0;i<t[x].size();i++)b[++num1]=t[x][i],b[num1].v=num2;
dfs(to[x][0]),dfs(to[x][1]);
return;
}
bool cmp1(datay q,datay w)
{
if(q.v^w.v)return q.v<w.v;
return q.r>w.r;
}
int main()
{
int x;datay q;
scanf("%d",&n),num=1,maxx=sqrt(4*n);cin>>a;
for(int i=0;i<a.size();i++)
{
x=1;
for(int j=i;j<=i+maxx-1&&j<a.size();j++)
{
if(!to[x][a[j]-'0'])to[x][a[j]-'0']=++num;
x=to[x][a[j]-'0'];
q.l=i+1,q.r=j+1,t[x].push_back(q);
}
}
dfs(1);
sort(b+1,b+num1+1,cmp1);
for(int i=1;i<=num1;i++)
{
f[i]=query(b[i].l-1)+1;
modify(b[i].r,f[i]);
}
printf("%d",query(n));
return 0;
}
【Luogu P6383】Resurrection
题目描述
有一棵包含
保证对于
按照如下步骤生成一张包含
选取一个
求对于给定的树
因为答案可能很大,你只需要求出答案对
解题思路
注意到生成的图
手玩样例后发现新图
我们可以根据这条性质开始树形
注意到我们只需要知道祖先中有多少个节点可被连边即可,我们可以设
很明显可以用前缀和优化到
还有一种做法,我们可以设
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=998244353;
int n,deep[3005];
long long f[3005][3005],s=1;
vector<int> a[3005];
void dfs(int x)
{
for(int i=0;i<a[x].size();i++)deep[a[x][i]]=deep[x]+1,dfs(a[x][i]);
for(int i=1;i<=deep[x]+1;i++)
{
f[x][i]=1;
for(int j=0;j<a[x].size();j++)
f[x][i]=(f[x][i]*f[a[x][j]][i+1])%mod;
if(i!=0)f[x][i]=(f[x][i]+f[x][i-1])%mod;
}
return;
}
int main()
{
int x,y;
scanf("%d",&n);
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
if(x<y)swap(x,y);
a[x].push_back(y);
}
deep[n]=1,dfs(n);
for(int i=0;i<a[n].size();i++)s=(s*f[a[n][i]][1])%mod;
printf("%lld",s);
return 0;
}
【Luogu P7888】 Distinct Subsequences
题目描述
给定一个由小写字符构成的字符串
令一个字符串的价值为该串的本质不同非空子序列个数,其中子序列可以为整体。
求
解题思路
我们先来考虑如何求出某序列的所有不同子序列。
我们设该序列为
考虑
考虑优化,方程可以优化为
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
string a;
long long f[1000005],d[26],s,cnt[26][1000005],p2[1000005],inv2[1000005],q;
long long poww(long long x,long long y)
{
if(abs(y)<=1000000)
{
if(y<0)return inv2[-y];
return p2[y];
}
long long h=1;
while(y)
{
if(y&1)h=(h*x)%mod;
x=(x*x)%mod,y>>=1;
}
return h;
}
int main()
{
cin>>a,cnt[a[0]-'a'][0]=p2[0]=inv2[0]=1,q=poww(2,mod-2);
for(int i=1;i<=1000000;i++)p2[i]=p2[i-1]*2%mod,inv2[i]=inv2[i-1]*q%mod;
for(int i=1;i<a.size();i++)
{
cnt[a[i]-'a'][i]=1;
for(int j=0;j<26;j++)cnt[j][i]+=cnt[j][i-1];
}
for(int i=0;i<a.size();i++)
{
f[i]=poww(2,i-cnt[a[i]-'a'][i]+1);
f[i]=(f[i]+d[a[i]-'a']*poww(2,i-cnt[a[i]-'a'][i])%mod)%mod;
for(int j=0;j<26;j++)
d[j]=(d[j]+f[i]*poww(2,cnt[j][i]-i)%mod)%mod;
}
for(int i=a.size()-1;i>=0;i--)s=(s+poww(2,a.size()-1-i)*f[i]%mod)%mod;
printf("%lld",s);
return 0;
}
【AGC030F】 Permutation and Minimum
题目描述
有一个
但是已经有一些位置强制填了特定的数了,输入时会给出。
最后令长度为
询问所有方案中能得到的不同的
。
解题思路
我们先考虑全是
但是注意到
考虑已确定的位置,对于
所以我们需要分开记录为
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
const int mod=1e9+7;
int n,a[605],f[2][605][605],pre[605];
bool v[605];
void add(int &q,int w){q=(q+w>=mod?q+w-mod:q+w);return;}
int main()
{
long long x,y;
scanf("%d",&n),n<<=1;
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]!=-1){v[a[i]]=1;pre[a[i]]=i;}
}
f[(n&1)^1][0][0]=1;
for(int i=n;i>=1;i--)
{
x=i&1,y=(x^1);
for(int j=0;j<=n/2;j++)
for(int u=0;u<=n/2;u++)
{
if(v[i]&&a[((pre[i]-1)^1)+1]!=-1){f[x][j][u]=f[y][j][u];continue;}
f[x][j][u]=0;
if(v[i])
{
add(f[x][j][u],f[y][j+1][u]);
if(u>=1)add(f[x][j][u],f[y][j][u-1]);
}
else
{
add(f[x][j][u],int(((long long)(u+1))*f[y][j][u+1]%mod)),add(f[x][j][u],f[y][j+1][u]);
if(j>=1)add(f[x][j][u],f[y][j-1][u]);
}
}
}
x=1;
for(int i=1;i<=n;i+=2)
if(a[i]==-1&&a[i+1]==-1)f[1][0][0]=(x*f[1][0][0]%mod),x++;
printf("%d",f[1][0][0]);
return 0;
}
【AGC004E】 Salvage Robots
题目描述
有一个棋盘,上面要么是空的,要么有一个机器人,要么是一个出口(整个地图只有一个出口)。每次可以命令所有机器人向上下左右中的某个方向移动一格,如果它超出了棋盘的边界就会消失。如果它到了出口的位置就会被你救下(并且从棋盘上消失)。求你能够救下的机器人的最大值,
解题思路
先转化一下题意,改成只有出口动,机器人不动,如果出口距离机器人的横或纵坐标超出了某个值,该机器人就死了,否则该机器人移动到了出口答案就
做
关于每次转移增加的值,为一段区间内的机器人个数,用前缀和处理即可,同时需要注意上下或左右的最远边界的到达会影响该区间的左右界,不能直接用
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
short f[105][105][105][105],s=0;
int n,m,stx,sty,a[205][205];
string x;
inline int query(int q,int w,int e,int r)
{
if(q<=0||w<=0||e<=0||r<=0||q>n||w>m||e>n||r>m)return 0;
return a[e][r]+a[q-1][w-1]-a[q-1][r]-a[e][w-1];
}
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
{
cin>>x,x=' '+x;
for(int j=1;j<=m;j++)
{
if(x[j]=='E')stx=i,sty=j;
a[i][j]=a[i-1][j]+a[i][j-1]-a[i-1][j-1]+(x[j]=='o');
}
}
for(int i=0;i<=stx;i++)
for(int j=0;j<=n-stx;j++)
for(int u=0;u<=sty;u++)
for(int k=0;k<=m-sty;k++)f[i][j][u][k]=-10000;
f[0][0][0][0]=0;
for(int i=0;i<=stx-1;i++)
for(int j=0;j<=n-stx;j++)
for(int u=0;u<=sty-1;u++)
for(int k=0;k<=m-sty;k++)
{
if(f[i][j][u][k]<0)continue;
s=max(s,f[i][j][u][k]);
if(i+j+1<=stx-1)f[i+1][j][u][k]=max(f[i+1][j][u][k],short(f[i][j][u][k]+query(stx-i-1,max(sty-u,k+1),stx-i-1,min(m-u,sty+k))));
if(i+j+1<=n-stx)f[i][j+1][u][k]=max(f[i][j+1][u][k],short(f[i][j][u][k]+query(stx+j+1,max(sty-u,k+1),stx+j+1,min(m-u,sty+k))));
if(u+k+1<=sty-1)f[i][j][u+1][k]=max(f[i][j][u+1][k],short(f[i][j][u][k]+query(max(stx-i,j+1),sty-u-1,min(stx+j,n-i),sty-u-1)));
if(u+k+1<=m-sty)f[i][j][u][k+1]=max(f[i][j][u][k+1],short(f[i][j][u][k]+query(max(stx-i,j+1),sty+k+1,min(stx+j,n-i),sty+k+1)));
}
cout<<s;
return 0;
}
【CF398B】 Painting The Wall
题目描述
有一个
解题思路
如果随机选择选择不到涂过色的格子,那么这题很难,但是可选择到,所以
期望
我们设
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
int n,m,v1[2005],v2[2005];
double f[2005][2005];
int main()
{
int x,y;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
v1[x]=1,v2[y]=1;
}
x=y=0;
for(int i=1;i<=n;i++)x+=v1[i],y+=v2[i];
x=n-x,y=n-y;
for(int i=1;i<=x;i++)f[i][0]=f[i-1][0]+double(n)/double(i);
for(int i=1;i<=y;i++)f[0][i]=f[0][i-1]+double(n)/double(i);
for(int i=1;i<=x;i++)
for(int j=1;j<=y;j++)
{
f[i][j]=(n*n+f[i-1][j-1]*i*j+f[i-1][j]*i*(n-j)+f[i][j-1]*(n-i)*j);
f[i][j]/=double(n*n-(n-i)*(n-j));
}
printf("%.9lf",f[x][y]);
return 0;
}
【ARC121F】 Logical Operations on Tree
题目描述
给定一棵树,给每个点填
解题思路
我们先来思考对于每个方案应该怎样合并,很明显,先把所有
我们可以把
但是统计至少有一个连通块权值为
前面的预处理即可,后面的我们做一个简单的树形
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
const long long mod=998244353;
int n,siz[100005];
long long f[100005][3],p[200005];
vector<int> a[100005];
void dfs(int x,int y)
{
siz[x]=1;
f[x][0]=f[x][2]=1;
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
dfs(a[x][i],x);
f[x][0]=f[x][0]*((f[a[x][i]][0]*2+f[a[x][i]][1]*2+f[a[x][i]][2])%mod)%mod;
f[x][1]=(f[x][1]*((f[a[x][i]][0]*2+f[a[x][i]][1]*2+f[a[x][i]][2])%mod)%mod+f[x][2]*(f[a[x][i]][0]+f[a[x][i]][1])%mod)%mod;
f[x][2]=f[x][2]*((f[a[x][i]][0]+f[a[x][i]][1]+f[a[x][i]][2])%mod)%mod;
siz[x]+=siz[a[x][i]];
}
return;
}
int main()
{
int x,y;
scanf("%d",&n),p[0]=1;
for(int i=1;i<=200000;i++)p[i]=p[i-1]*2%mod;
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
a[x].push_back(y),a[y].push_back(x);
}
dfs(1,0);
printf("%lld",(p[2*n-1]-f[1][0]-f[1][1]+3*mod)%mod);
return 0;
}
【Luogu P2151】 HH去散步
题目描述
HH 有个一成不变的习惯,喜欢饭后百步走。所谓百步走,就是散步,就是在一定的时间内,走过一定的距离。但是同时 HH 又是个喜欢变化的人,所以他不会立刻沿着刚刚走来的路走回。又因为 HH 是个喜欢变化的人,所以他每天走过的路径都不完全一样,他想知道他究竟有多少种散步的方法。
现在给你学校的地图(假设每条路的长度都是一样的都是
解题思路
观察数据范围,由于
先不考虑不能折返的限制,我们只需把连边情况转化成矩阵在做矩阵快速幂即可。
由于不能折返,我们也许还需额外记录边的信息,但是这样绝对会爆。
我们已经记录了边的信息同时记录走的方向,那我们已经知道到了那个节点,那我们就可以不用记录节点信息了,直接预处理出每条边可以到那些边就可以了。
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=45989;
struct datay
{
int x,y;
}a[105];
int n,m,k,st,en;
long long b[125][125],h[125][125],d[125][125],s;
int main()
{
int x,y;
scanf("%d%d%d%d%d",&n,&m,&k,&st,&en);
for(int i=1;i<=m;i++)scanf("%d%d",&a[i].x,&a[i].y);
k--;
for(int i=1;i<=m;i++)
{
if(a[i].x==st)d[0][2*i-2]=1;
if(a[i].y==st)d[0][2*i-1]=1;
}
for(int i=1;i<=2*m;i++)
{
x=(i-1)/2+1;
if(!(i&1))swap(a[x].x,a[x].y);
for(int j=1;j<=2*m;j++)
{
if(i==j)continue;
y=(j-1)/2+1;
if(!(j&1))swap(a[y].x,a[y].y);
if(a[x].y==a[y].x)b[i-1][j-1]=1;
if(!(j&1))swap(a[y].x,a[y].y);
}
if(!(i&1))swap(a[x].x,a[x].y);
}
while(k)
{
if(k&1)
{
for(int i=0;i<2*m;i++)
for(int j=0;j<2*m;j++)
{
h[i][j]=0;
for(int u=0;u<2*m;u++)h[i][j]=(h[i][j]+d[i][u]*b[u][j]%mod)%mod;
}
for(int i=0;i<2*m;i++)
for(int j=0;j<2*m;j++)
d[i][j]=h[i][j];
}
k>>=1;
for(int i=0;i<2*m;i++)
for(int j=0;j<2*m;j++)
{
h[i][j]=0;
for(int u=0;u<2*m;u++)h[i][j]=(h[i][j]+b[i][u]*b[u][j]%mod)%mod;
}
for(int i=0;i<2*m;i++)
for(int j=0;j<2*m;j++)
b[i][j]=h[i][j];
}
for(int i=1;i<=2*m;i++)
{
if(!(i&1))swap(a[(i-1)/2+1].x,a[(i-1)/2+1].y);
if(a[(i-1)/2+1].y==en)s=(s+d[0][i-1])%mod;
}
printf("%lld",s);
return 0;
}
【ARC100E】 Or Plus Max
题目描述
给你一个长度为
解题思路
直接考虑
即使这样还是不好做,我们观察到若
这个问题就很好做了,若只有
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
int n,a[300005],p,f[300005][2],s;
int main()
{
scanf("%d",&n),p=(1<<n);
for(int i=0;i<p;i++)scanf("%d",&a[i]),f[i][0]=a[i];
for(int i=0;i<n;i++)
for(int j=0;j<p;j++)
if(j&(1<<i))
{
f[j][1]=max(f[j][1],f[j][0]+f[j^(1<<i)][0]);
f[j][0]=max(f[j][0],f[j^(1<<i)][0]);
f[j][1]=max(f[j][1],f[j^(1<<i)][1]);
}
for(int i=1;i<p;i++)s=max(s,f[i][1]),printf("%d\n",s);
return 0;
}
【ARC187C】 1 Loop Bubble Sort
题目描述
对于
- 对于按此顺序排列的
,如果是 ,则交换 和 。
给你一个长度为
求在每个
解题思路
题目要求我们做的操作等价于做一次冒泡排序,注意到对于一个数
那我们就变
显然分类讨论,当
再来考虑
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=998244353;
int n,a[5005],q,v1[5005],v2[5005];
long long f[2][5005],s=0;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]>0)v1[a[i]]=1,v2[i]=1;
}
for(int i=1;i<=n;i++)v1[i]=v1[i-1]+(v1[i]^1),v2[i]=(v2[i]^1)+v2[i-1];
for(int i=1;i<=n;i++)f[1][i]=1;
for(int i=2;i<=n;i++)
{
q=(i&1);
if(a[i-1]==-1)
{
s=0;
for(int j=1;j<=n;j++)
{
f[q][j]=s;
if(v1[j-1]>=v2[i-1])f[q][j]=(f[q][j]+f[q^1][j]*(v1[j-1]-v2[i-2])%mod)%mod;
if(v1[j]-v1[j-1])s=(s+f[q^1][j])%mod;
}
}
else
{
for(int j=1;j<=n;j++)f[q][j]=0;
for(int j=a[i-1]+1;j<=n;j++)f[q][j]=(f[q][j]+f[q^1][j]+f[q^1][a[i-1]])%mod;
}
}
printf("%lld",f[n&1][n]);
return 0;
}
【AGC022E】 Median Replace
题目描述
有个长度为
一次操作可将
求有多少将
解题思路
我们从前往后消除,思考最优的消除策略。
当出现
考虑出现
根据这些限制,我们可以做一个线性
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
const long long mod=1e9+7;
string a;
long long f[300005][8];
int main()
{
cin>>a,a=' '+a,f[0][0]=1;
for(int i=1;i<a.size();i++)
{
if(a[i]=='0'||a[i]=='?')
{
f[i][1]=(f[i][1]+f[i-1][0]+f[i-1][2])%mod;
f[i][2]=(f[i][2]+f[i-1][1])%mod;
f[i][4]=(f[i][4]+f[i-1][3]+f[i-1][5])%mod;
f[i][5]=(f[i][5]+f[i-1][4])%mod;
f[i][6]=(f[i][6]+f[i-1][6])%mod;
}
if(a[i]=='1'||a[i]=='?')
{
f[i][3]=(f[i][3]+f[i-1][0]+f[i-1][4])%mod;
f[i][0]=(f[i][0]+f[i-1][1])%mod;
f[i][1]=(f[i][1]+f[i-1][2])%mod;
f[i][6]=(f[i][6]+f[i-1][3]+f[i-1][6])%mod;
f[i][4]=(f[i][4]+f[i-1][5])%mod;
}
}
printf("%lld",(f[a.size()-1][3]+f[a.size()-1][6])%mod);
return 0;
}
【ARC186E】 Missing Subsequence
题目描述
给定正整数
- 除了序列
以外的其它所有长度为 ,值域为 的整数序列都是 的(不一定连续的)子序列。
解题思路
先考虑如何求出包含所有长度
考虑题目要求,由于
思考其它还需满足的条件,发现对于
那么就可以用
系数如何求?预处理系数,我们再做一次
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
const long long mod=998244353;
int n,m,k,t[405];
long long f[405][405],d[405][2],f1[405][405],s;
long long poww(long long x,long long y)
{
long long h=1;
while(y)
{
if(y&1)h=(h*x)%mod;
x=(x*x)%mod,y>>=1;
}
return h;
}
int main()
{
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=m;i++)scanf("%d",&t[i]);
f1[0][0]=f[0][0]=1;
for(int i=1;i<=n;i++)
for(int j=1;j<=i;j++)
f1[i][j]=(f1[i-1][j-1]*j%mod+f1[i-1][j]*j%mod)%mod;
for(int i=k;i<=n;i++)
{
d[i][1]=f1[i-1][k-1];
for(int j=k-1;j<i;j++)d[i+1][0]=(d[i+1][0]+(f1[j-1][k-2]*(k-1)%mod)*(poww(k-1,i-j)-poww(k-2,i-j))%mod+mod)%mod;
}
for(int i=1;i<=n;i++)
for(int j=1;j<m;j++)
for(int u=1;u<=i;u++)
f[i][j]=(f[i][j]+f[u-1][j-1]*d[i-u+1][t[j]==t[j+1]]%mod)%mod;
for(int i=1;i<=n-k+2;i++)s=(f[i-1][m-1]*f1[n-i+1][k-1]%mod+s)%mod;
cout<<s;
return 0;
}
【Luogu P6189】 跑步
题目描述
给出一个数
解题思路
模拟赛时
使用根号分治,对于
那我们只需把这两种
Code
#include<bits/stdc++.h>
using namespace std;
int n,mod,block,f[100005],dp[351][100005],d[100005],s;
int main()
{
scanf("%d%d",&n,&mod),block=sqrt(n),f[0]=dp[0][0]=1;
for(int i=1;i<block;i++)
for(int j=i;j<=n;j++)f[j]=(f[j]+f[j-i])%mod;
for(int i=0;i<=block+1;i++)
for(int j=0;j<=n;j++)
{
d[j]=(d[j]+dp[i][j])%mod;
if(j+i<=n&&i!=0)dp[i][j+i]=(dp[i][j+i]+dp[i][j])%mod;
if(j+block<=n)dp[i+1][j+block]=(dp[i+1][j+block]+dp[i][j])%mod;
}
for(int i=0;i<=n;i++)s=(s+(long long)(f[i])*d[n-i]%mod)%mod;
cout<<s;
return 0;
}
【Luogu P7962】 方差
题目描述
给定长度为
其中方差的定义为:数列中每个数与平均值的差的平方的平均值。更形式化地说,方差的定义为
解题思路
方差 $D = \frac{1}{n} \sum_{i = 1}^{n} {(a_i - \bar a)}2=\frac{n\sum_{i=1}n a_i^2 -(\sum_{i=1}^n a_i)2}{n2} $ ,所以答案为
题目中操作的本质是交换相邻两个数的差分数组,从差分角度考虑合适方差最小,发现当数越密集,即差分呈单谷状分布时,方差最小。
那我们就可以把差分值按顺序放进数组中了,考虑从大到小把差分值放入数组中,但由于需要存储上下分别插到了哪,这个
考虑从小到大把差分值放入数组中,那我们每次只需要在原来的基础上放在序列的头或尾即可,设
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const long long MAXN=300000;
long long n,a[10005],num,f[2][MAXN+5],x=0,s=1e15;
int main()
{
memset(f,0x3F,sizeof(f));
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=1;i<n;i++)a[i]=a[i+1]-a[i];
sort(a+1,a+n),f[0][0]=0;
for(int i=1;i<n;i++)
{
if(a[i]==0)continue;
num++;
for(int j=0;j<=MAXN;j++)
{
if(f[(num&1)^1][j]>=1e16)continue;
if(j+x+a[i]<=MAXN)f[num&1][j+x+a[i]]=min(f[num&1][j+x+a[i]],f[(num&1)^1][j]+(x+a[i])*(x+a[i]));
if(j+a[i]*i<=MAXN)f[num&1][j+a[i]*i]=min(f[num&1][j+a[i]*i],f[(num&1)^1][j]+a[i]*a[i]*i+2*j*a[i]);
f[(num&1)^1][j]=2e16;
}
x+=a[i];
}
for(long long i=0;i<=MAXN;i++)
if(f[num&1][i]<1e16)s=min(s,n*f[num&1][i]-i*i);
printf("%lld",s);
return 0;
}
【AGC043D】 Merge Triplets
题目描述
- 给定如下构造生成长度为
的排列 的方法:- 先生成一个长度为
的排列 。然后将 , 分成一块。 - 有
个指针,初始指向每个块的第一个数。 - 每次选择所有指针指向的数中最小的数删除,然后放到
的末尾。之后指向被删除的数后移一个位置。若移出块了,则删除这个指针。
- 先生成一个长度为
- 请你求出,一共能生成长度为
的排列共多少种。答案可能很大,请求出对 取模的结果。 , 。
解题思路
没有比较好的映射方式连接
根据构造方式,我们想到一个特殊性质:连续下降的子串长度最多只有
那我们就可以考虑
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
long long n,mod,f[6005][9005],s;
int main()
{
scanf("%lld%lld",&n,&mod);
f[0][3000]=1;
for(int i=1;i<=3*n;i++)
for(int j=0;j<=9000;j++)
{
if(j>0)f[i][j]=f[i-1][j-1];
if(j<9000&&i>=2)f[i][j]=(f[i][j]+f[i-2][j+1]*(i-1)%mod)%mod;
if(i>=3)f[i][j]=(f[i][j]+(f[i-3][j]*(i-1)%mod)*(i-2)%mod)%mod;
}
for(int i=3000;i<=9000;i++)s=(s+f[3*n][i])%mod;
printf("%lld",s);
return 0;
}
【AGC035E】 Develop
题目描述
在黑板上写有
解题思路
因为保留的数的范围为
思考什么删去数的集合才是合法的,我们可以将每个节点
考虑
考虑
这样转化实在难以
我们
我们考虑记录删到哪位、当前
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
long long n,k,mod,m1,m2,m,f[155][155][155],s,maxx;
long long solve1(long long m)
{
s=0,f[0][0][0]=1;
for(int i=1;i<=m;i++)
{
f[i][0][0]=0;
for(int j=0;j<=k;j++)f[i][0][0]=(f[i][0][0]+f[i-1][j][0])%mod;
for(int j=1;j<=k;j++)f[i][j][0]=f[i-1][j-1][0];
}
for(int i=0;i<=k;i++)s=(s+f[m][i][0])%mod;
return s;
}
void solve3()
{
long long x;
m2=m1=(k-1)/2;
if(n&1)m2++;
m=(n-m1-m2)/2,maxx=k+2;
f[0][0][0]=1;
for(int i=1;i<=m1;i++)
{
for(int j=0;j<maxx;j++)
for(int u=0;u<=maxx;u++)f[i][0][0]=(f[i][0][0]+f[i-1][j][u])%mod;
for(int j=1;j<=maxx;j++)
for(int u=0;u<maxx;u++)f[i][0][j]=(f[i][0][j]+f[i-1][u][j-1])%mod;
for(int j=0;j<maxx;j++)f[i][0][maxx]=(f[i][0][maxx]+f[i-1][j][maxx])%mod;
}
for(int i=m1+1;i<=m1+m;i++)
{
for(int j=0;j<maxx;j++)
for(int u=0;u<=maxx;u++)f[i][0][0]=(f[i][0][0]+f[i-1][j][u])%mod;
for(int j=1;j<=maxx;j++)
for(int u=0;u<maxx;u++)f[i][0][j]=(f[i][0][j]+f[i-1][u][j-1])%mod;
for(int j=0;j<maxx;j++)f[i][0][maxx]=(f[i][0][maxx]+f[i-1][j][maxx])%mod;
for(int j=0;j<=maxx;j++)f[i][0][0]=(f[i][0][0]+f[i-1][0][j])%mod;
for(int j=3;j<maxx;j++)
for(int u=0;u<=maxx;u++)f[i][j][0]=(f[i][j][0]+f[i-1][j-1][u])%mod;
for(int j=0;j<maxx;j++)
for(int u=0;u<maxx;u++)
{
x=max(j+1,u+2);
if(x>=maxx)continue;
f[i][x][u+1]=(f[i][x][u+1]+f[i-1][j][u])%mod;
}
}
for(int i=m1+m+1;i<=m1+m+m2;i++)
{
for(int j=0;j<maxx;j++)
for(int u=0;u<=maxx;u++)f[i][0][0]=(f[i][0][0]+f[i-1][j][u])%mod;
for(int j=0;j<=maxx;j++)f[i][0][0]=(f[i][0][0]+f[i-1][0][j])%mod;
for(int j=3;j<maxx;j++)
for(int u=0;u<=maxx;u++)f[i][j][0]=(f[i][j][0]+f[i-1][j-1][u])%mod;
}
long long s=0;
for(int i=0;i<maxx;i++)
for(int j=0;j<=maxx;j++)s=(s+f[m1+m+m2][i][j])%mod;
cout<<s;
return;
}
int main()
{
scanf("%lld%lld%lld",&n,&k,&mod);
if(!(k&1))k>>=1,printf("%lld",solve1(n/2)*solve1(n-n/2)%mod);
else solve3();
return 0;
}
【ARC112E】 Cigar Box
题目描述
给定序列
解题思路
我们考虑随便找出一组合法的操作方案,不考虑操作次数的限制,我们只需按顺序一个一个放好即可。
这个我们一个启发:我们可以把放好的序列看成三部分,前面和后面的部分是按顺序操作过的,只有中间的部分是没操作过的。
那么对于这种划分,只要中间的部分是递增的以及操作过的数不超过
我们需要求出一种划分方案的方案数,设前面部分有
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=998244353;
long long n,m,s,a[3005],d[3005],C[3005][3005],p[3005],f[3005][3005];
int main()
{
C[0][0]=p[0]=f[0][0]=1;
for(int i=1;i<=3000;i++)
{
p[i]=p[i-1]*2%mod,C[i][0]=1;
for(int j=1;j<=i;j++)
{
C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
f[i][j]=(f[i-1][j-1]+f[i-1][j]*j%mod)%mod;
}
}
scanf("%lld%lld",&n,&m),d[n+1]=n+1;
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
for(d[i]=i;d[i]<n;d[i]++)
if(a[d[i]+1]<a[d[i]])break;
for(int i=0;i<=n;i++)
for(int j=0;j<=n-i;j++)
if(d[i+1]>=n-j&&i+j<=m)s=(s+(p[m-i-j]*C[i+j][i]%mod)*f[m][i+j]%mod)%mod;
printf("%lld",s);
return 0;
}
【AGC013D】 Piling Up
题目描述
一开始有
解题思路
首先,黑白球总数不变,所以只需记录黑球的数量即可。
考虑每种操作的限制,因为放了一组黑白球进去,所以后拿出的球无特殊限制,所有我们只需保证前面拿球时保证存在该颜色的球即可。
那我们就可以直接做
但是注意还要去重,我们只需保证每个方案在做的过程中出现了黑球
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
long long n,m,f[3005][3005][2];
int main()
{
scanf("%lld%lld",&n,&m),f[0][0][1]=1;
for(int i=1;i<=n;i++)f[0][i][0]=1;
for(int i=0;i<m;i++)
{
for(int j=0;j<=n;j++)
{
for(int u=0;u<=1;u++)
{
if(j!=n)f[i+1][j+1][u]=(f[i+1][j+1][u]+f[i][j][u])%mod,f[i+1][j][u]=(f[i+1][j][u]+f[i][j][u])%mod;
if(j>1)f[i+1][j-1][u]=(f[i+1][j-1][u]+f[i][j][u])%mod,f[i+1][j][u]=(f[i+1][j][u]+f[i][j][u])%mod;
else if(j==1)f[i+1][0][1]=(f[i+1][0][1]+f[i][j][u])%mod,f[i+1][1][1]=(f[i+1][1][1]+f[i][j][u])%mod;
}
}
}
long long s=0;
for(int i=0;i<=n;i++)s=(s+f[m][i][1])%mod;
printf("%lld",s);
return 0;
}
【Luogu P3226】 集合选数
题目描述
《集合论与图论》这门课程有一道作业题,要求同学们求出
同学们不喜欢这种具有枚举性质的题目,于是把它变成了以下问题:对于任意一个正整数
解题思路
首先,若
每个集合内为独立的问题,我们分开考虑,将集合内所有数
我们考虑最多有多少行多少列,显然是
如此小的网格我们很明显可以做状压
时间复杂度大概是
Code
#include<bits/stdc++.h>
using namespace std;
long long n,mod,f[25][10005],m,d[25][10005];
bool v[10005];
bool check(long long x)
{
long long y=0;
while(x)
{
if((x&1)&&(y))return false;
y=x&1;
x>>=1;
}
return true;
}
long long solve(long long x)
{
f[0][0]=1,m=0;
for(int i=0;i<=10000;i++)d[0][i]=1;
long long y,q=0,w=(1<<13),e;
while(x<=n)
{
m++,y=x,q=0;
while(y<=n)q++,y*=3;
e=(1<<q);
for(int i=0;i<e;i++)f[m][i]=d[m][i]=0;
for(int i=0;i<e;i++)if(v[i])d[m][i]=f[m][i]=d[m-1][(w-1)^i];
w=e;
for(int i=0;i<q;i++)
for(int j=0;j<e;j++)
if(j&(1<<i))d[m][j]=(d[m][j]+d[m][j^(1<<i)])%mod;
x*=2;
}
long long s=0;
for(int i=0;i<w;i++)s=(s+f[m][i])%mod;
return s;
}
void poi()
{
long long s=1;
scanf("%lld",&n),mod=1e9+1;
for(int i=1;i<=n;i++)
if((i%2!=0)&&(i%3!=0))s=(s*solve(i))%mod;
printf("%lld\n",s);
return;
}
int main()
{
for(int i=0;i<(1<<13);i++)v[i]=check(i);
poi();
return 0;
}
数据结构
【Luogu P10856】 Xor-Forces
题目描述
给定一个长度为
- 操作一:给定
,设数列 满足 ,将 修改为 。其中 表示按位异或运算。 - 操作二:给定
,查询 的下标在 之间的子数组有多少颜色段。不保证 ,若 ,请自行交换 。
其中,一个极长的所有数都相等的子数组称为一个颜色段。
部分测试点要求强制在线,
解题思路
首先有一条极其重要的性质为数组长度为
我们思考
我们发现这个交换可以看成线段树上的某些块对换,所以我们可以线段树遍历时,若该位为
那么就可以进行查询了,修改直接把直接异或之和存下来即可。
最后一个问题:怎么处理改变位置的操作对每个块内部相对位置的影响。
我们可以想到,不改变块内相对位置的位是不用理它的,所以对于一个第
由于层数越大块数越多,我们可以把会影响块内位置的存下来,即只存前
时间复杂度即为
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
int lc,rc,v;
};
int qwe,n,k,m,v1[1000005],s;
vector<datay> t[2000005];
datay merge(datay x,datay y)
{
datay h;
h.lc=x.lc,h.rc=y.rc;
h.v=(x.v+y.v-(x.rc==y.lc));
return h;
}
void build(int x,int l,int r,int p)
{
if(l==r)
{
datay h;
h.lc=h.rc=v1[l],h.v=1;
t[x].push_back(h);
return;
}
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1,z=p>>1;
build(lc,l,mid,z),build(rc,mid+1,r,z);
for(int i=0;i<p;i++)
{
if(i&z)t[x].push_back(merge(t[rc][i^z],t[lc][i^z]));
else t[x].push_back(merge(t[lc][i],t[rc][i]));
}
return;
}
datay query(int x,int l,int r,int ql,int qr,int p)
{
if(ql<=l&&r<=qr)return t[x][(p-1)&s];
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1,z=p>>1;
if(s&z)swap(lc,rc);
if(qr<=mid)return query(lc,l,mid,ql,qr,z);
if(ql>mid)return query(rc,mid+1,r,ql,qr,z);
return merge(query(lc,l,mid,ql,qr,z),query(rc,mid+1,r,ql,qr,z));
}
int main()
{
int lst=0,op,x,y;
scanf("%d%d%d",&qwe,&k,&m);
n=(1<<k);
for(int i=0;i<n;i++)scanf("%d",&v1[i]);
build(1,0,n-1,n);
for(int i=1;i<=m;i++)
{
scanf("%d",&op);
if(op==1)
{
scanf("%d",&x),x^=(lst*qwe);
s^=x;
}
else
{
scanf("%d%d",&x,&y);
x^=(lst*qwe),y^=(lst*qwe);
if(x>y)swap(x,y);
lst=query(1,0,n-1,x,y,n).v;
printf("%d\n",lst);
}
}
return 0;
}
【Luogu P4587】 神秘数
题目描述
一个可重复数字集合
现给定长度为
解题思路
我们先来分析一个集合
对于一个集合
我们发现从小到大遍历一个集合
我们开一棵值域线段树用来存储
现在是区间询问,我们只需要开一棵主席树即可,时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
int lc,rc,v;
}f[10000005];
const int maxx=1e9;
int n,m,a[100005],num,v1[100005],root[100005];
int modify(int x,int l,int r,int k,int v)
{
int h=++num;f[h]=f[x];
if(l==r){f[h].v+=v;return h;}
int mid=(l+r)>>1;
if(k<=mid)f[h].lc=modify(f[x].lc,l,mid,k,v);
else f[h].rc=modify(f[x].rc,mid+1,r,k,v);
f[h].v=f[f[h].lc].v+f[f[h].rc].v;
return h;
}
int query(int x,int l,int r,int ql,int qr)
{
if(ql<=l&&r<=qr)return f[x].v;
int mid=(l+r)>>1,h=0;
if(ql<=mid&&f[x].lc)h+=query(f[x].lc,l,mid,ql,qr);
if(qr>mid&&f[x].rc)h+=query(f[x].rc,mid+1,r,ql,qr);
return h;
}
int solve(int l,int r)
{
if(query(root[r],1,maxx,1,1)-query(root[l-1],1,maxx,1,1)==0)return 1;
int x=1,s=1,y,pre=1;
for(int i=1;i<=n;i++)
{
if(pre>s)break;
y=query(root[r],1,maxx,pre,s)-query(root[l-1],1,maxx,pre,s);
pre=s+1,s+=y;
}
return s;
}
int main()
{
int x=0,y;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)root[i]=modify(root[i-1],1,maxx,a[i],a[i]);
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
if(x>y)swap(x,y);
printf("%d\n",solve(x,y));
}
return 0;
}
【ARC187D】 Many Easy Optimizations
题目描述
给出两个长度为
解题思路
钦定
考虑优化,我们每次询问会重复的枚举某个值作为最小值的情况,由于相邻的询问之间只差了一组数,我们考虑这组数对之前枚举的最小值的影响,同时在考虑当前的
考虑进一步优化,我们考虑直接枚举以
每次加进一组数时,最小值为
继续观察,由于
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
const int MAXN=(2e9);
struct datay
{
int x,y;
}a[500005];
int n,f[4000005],maxx[4000005],v1[1000005],d[4000005],minn[4000005];
set<int> l1;
map<int,int> l2;
void galaxy(int x,int l,int r,int v){f[x]=v-v1[r],maxx[x]=minn[x]=v,d[x]=v;return;}
void pushdown(int x,int l,int r)
{
if(d[x]==0)return;
int mid=(l+r)>>1,lc=(x<<1),rc=(x<<1)|1;
galaxy(lc,l,mid,d[x]),galaxy(rc,mid+1,r,d[x]),d[x]=0;
return;
}
void build(int x,int l,int r)
{
if(l==r){maxx[x]=minn[x]=v1[l],f[x]=0;return;}
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
build(lc,l,mid),build(rc,mid+1,r),f[x]=0,maxx[x]=max(maxx[rc],maxx[lc]),minn[x]=min(minn[lc],minn[rc]);
return;
}
void modify(int x,int l,int r,int ql,int qr,int v)
{
if(ql<=l&&r<=qr){galaxy(x,l,r,v);return;}
pushdown(x,l,r);
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
if(ql<=mid)modify(lc,l,mid,ql,qr,v);
if(qr>mid)modify(rc,mid+1,r,ql,qr,v);
maxx[x]=max(maxx[lc],maxx[rc]),f[x]=min(f[lc],f[rc]),minn[x]=min(minn[lc],minn[rc]);
return;
}
int query(int x,int l,int r,int k)
{
if(l==r)return l;
pushdown(x,l,r);
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
if(minn[rc]<=k)return query(rc,mid+1,r,k);
return query(lc,l,mid,k);
}
int main()
{
int num=0,x;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i].x),l1.insert(a[i].x);
for(int i=1;i<=n;i++)scanf("%d",&a[i].y),l1.insert(a[i].y);
for(int i=1;i<=n;i++)
if(a[i].x>a[i].y)swap(a[i].x,a[i].y);
for(set<int>::iterator q=l1.begin();q!=l1.end();q++)l2[*q]=++num,v1[num]=*q;
for(int i=1;i<=n;i++)a[i].x=l2[a[i].x],a[i].y=l2[a[i].y];
build(1,1,num);
for(int i=1;i<=n;i++)
{
if(minn[1]<=v1[a[i].x])x=query(1,1,num,v1[a[i].x]),modify(1,1,num,1,min(x,a[i].x),v1[a[i].x]);
x=query(1,1,num,v1[a[i].y]);
if(a[i].x!=a[i].y&&a[i].x+1<=min(a[i].y,x))modify(1,1,num,a[i].x+1,min(a[i].y,x),v1[a[i].y]);
if(a[i].y!=num)modify(1,1,num,a[i].y+1,num,MAXN);
printf("%d\n",f[1]);
}
return 0;
}
【Luogu P6072】 Path
题目描述
给定一棵
解题思路
设
由于路径边权异或和可以用树上差分转化为求两点异或和最大的问题,求子树两点异或和最大的问题明显用树上启发式合并
注意到我们可以直接选出树上进行异或操作后最大的两点作为大多数点的答案,只有
总时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
int n,f[20000005][2],num,son[30005],root,siz[30005],dfn[30005],out[30005],num1,re[30005],v1[20000005];
vector<int> a[30005],t[30005];
int v[30005],s,p2[105],ans[30005],ans1[30005];
void add(int x)
{
int p=1,q;
for(int i=30;i>=0;i--)
{
if(x&p2[i])q=1;
else q=0;
if(!f[p][q])f[p][q]=++num,f[num][0]=f[num][1]=0,p=num;
else p=f[p][q];
v1[p]++;
}
return;
}
int query(int x)
{
int h=0,p=1,q;
for(int i=30;i>=0;i--)
{
if(x&p2[i])q=1;
else q=0;
if(f[p][q^1]&&v1[f[p][q^1]])h|=p2[i],p=f[p][q^1];
else p=f[p][q];
}
return h;
}
void del(int x)
{
int p=1,q;
for(int i=30;i>=0;i--)
{
if(x&p2[i])q=1;
else q=0;
p=f[p][q],v1[p]--;
}
return;
}
void dfs1(int x,int y)
{
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
son[a[x][i]]=x,dfs1(a[x][i],x);
}
return;
}
void dfs2(int x,int y,int z)
{
if(!z)
{
s=max(s,query(v[x]));
add(v[x]);
for(int i=0;i<a[x].size();i++)if(a[x][i]!=y)dfs2(a[x][i],x,0);
return;
}
ans[x]=s;
if(x==root)return;
s=max(s,query(v[x]));
add(v[x]);
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y||a[x][i]==son[x])continue;
dfs2(a[x][i],x,0);
}
dfs2(son[x],x,1);
return;
}
void dfs3(int x,int y)
{
siz[x]=1;
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
v[a[x][i]]=v[x]^t[x][i],dfs3(a[x][i],x),siz[x]+=siz[a[x][i]];
}
return;
}
void dfs4(int x,int y)
{
dfn[x]=++num1,re[num1]=x;
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y||a[x][i]==son[x])continue;
dfs4(a[x][i],x);
}
if(son[x])dfs4(son[x],x);
out[x]=num1;
return;
}
void dfs5(int x,int y)
{
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y||a[x][i]==son[x])continue;
dfs5(a[x][i],x);
}
int s1=s;
if(son[x])
{
dfs5(son[x],x);
for(int i=dfn[x];i<dfn[son[x]];i++)s=max(s,query(v[re[i]])),add(v[re[i]]);
}
else s=max(s,query(v[x])),add(v[x]);
ans1[x]=s;
if(son[y]!=x)
{
s=s1;
for(int i=dfn[x];i<=out[x];i++)del(v[re[i]]);
}
return;
}
int main()
{
int x=1,y=1,z;
scanf("%d",&n),p2[0]=1,s=0,num=1;
for(int i=1;i<=30;i++)p2[i]=p2[i-1]*2;
for(int i=1;i<n;i++)
{
scanf("%d%d%d",&x,&y,&z);
a[x].push_back(y),a[y].push_back(x);
t[x].push_back(z),t[y].push_back(z);
}
dfs3(1,0);
for(int i=1;i<=n;i++)
{
x=query(v[i]);
if(x>s){s=x;y=i;}
add(v[i]);
}
for(int i=1;i<=n;i++)
if((v[i]^v[y])==s){x=i;break;}
for(int i=1;i<=n;i++)ans[i]=s;
f[1][0]=f[1][1]=s=0,num=1,dfs1(x,0);
root=x,dfs2(1,0,1);
f[1][0]=f[1][1]=s=0,num=1,dfs1(y,0);
root=y,dfs2(1,0,1);
f[1][0]=f[1][1]=0,num=1,s=0;
memset(son,0,sizeof(son));
for(int i=1;i<=n;i++)
for(int j=0;j<a[i].size();j++)
if(a[i][j]>i&&siz[son[i]]<siz[a[i][j]])son[i]=a[i][j];
dfs4(1,0);
memset(v1,0,sizeof(v1));
dfs5(1,0),s=0;
for(int i=1;i<=n;i++)
if(i!=1)s=max(s,ans[i]+ans1[i]);
printf("%d",s);
return 0;
}
【Luogu P6864】 记忆
题目描述
有一个括号串 ()
),接下来有
-
在当前
的末尾加一对括号(即 变为S()
); -
在当前
的最外面加一对括号(即 变为(S)
); -
取消第
个操作,即去除第 个操作造成过的一切影响(例如,如果第 个操作也是取消操作,且取消了第 个操作,那么当前操作的实质就是恢复了第 个操作的作用效果)。
每次操作后,你需要输出
一个括号串能够括号匹配,当且仅当其左右括号数量相等,且任意一个前缀中左括号数量不少于右括号数量,
解题思路
假设没有撤销操作,我们来考虑如何处理。
我们设处理到第
考虑撤销操作,其实每个撤销操作可以看成令某个操作生效
我们只需求出
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long a[3][3];
}f[1000005],v1,v2,c1,c2,q1,w1;
int n,v[1000005],pre[1000005];
datay operator *(const datay &q,const datay &w)
{
datay c;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
{
c.a[i][j]=0;
for(int u=0;u<3;u++)c.a[i][j]+=q.a[i][u]*w.a[u][j];
}
return c;
}
void build(int x,int l,int r)
{
if(l==r){f[x]=c2;return;}
int mid=(l+r)>>1,lc=(x<<1),rc=(x<<1)|1;
build(lc,l,mid),build(rc,mid+1,r);
f[x]=f[lc]*f[rc];
return;
}
void modify1(int x,int l,int r,int k,int v)
{
if(l==r)
{
if(v==1)f[x]=v1;
else f[x]=v2;
return;
}
int mid=(l+r)>>1,lc=(x<<1),rc=(x<<1)|1;
if(k<=mid)modify1(lc,l,mid,k,v);
else modify1(rc,mid+1,r,k,v);
f[x]=f[lc]*f[rc];
return;
}
void modify2(int x,int l,int r,int k)
{
if(l==r){v[x]^=1;return;}
int mid=(l+r)>>1,lc=(x<<1),rc=(x<<1)|1;
if(k<=mid)modify2(lc,l,mid,k);
else modify2(rc,mid+1,r,k);
q1=(v[lc])?c2:f[lc];
w1=(v[rc])?c2:f[rc];
f[x]=q1*w1;
return;
}
int main()
{
int op,x;
v1.a[0][0]=v1.a[1][0]=v1.a[1][1]=v1.a[2][0]=v1.a[2][1]=v1.a[2][2]=1;
v2.a[0][0]=v2.a[2][0]=v2.a[2][1]=v2.a[2][2]=1;
c1.a[0][0]=c1.a[0][1]=c1.a[0][2]=c2.a[0][0]=c2.a[1][1]=c2.a[2][2]=1;
scanf("%d",&n),build(1,1,n);
for(int i=1;i<=n;i++)
{
scanf("%d",&op);
if(op==1)modify1(1,1,n,i,1);
if(op==2)modify1(1,1,n,i,2);
if(op==3)
{
scanf("%d",&x);
if(pre[x])pre[i]=pre[x],modify2(1,1,n,pre[i]);
else pre[i]=x,modify2(1,1,n,pre[i]);
}
printf("%lld\n",(c1*f[1]).a[0][0]);
}
return 0;
}
【Luogu P3246】 序列
题目描述
给定长度为
现在有
解题思路
数据范围是可以跑
由于每个区间的贡献为
由于要求区间
那么从
注意到最后经过的节点一定是区间最小值,那我们只需要预处理
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
int l,r,p;
long long v;
}t[1000005];
int n,m,block,pre[100005],las[100005],z,f[100005][21],lo[100005],p[21],x;
long long pre_f[100005],las_f[100005],a[100005],s;
stack<int> l1;
bool cmp1(datay q,datay w)
{
if((q.l-1)/block!=(w.l-1)/block)return q.l<w.l;
return q.r<w.r;
}
bool cmp2(datay q,datay w)
{
return q.p<w.p;
}
int query(int l,int r)
{
z=lo[r-l+1];
if(a[f[l][z]]<a[f[r-p[z]+1][z]])return f[l][z];
return f[r-p[z]+1][z];
}
void pre_add(int l,int r)
{
x=query(l,r);
s+=las_f[l]-las_f[x]+a[x]*(r-x+1);
return;
}
void pre_del(int l,int r)
{
x=query(l,r);
s-=las_f[l]-las_f[x]+a[x]*(r-x+1);
return;
}
void las_add(int l,int r)
{
x=query(l,r);
s+=pre_f[r]-pre_f[x]+a[x]*(x-l+1);
return;
}
void las_del(int l,int r)
{
x=query(l,r);
s-=pre_f[r]-pre_f[x]+a[x]*(x-l+1);
return;
}
int main()
{
int l=1,r=0;
p[0]=-(lo[0]=-1);
for(int i=1;i<=20;i++)p[i]=p[i-1]<<1;
for(int i=1;i<=100000;i++)lo[i]=lo[i>>1]+1;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]),f[i][0]=i;
for(int i=1;i<=m;i++)scanf("%d%d",&t[i].l,&t[i].r),t[i].p=i;
for(int i=1;i<=20;i++)
for(int j=1;j+p[i]-1<=n;j++)
{
if(a[f[j][i-1]]<a[f[j+p[i-1]][i-1]])f[j][i]=f[j][i-1];
else f[j][i]=f[j+p[i-1]][i-1];
}
block=sqrt(n);
sort(t+1,t+m+1,cmp1);
for(int i=1;i<=n;i++)
{
while(l1.size()!=0&&a[l1.top()]>=a[i])l1.pop();
if(l1.size()!=0)pre[i]=l1.top();
l1.push(i);
}
while(l1.size()!=0)l1.pop();
for(int i=1;i<=n;i++)pre_f[i]=pre_f[pre[i]]+a[i]*(i-pre[i]);
l1.push(n+1),a[n+1]=-1e9-5;
for(int i=n;i>=1;i--)
{
while(l1.size()!=0&&a[l1.top()]>=a[i])l1.pop();
if(l1.size()!=0)las[i]=l1.top();
l1.push(i);
}
for(int i=n;i>=1;i--)las_f[i]=las_f[las[i]]+a[i]*(las[i]-i);
for(int i=1;i<=m;i++)
{
while(l>t[i].l)l--,pre_add(l,r);
while(r<t[i].r)r++,las_add(l,r);
while(l<t[i].l)pre_del(l,r),l++;
while(r>t[i].r)las_del(l,r),r--;
t[i].v=s;
}
sort(t+1,t+m+1,cmp2);
for(int i=1;i<=m;i++)printf("%lld\n",t[i].v);
return 0;
}
【ARC101F】 Robots and Exits
题目描述
现在有
现在执行若干次操作,每次操作可以是:
- 将所有机器人的坐标减一
- 将所有机器人的坐标加一
当一个机器人移到出口的的时候他就会消失
操作将进行直到所有机器人消失
两种操作序列不同,当且仅当存在至少一个机器人在两次操作序列进行完成后从不同的出口消失
给出每个机器人和出口的坐标,求有多少种不同的操作序列,输出方案数对
坐标
解题思路
首先每个机器人只可能从它左边第一个出口和它右边第一个出口出来,具体从哪个出要看向左移动的多还是向右移动的多。
我们可以求出每个机器人距离左边和右边的出口多远,设为
我们找一种映射方案,把这些机器人都放到坐标轴上,坐标为
由于全部一起操作不好做,我们把机器人看成两条直线,为
还是不好做,我们把机器人的
此时问题已经变成了一个二维偏序问题,为了去重,我们只需将轨迹紧贴着那些在它下面的点即可。
那么我们就可以做
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
struct datay
{
long long x,y;
}a[500005];
long long n,m,b1[500005],b2[500005],f[500005],d[500005],s=1;
long long lowbit(long long x)
{
return x&(-x);
}
void modify(long long x,long long y)
{
for(int i=x;i<=n;i+=lowbit(i))f[i]=(f[i]+y)%mod;
return;
}
long long query(long long x)
{
long long h=0;
while(x)h=(h+f[x])%mod,x-=lowbit(x);
return h;
}
bool cmp1(datay q,datay w)
{
if(q.x!=w.x)return q.x<w.x;
return q.y>w.y;
}
bool cmp2(datay q,datay w)
{
return q.y<w.y;
}
bool cmp3(datay q,datay w)
{
if(q.x==0||q.y==0)return false;
if(w.x==0||w.y==0)return true;
return false;
}
int main()
{
long long x=0,y=0;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld",&b1[i]);
for(int i=1;i<=m;i++)scanf("%lld",&b2[i]);
sort(b1+1,b1+n+1),sort(b2+1,b2+m+1);
for(int i=1;i<=n;i++)
{
while(x<m&&b2[x+1]<=b1[i])x++;
if(x==0)a[i].x=0;
else a[i].x=b1[i]-b2[x];
}
x=m+1;
for(int i=n;i>=1;i--)
{
while(x>1&&b2[x-1]>=b1[i])x--;
if(x==m+1)a[i].y=0;
else a[i].y=b2[x]-b1[i];
}
sort(a+1,a+n+1,cmp3);
for(int i=1;i<=n;i++)
if(a[i].x<=0||a[i].y<=0){n=i-1;break;}
sort(a+1,a+n+1,cmp2),x=0;
for(int i=1;i<=n;i++)
{
if(a[i].y!=a[i-1].y)x++;
a[i-1].y=y,y=x;
}
a[n].y=x;
sort(a+1,a+n+1,cmp1);
for(int i=1;i<=n;i++)
{
if(a[i].x==a[i-1].x&&a[i].y==a[i-1].y)continue;
d[i]=query(a[i].y-1)+1;
modify(a[i].y,d[i]);
}
for(int i=1;i<=n;i++)s=(s+d[i])%mod;
cout<<s;
return 0;
}
杂题
【Luogu P10991】 选段排序
题目描述
给定一个长度为
求选择一个区间排序后
解题思路
这题不好做贪心,我们来猜一下性质。
对于一个区间
那对于区间
对于
我们用个数据结构维护即可,可以用优先队列,这里用了线段树,时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
int n,k1,k2,a[200005],f[4000005],s,maxx;
void modify(int x,int l,int r,int k,int v)
{
if(l==r){f[x]+=v;return;}
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
if(k<=mid)modify(lc,l,mid,k,v);
else modify(rc,mid+1,r,k,v);
f[x]=f[lc]+f[rc];
return;
}
int query(int x,int l,int r,int k)
{
if(l==r)return l;
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
if(f[lc]<k)return query(rc,mid+1,r,k-f[lc]);
return query(lc,l,mid,k);
}
int main()
{
scanf("%d%d%d",&n,&k1,&k2);
for(int i=1;i<=n;i++)scanf("%d",&a[i]),maxx=max(maxx,a[i]);
for(int i=k1;i<k2;i++)modify(1,1,maxx,a[i],1);
for(int i=k2;i<=n;i++)
{
modify(1,1,maxx,a[i],1);
s=max(s,query(1,1,maxx,k2-k1+1)-query(1,1,maxx,1));
}
memset(f,0,sizeof(f));
for(int i=k1+1;i<=k2;i++)modify(1,1,maxx,a[i],1);
for(int i=k1;i>=1;i--)
{
modify(1,1,maxx,a[i],1);
s=max(s,query(1,1,maxx,k2-i+1)-query(1,1,maxx,k1-i+1));
}
printf("%d",s);
return 0;
}
【ARC121D】 1 or 2
题目描述
你有
你需要吃糖,每次你可以选择吃
你需要求出吃完所有糖果的所有可能的情况中,黑板上数字最大值和最小值之差最小是多少,
解题思路
有两个操作不好处理,我们把这两个操作看成一个操作。
我们思考一下,每次只吃一个糖相当于吃了一个糖和吃了一棵为
考虑每次如果吃两个糖怎样最优,肯定是一头一尾的吃最好。
我们每次枚举有多少颗为
Code
#include<bits/stdc++.h>
using namespace std;
long long n1,n2,n,m,a1[5005],a2[5005],t[10005],maxx,minn,s=2e9;
int main()
{
long long x;
scanf("%lld",&n);
if(n==1){cout<<0;return 0;}
for(int i=1;i<=n;i++)
{
scanf("%lld",&x);
if(x<=0)a1[++n1]=x;
else a2[++n2]=x;
}
sort(a1+1,a1+n1+1);
sort(a2+1,a2+n2+1);
for(int i=((n-1)/2+1)*2;i<=2*n;i+=2)
{
m=0,maxx=-2e9,minn=2e9;
for(int j=1;j<=n1;j++)t[++m]=a1[j];
for(int j=1;j<=i-n;j++)t[++m]=0;
for(int j=1;j<=n2;j++)t[++m]=a2[j];
for(int j=1;j<=m/2;j++)maxx=max(maxx,t[j]+t[m-j+1]),minn=min(minn,t[j]+t[m-j+1]);
s=min(maxx-minn,s);
}
cout<<s;
return 0;
}
【ARC108F】 Paint Tree
题目描述
给定一棵
设
求所有
解题思路
我们分开枚举每一种情况,设
根据这个性质,那么若当前最远距离为
设
设
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
int n,deep[200005],f[200005][21],root,root1,l;
long long d[200005],p2[200005],s;
vector<int> a[200005];
int LCA(int x,int y)
{
if(deep[x]<deep[y])swap(x,y);
for(int i=20;i>=0;i--)x=(deep[f[x][i]]>=deep[y]?f[x][i]:x);
if(x==y)return x;
for(int i=20;i>=0;i--)x=(f[x][i]!=f[y][i])?f[x][i]:x,y=(deep[x]!=deep[y]?f[y][i]:y);
return f[x][0];
}
int dis(int x,int y)
{
return deep[x]+deep[y]-2*deep[LCA(x,y)];
}
void dfs1(int x,int y)
{
deep[x]=deep[y]+1,f[x][0]=y;
if(deep[root]<deep[x])root=x;
for(int i=0;i<a[x].size();i++)
if(a[x][i]!=y)dfs1(a[x][i],x);
return;
}
int main()
{
int x,y;
scanf("%d",&n),p2[0]=1;
for(int i=1;i<n;i++)
{
scanf("%d%d",&x,&y);
a[x].push_back(y);
a[y].push_back(x);
}
dfs1(1,0),root1=1;
for(int i=1;i<=20;i++)
for(int j=1;j<=n;j++)f[j][i]=f[f[j][i-1]][i-1];
for(int i=1;i<=n;i++)
if(dis(root,root1)<dis(root,i))root1=i;
for(int i=1;i<=n;i++)
{
if(i==root||i==root1)continue;
d[max(dis(root,i),dis(root1,i))]++;
l=max(l,min(dis(root,i),dis(root1,i)));
}
for(int i=1;i<=n;i++)d[i]+=d[i-1];
for(int i=1;i<=n;i++)p2[i]=p2[i-1]*2%mod;
s=(p2[d[l]]*l%mod);
for(int i=l+1;i<=n;i++)s=(s+(p2[d[i]]-p2[d[i-1]])*i%mod)%mod;
printf("%lld",((s+mod)*2+dis(root,root1)*p2[n-1]%mod)%mod);
return 0;
}
【MXOI 11.23 T2】 交换
题目描述
有一个长度为
你需要执行
输出
解题思路
我们先来考虑没有
此题每次操作的位置要后移当前操作次数位 ,那么每次执行的操作变不同了,我们考虑在原来的做法上优化。
注意到第二组
时间复杂度
Code
#include<bits/stdc++.h>
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
using namespace std;
struct datay
{
long long x,y;
}t[100005];
long long n,m,k,b[100005],f[100005],a[100005],d[100005],i;
int main()
{
long long x,y;
scanf("%lld%lld%lld",&n,&m,&k),x=k/m,y=((k/m)-1)*m;
for(i=0;i<n;i++)scanf("%lld",&b[i]),f[i]=a[i]=i;
for(i=0;i<m;i++)scanf("%lld%lld",&t[i].x,&t[i].y);
for(i=1;i<=m;i++)swap(a[(t[i%m].x+i+y)%n],a[(t[i%m].y+i+y)%n]);
for(i=0;i<n;i++)a[i]=(a[i]+m)%n;
while(x)
{
if(x&1)
{
for(i=0;i<n;i++)d[i]=f[a[i]];
for(i=0;i<n;i++)f[i]=d[i];
}
for(i=0;i<n;i++)d[i]=a[a[i]];
for(i=0;i<n;i++)a[i]=d[i];
x>>=1;
}
for(i=(k/m)*m+1;i<=k;i++)swap(f[(t[i%m].x+i%n)%n],f[(t[i%m].y+i%n)%n]);
for(i=0;i<n;i++)f[i]=(f[i]-((k/m)*m)%n)%n,f[i]=(f[i]+n)%n;
for(i=0;i<n;i++)printf("%lld ",b[f[i]]);
return 0;
}
【Luogu P10833】 下 Niz
题目描述
给定长度为
; 是 的排列。
解题思路
手玩一些样例后发现答案其实不多,我们考虑找弱化条件来找每一个区间。
我们考虑满足这样条件的区间
由于区间内只能有一个
由于区间长度
我们还需检查这些区间是否满足题目要求,用线段树存每个值最后出现的位置,用线段树二分求出区间
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
int l,r;
}t[2000005];
int n,a[1000005],pre[1000005],las[1000005],m,s,f[4000005];
vector<int> l1;
stack<int> l;
bool cmp1(datay q,datay w){return q.r<w.r;}
void modify(int x,int l,int r,int k,int v)
{
if(l==r){f[x]=v;return;}
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
if(k<=mid)modify(lc,l,mid,k,v);
else modify(rc,mid+1,r,k,v);
f[x]=min(f[lc],f[rc]);
return;
}
int query(int x,int l,int r,int k)
{
if(l==r)return l-(f[x]<k);
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
if(f[lc]>=k)return query(rc,mid+1,r,k);
return query(lc,l,mid,k);
}
int query1(int x,int l,int r,int k)
{
if(l==r)return f[x];
int mid=(l+r)>>1;
if(k<=mid)return query1((x<<1),l,mid,k);
return query1((x<<1)|1,mid+1,r,k);
}
int main()
{
int x,q,w,y;
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]==1)l1.push_back(i);
}
a[0]=a[n+1]=n+2,l.push(0),s=-l1.size();
for(int i=1;i<=n;i++)
{
while(l.size()&&a[l.top()]<=a[i])l.pop();
pre[i]=l.top();
l.push(i);
}
while(l.size())l.pop();
l.push(n+1);
for(int i=n;i>=1;i--)
{
while(l.size()&&a[l.top()]<=a[i])l.pop();
las[i]=l.top();
l.push(i);
}
for(int i=0;i<l1.size();i++)
{
x=l1[i];
if(i!=0)q=l1[i-1];
else q=0;
if(i!=l1.size()-1)w=l1[i+1];
else w=n+1;
while(x>q)
{
y=x,x=pre[x];
for(int j=max(x,q)+1;j<=y;j++)t[++m].l=j,t[m].r=(j+a[y]-1);
}
x=l1[i];
while(x<w)
{
y=x,x=las[x],q=min(x,w);
for(int j=y;j<q;j++)t[++m].l=j-a[y]+1,t[m].r=j;
}
}
sort(t+1,t+m+1,cmp1);
for(int i=1;i<=m;i++)
{
if(t[i].r>n)break;
for(int j=t[i-1].r+1;j<=t[i].r;j++)modify(1,1,n,a[j],j);
if(t[i].l>=1&&query(1,1,n,t[i].l)>=t[i].r-t[i].l+1)s++;
}
printf("%d",s);
return 0;
}
【CF442C】 Artem and Array
题目描述
给定长度为
请你计算至多能得到多少分。
解题思路
对于三个数
先对一遍数列中的每个数都做一次这个操作,删完后的数列再不断的重复,最后可以发现,若一个数
我们可以在每读入一个数就判断
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
long long n,a[5000005],s;
vector<long long> l1;
int main()
{
scanf("%lld",&n);
if(n==1){printf("0");return 0;}
scanf("%lld%lld",&a[1],&a[2]);
for(int i=3;i<=n;i++)
{
scanf("%lld",&a[i]);
while(i>=2&&a[i-2]>=a[i-1]&&a[i-1]<=a[i])s+=min(a[i-2],a[i]),a[i-1]=a[i],n--,i--;
}
sort(a+1,a+n+1);
for(int i=1;i<=n-2;i++)s+=a[i];
cout<<s;
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧