2024寒假小结
树
【luogu P1084】 [NOIP2012 提高组] 疫情控制
题目描述
H 国有 $n $ 个城市,这 \(n\) 个城市用 $ n-1 $ 条双向道路相互连通构成一棵树,$1 $ 号城市是首都,也是树中的根节点。
H 国的首都爆发了一种危害性极高的传染病。当局为了控制疫情,不让疫情扩散到边境城市(叶子节点所表示的城市),决定动用军队在一些城市建立检查点,使得从首都到边境城市的每一条路径上都至少有一个检查点,边境城市也可以建立检查点。但特别要注意的是,首都是不能建立检查点的。
现在,在 H 国的一些城市中已经驻扎有军队,且一个城市可以驻扎多个军队。一支军队可以在有道路连接的城市间移动,并在除首都以外的任意一个城市建立检查点,且只能在一个城市建立检查点。一支军队经过一条道路从一个城市移动到另一个城市所需要的时间等于道路的长度(单位:小时)。
请问最少需要多少个小时才能控制疫情。注意:不同的军队可以同时移动。(\(1 \le n \le 5 \times 10^4\))
解题思路
军队可以同时移动,求最少时间,让我们想到二分。
已知时间,我们如何求是否存在可行一种可行的方案?我们可以贪心解决。
首先,先将所有的军队尽量上移到根节点的子节点,将所有能到达根节点的子节点的军队按剩余时间从小到大排。
然后处理哪些根节点的子节点的子树还是有未符合条件的(不包含已到根节点的子节点的军队),很明显,对于哪些未符合条件的,只需将军队移动到根节点的子节点就可以了。
找哪些军队可以去别的子树,若军队所在子树不符合条件,那么也需要留下一个不能走到根节点到走回来的节点(贪心,该军队从根节点走到的节点所需时间必小于它走到根节点的时间,不如让其他点驻扎),处理好军队统一到根节点,排序后与不满足要求的子树比较即可。
时间复杂度 \(O(nlog^2n)\) 。
Code
#include<bits/stdc++.h>
using namespace std;
long long n,m,d[50005],f[50005][21],f1[50005][21],b[50005],ll,tr[50005],rr,fa[50005],bb[50005],l1;
vector<long long> a[50005],t[50005];
bool v[50005];
void dijah(long long x,long long y)
{
fa[x]=fa[y];
if(y==1)fa[x]=x;
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
dijah(a[x][i],x);
f1[a[x][i]][0]=t[x][i];
f[a[x][i]][0]=x;
}
return;
}
bool dfs(long long x,long long y)
{
if(v[x])return true;
if(a[x].size()==1&&a[x][0]==y)
{
if(y==1)tr[++rr]=x;
return false;
}
bool qwe=true;
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
qwe&=dfs(a[x][i],x);
}
if((!qwe)&&(y==1))tr[++rr]=x;
return qwe;
}
bool cmp1(long long q,long long w)
{
if(!v[q])return false;
if(!v[w])return true;
return f1[q][0]>f1[w][0];
}
bool cmp2(long long q,long long w)
{
return q>w;
}
bool cmp3(long long q,long long w)
{
return f1[q][20]>f1[w][20];
}
int main()
{
long long x,y,z;
scanf("%lld",&n);
for(int i=1;i<n;i++)
{
scanf("%lld%lld%lld",&x,&y,&z);
a[x].push_back(y);
a[y].push_back(x);
t[x].push_back(z);
t[y].push_back(z);
}
dijah(1,0);
for(int i=1;i<=20;i++)
{
for(int j=1;j<=n;j++)f[j][i]=f[f[j][i-1]][i-1],f1[j][i]=f1[j][i-1]+f1[f[j][i-1]][i-1];
}
scanf("%lld",&m);
for(int i=1;i<=m;i++)
{
scanf("%lld",&d[i]);
}
long long l=1,r=1e10+5,mid,p,poi,s=1e10+5;
while(l<=r)
{
memset(v,false,sizeof(v));
mid=(l+r)>>1;
ll=rr=l1=0;
for(int i=1;i<=m;i++)
{
p=d[i];
poi=mid;
for(int j=20;j>=0;j--)
{
if(f1[p][j]<=poi)
{
poi-=f1[p][j];
p=f[p][j];
}
}
if(p<=1)b[++ll]=d[i];
else
{
v[p]=true;
}
}
dfs(1,0);
if(rr>ll)
{
l=mid+1;
continue;
}
memset(v,false,sizeof(v));
for(int i=1;i<=rr;i++)v[tr[i]]=true;
sort(b+1,b+ll+1,cmp3);
for(int i=1;i<=ll;i++)
{
if(v[fa[b[i]]]&&f1[b[i]][20]+f1[fa[b[i]]][20]>mid)v[fa[b[i]]]=false;
else bb[++l1]=mid-f1[b[i]][20];
}
p=0;
sort(tr+1,tr+rr+1,cmp1);
sort(bb+1,bb+l1+1,cmp2);
for(int i=1;i<=rr;i++)
{
if(!v[tr[i]])continue;
if(bb[i]-f1[tr[i]][0]<0)
{
p=1;
break;
}
}
if(p)
{
l=mid+1;
continue;
}
r=mid-1;
s=min(s,mid);
}
if(s==1e9+5)cout<<-1;
else cout<<s;
return 0;
}
【CF1132G】 Greedy Subsequences
题目描述
定义一个序列的最长贪心严格上升子序列为:若选出的子序列为 \(a\),对于其中相邻两项 \(i,j\),不存在 \(i < k < j\),满足在原序列 \(b\) 中,有 \(b_i < b_k\),换句话说就是选择一个元素后必须选择它之后第一个大于它的元素
给定一个长度为 \(n\) 的序列,同时给定一个常数 \(k\),求该序列的所有长度为 \(k\) 的子区间的最长贪心严格上升子序列的长度
其中 \(1 \le k \le n \le 10^6, 1 \le a_i \le n\)
解题思路
因为每个数后面接的肯定是第一个比他大的数(只有一个),所以我们可以以此建一棵树,每个节点的父亲为他后面对应的第一个比它大的数。
此问题转化为:树上一个点集,每次加入一个节点并删去一个节点,求一个节点距离祖先最大的距离。
我们可以用 \(dfn\) 序维护:每次加进去的数所对应的节点的子树的所有点的权值 \(+1\) ,删去一个点时把该点的点权 \(-INF\) ,每次查询所有点的权值最大值即可。
用线段树维护,时间复杂度 \(O(nlogn)\) 。
Code
#include<bits/stdc++.h>
using namespace std;
long long n,m,a[1000005],out[1000005],dfn[1000005],num,f[4000005],d[4000005];
stack<long long> l;
vector<long long> t[1000005];
void galaxy(long long x,long long l,long long r,long long v)
{
f[x]+=v,d[x]+=v;
return;
}
void pushdown(long long x,long long l,long long r)
{
if(d[x]==0)return;
long long 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 dijah(long long x,long long l,long long r,long long ql,long long qr,long long v)
{
if(ql<=l&&r<=qr)
{
galaxy(x,l,r,v);
return;
}
pushdown(x,l,r);
long long mid=(l+r)>>1,lc=(x<<1),rc=(x<<1)|1;
if(ql<=mid)dijah(lc,l,mid,ql,qr,v);
if(qr>mid)dijah(rc,mid+1,r,ql,qr,v);
f[x]=max(f[lc],f[rc]);
return;
}
long long gaia(long long x,long long l,long long r,long long ql,long long qr)
{
if(ql<=l&&r<=qr)return f[x];
pushdown(x,l,r);
long long mid=(l+r)>>1,lc=(x<<1),rc=(x<<1)|1,h=0;
if(ql<=mid)h=gaia(lc,l,mid,ql,qr);
if(qr>mid)h=max(h,gaia(rc,mid+1,r,ql,qr));
return h;
}
void dfs(long long x,long long y)
{
dfn[x]=++num;
for(int i=0;i<t[x].size();i++)dfs(t[x][i],x);
out[x]=num;
return;
}
int main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
l.push(n+1);
a[n+1]=1e6+5;
for(int i=n;i>=1;i--)
{
while(l.size()&&a[l.top()]<=a[i])l.pop();
t[l.top()].push_back(i);
l.push(i);
}
dfs(n+1,0);
for(int i=1;i<m;i++)dijah(1,1,n+1,dfn[i],out[i],1);
for(int i=1;i<=n-m+1;i++)
{
dijah(1,1,n+1,dfn[i+m-1],out[i+m-1],1);
if(i!=1)dijah(1,1,n+1,dfn[i-1],dfn[i-1],-1e9);
printf("%lld ",gaia(1,1,n+1,1,n+1));
}
return 0;
}
Tree Queries
题目描述
给定一棵\(N\)个节点的树,有\(Q\)次操作
\(1\space v\space d\) 给定一个点\(v\)和一个权值\(d\),等概率地选择一个点\(r\),对每一个点\(u\),若\(v\)在\(u\)到\(r\)的路径上,则\(u\)的权值加上\(d\) (权值一开始为\(0\))
\(2\space v\) 查询\(v\)的权值期望,对\(998244353\)取模
\(1\leqslant N,Q \leqslant 150000\)
解题思路
根节点特殊处理,先来看别的节点。
对于一个节点 \(x\),若其被修改,那么不在其子树内的节点,权值会增加 $ size_x / n * d$ 。
若在其子树内,设其属于 \(y\) 的子树内,\(y\) 为 \(x\) 的儿子,该节点权值增加 \((n-size_y)/n * d\) 。
第一种情况用 \(dfn\) 序+线段树可解决,如何解决第二种情况,我们可以用到树剖向上跳时最多经过 \(logn\) 条链的性质,每次修改时只修改重儿子的子树,每次查询时,跳到链顶就代表链顶节点为轻儿子,此时再加。
时间复杂度为 \(O(nlogn)\) 。
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=998244353;
long long n,m,siz[150005],deep[150005],fa[150005],son[150005],dfn[150005],out[150005],top[150005],num,f[600005],d[600005],t[150005];
vector<long long> a[150005];
long long poww(long long x,long long y)
{
long long h=1;
while(y)
{
if(y&1)
{
h*=x;
h%=mod;
}
x*=x;
x%=mod;
y>>=1;
}
return h;
}
void up(long long x)
{
f[x]=f[x<<1]+f[(x<<1)|1];
return;
}
void galaxy(long long x,long long l,long long r,long long v)
{
f[x]+=(r-l+1)*v;
d[x]+=v;
f[x]%=mod;
d[x]%=mod;
return;
}
void pushdown(long long x,long long l,long long r)
{
if(d[x]==0)return;
long long lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
galaxy(lc,l,mid,d[x]);
galaxy(rc,mid+1,r,d[x]);
d[x]=0;
return;
}
void dijah(long long x,long long l,long long r,long long ql,long long qr,long long v)
{
if(ql<=l&&r<=qr)
{
galaxy(x,l,r,v);
return;
}
pushdown(x,l,r);
long long lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
if(ql<=mid)dijah(lc,l,mid,ql,qr,v);
if(qr>mid)dijah(rc,mid+1,r,ql,qr,v);
up(x);
return;
}
long long gaia(long long x,long long l,long long r,long long ql,long long qr)
{
if(ql<=l&&r<=qr)
{
return f[x];
}
pushdown(x,l,r);
long long lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1,h=0;
if(ql<=mid)h+=gaia(lc,l,mid,ql,qr);
if(qr>mid)h+=gaia(rc,mid+1,r,ql,qr);
return h%mod;
}
void dfs1(long long x,long long 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);
if(siz[a[x][i]]>siz[son[x]])son[x]=a[x][i];
siz[x]+=siz[a[x][i]];
}
return;
}
void dfs2(long long x,long long y)
{
dfn[x]=++num;
if(son[x]==0)
{
out[x]=num;
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);
}
out[x]=num;
return;
}
long long query(long long x)
{
long long h=0;
while(top[x]!=1)
{
x=top[x];
h+=(n-siz[x])*t[fa[x]];
x=fa[x];
h%=mod;
}
return h;
}
int main()
{
long long x,y,z;
scanf("%lld%lld",&n,&m);
for(int i=1;i<n;i++)
{
scanf("%lld%lld",&x,&y);
a[x].push_back(y);
a[y].push_back(x);
}
dfs1(1,0);
top[1]=1;
dfs2(1,0);
for(int i=1;i<=m;i++)
{
scanf("%lld%lld",&x,&y);
if(x==1)
{
scanf("%lld",&z);
dijah(1,1,n,dfn[y],dfn[y],n*z);
if(dfn[y]!=1)dijah(1,1,n,1,dfn[y]-1,siz[y]*z);
if(out[y]!=n)dijah(1,1,n,out[y]+1,n,siz[y]*z);
t[y]+=z;
if(son[y]!=0)dijah(1,1,n,dfn[son[y]],out[son[y]],(n-siz[son[y]])*z);
}
else
{
printf("%lld\n",(gaia(1,1,n,dfn[y],dfn[y])+query(y))*poww(n,mod-2)%mod);
}
}
return 0;
}
[USACO19DEC] Bessie's Snow Cow P
题目描述
农场下雪啦!Bessie 和往年开冬一样在堆雪牛。她之前是个写实派,总是想把她的雪牛堆得和个真牛一样。但今年不一样,受到来自东方的神秘力量的影响,她想来点抽象艺术,因此她想堆成一棵树的样子。这棵树由 \(N\) 个雪球,\(N-1\) 根树枝构成,每根树枝连接两个雪球,并且每两个雪球之间路径唯一。
Bessie 要给她的雪牛来点细节。因此她给其中一个雪球加了个鼻子,来表示这是他那抽象的牛的头,并且把它称作雪球 \(1\)。为了让雪牛更好看,她还要给某些雪球来点不同的颜色。于是,她用旧牛奶桶装满了颜料泼到雪牛上。这些颜料分别被编号为 \(1,2,\dots 10^5\),且每种颜色都无限量供应。
当 Bessie 把一桶颜料泼到一个雪球上时,这个雪球子树上的所有雪球也会被染色(我们称雪球 \(y\) 在雪球 \(x\) 的子树里当且仅当雪球 \(x\) 处在雪球 \(y\) 到雪球 \(1\) 的路径上)。Bessie 有着精确的泼颜料技术,因此在泼完一种颜料后,一个雪球上之前被染过的所有颜色依然清晰可见。例如,一个雪球之前显现出来颜色 \(\left[ 1,2,3 \right]\),然后 Bessie 把装有 \(4\) 号颜色的牛奶桶泼上去,那么这个雪球将显现出来颜色 \(\left[ 1,2,3,4 \right]\)。 在泼了几桶颜料以后,Bessie 可能想要了解她的雪牛有多五彩斑斓。令雪球 \(x\) 的『颜色丰富度』为这个雪球被染上的不同颜色总数 ,当 Bessie 想了解雪球 \(x\) 的相关信息时,你应该回答她雪球 \(x\) 的子树中所有的雪球的颜色丰富度之和。
救救孩子吧!
\(1 \le n,m \le 10^5\)
解题思路
我们先用 \(set\) 存储每个颜色在哪些节点被泼过,同时用线段树维护每个节点有多少颜色。
若颜色被重新覆盖,那一定是一个子树重新覆盖,所以,我们泼颜色时,可以检索 \(set\) 中在其子树内的节点,将这些节点的子树的节点的颜色数都 \(-1\) ,将他们从 \(set\) 中取出,最后再给一起泼颜色,全部 \(+1\) ,加入 \(set\)。
同时,若其祖先结点被泼过,那么该节点就不用泼,因为一个节点会覆盖其子树内节点,所以,若 \(set\) 中存在其祖先,一定是 \(set\) 中 \(dfn\) 序第一个小于其的节点,检验即可。
查询输出子树和,时间复杂度 \(O(nlogn)\) 。
Code
#include<bits/stdc++.h>
using namespace std;
long long n,m,dfn[100005],num,f[400005],d[400005],re[100005],out[100005];
vector<long long> a[100005];
set<long long> l[1000005];
void galaxy(long long x,long long l,long long r,long long v)
{
f[x]+=(r-l+1)*v;
d[x]+=v;
return;
}
void pushdown(long long x,long long l,long long r)
{
if(d[x]==0)return;
long long lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
galaxy(lc,l,mid,d[x]),galaxy(rc,mid+1,r,d[x]);
d[x]=0;
return;
}
void dijah(long long x,long long l,long long r,long long ql,long long qr,long long v)
{
if(ql<=l&&r<=qr)return galaxy(x,l,r,v),void();
pushdown(x,l,r);
long long mid=(l+r)>>1,lc=(x<<1),rc=(x<<1)|1;
if(ql<=mid)dijah(lc,l,mid,ql,qr,v);
if(qr>mid)dijah(rc,mid+1,r,ql,qr,v);
f[x]=f[lc]+f[rc];
return;
}
long long gaia(long long x,long long l,long long r,long long ql,long long qr)
{
if(ql<=l&&r<=qr)return f[x];
pushdown(x,l,r);
long long mid=(l+r)>>1,lc=(x<<1),rc=(x<<1)|1,h=0;
if(ql<=mid)h+=gaia(lc,l,mid,ql,qr);
if(qr>mid)h+=gaia(rc,mid+1,r,ql,qr);
return h;
}
void dfs1(long long x,long long y)
{
dfn[x]=++num;
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
dfs1(a[x][i],x);
}
out[x]=num,re[dfn[x]]=num;
return;
}
int main()
{
long long x,y;
scanf("%lld%lld",&n,&m);
for(int i=1;i<n;i++)
{
scanf("%lld%lld",&x,&y);
a[x].push_back(y),a[y].push_back(x);
}
dfs1(1,0);
set<long long>::iterator q,w,e;
for(int i=1;i<=m;i++)
{
scanf("%lld",&x);
if(x==1)
{
scanf("%lld%lld",&x,&y);
q=upper_bound(l[y].begin(),l[y].end(),dfn[x]);
if(q!=l[y].begin())
{
q--;
if(*q<=dfn[x]&&re[*q]>=dfn[x])continue;
}
q=lower_bound(l[y].begin(),l[y].end(),dfn[x]);
while(q!=l[y].end()&&*q<=out[x])
{
l[y].erase(q),dijah(1,1,n,*q,re[*q],-1);
q=lower_bound(l[y].begin(),l[y].end(),dfn[x]);
}
dijah(1,1,n,dfn[x],out[x],1);
l[y].insert(dfn[x]);
}
else
{
scanf("%lld",&x);
printf("%lld\n",gaia(1,1,n,dfn[x],out[x]));
}
}
return 0;
}
【CF1857G】 Counting Graphs
题目描述
给定一个有 \(n\) 个顶点的树,每个边有一个权值 \(w_i\)。
计算同时满足以下所有条件的带权图的数量:
- 该图无重边与自环。
- 所有的边的权值不大于给定的数 \(S\) 且为正整数。
- 该图有唯一的最小生成树。
- 该图的最小生成树是给定的树。
打印其模 \(998244353\) 的值。
我们说两个图是不同的,当且仅当其边集不同。
\(t\) 组数据,\(1\leq t \leq 10000\),\(2\leq \sum n \leq 2\times10^5\),\(1\leq S \leq 10^9\),\(1\leq w_i \leq S\)。
解题思路
按 \(kruskal\) 生成最小生成树的顺序来做,将每条边按边权从小到大排序,可以看做连接的两个集合中的每个点都可以连一条边。
一共有 \(size_x\times size_y-1\) 条边,每条边有 \(S-w_i+1\) 种可能,用快速幂解决。
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=998244353;
struct datay
{
long long x,y,v;
}a[200005];
long long n,m,f[200005],size1[200005];
long long dijah(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 search(long long x)
{
if(f[x]!=x)f[x]=search(f[x]);
return f[x];
}
bool cmp(datay q,datay w)
{
return q.v<w.v;
}
void poi()
{
long long x,y,s=1;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)f[i]=i,size1[i]=1;
for(int i=1;i<n;i++)scanf("%lld%lld%lld",&a[i].x,&a[i].y,&a[i].v);
sort(a+1,a+n,cmp);
for(int i=1;i<n;i++)
{
x=search(a[i].x),y=search(a[i].y);
s=(s*(dijah(m-a[i].v+1,size1[x]*size1[y]-1)%mod))%mod;
f[y]=x,size1[x]+=size1[y];
}
printf("%lld\n",s);
return;
}
int main()
{
long long qwe;
scanf("%lld",&qwe);
for(int i=1;i<=qwe;i++)poi();
return 0;
}
主席树
【luogu P4755】 Beautiful Pair
题目描述
小 D 有个数列 \(\{a\}\),当一个数对 \((i,j)\)(\(i \le j\))满足 \(a_i\) 和 \(a_j\) 的积不大于 \(a_i, a_{i+1}, \ldots, a_j\) 中的最大值时,小 D 认为这个数对是美丽的。请你求出美丽的数对的数量。(\(1 \le n \le 100000\))
解题思路
考虑对于每个数,在哪些区间中能取到最大值,找出左边和右边第 \(1\) 个比它大的数即可,用单调栈可解决。
对于一个数,是否存在其两边的区间中各选一个数,使得乘积小于等于该数。
我们可以枚举左边或右边,用该数除以枚举的数,查询另一边有多少个数小于等于商,这个可用主席树做。
但这样做时间复杂度是 \(O(n^2logn)\) 的,所以,我们选枚举的区间时要选长度小的区间,可将时间复杂度降到 \(O(nlog^2n)\) 。
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
int v,lc,rc;
}a[3000005];
int n;
long long t[100005];
int le[100005],ri[100005],m;
long long d[100005],b[100005];
stack<long long> l;
int root[100005],num;
int build(int l,int r)
{
if(l==r)
{
return ++num;
}
int p=++num,mid=(l+r)>>1;
a[p].lc=build(l,mid);
a[p].rc=build(mid+1,r);
return p;
}
int dijah(int x,int l,int r,int k,int v)
{
int h=++num;
a[h]=a[x];
if(l==r)
{
a[h].v++;
return h;
}
int mid=(l+r)>>1;
if(k<=mid)a[h].lc=dijah(a[x].lc,l,mid,k,v);
else a[h].rc=dijah(a[x].rc,mid+1,r,k,v);
a[h].v=a[a[h].lc].v+a[a[h].rc].v;
return h;
}
int gaia(int x,int l,int r,int ql,int qr)
{
if(qr==0)return 0;
if(ql<=l&&r<=qr)
{
return a[x].v;
}
int mid=(l+r)>>1,h=0;
if(ql<=mid)h+=gaia(a[x].lc,l,mid,ql,qr);
if(qr>mid)h+=gaia(a[x].rc,mid+1,r,ql,qr);
return h;
}
int p(long long x)
{
int l1=1,r1=m,mid,h=0;
while(l1<=r1)
{
mid=(l1+r1)>>1;
if(d[mid]<=x)h=max(h,mid),l1=mid+1;
else r1=mid-1;
}
return h;
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld",&t[i]);
l.push(0);
t[0]=t[n+1]=1e9+5;
for(int i=1;i<=n;i++)
{
while(l.size()!=0&&t[l.top()]<t[i])l.pop();
le[i]=l.top();
l.push(i);
}
while(l.size()!=0)l.pop();
l.push(n+1);
for(int i=n;i>=1;i--)
{
while(l.size()!=0&&t[l.top()]<=t[i])l.pop();
ri[i]=l.top();
l.push(i);
}
for(int i=1;i<=n;i++)le[i]++,ri[i]--;
root[0]=build(1,n);
for(int i=1;i<=n;i++)b[i]=t[i];
sort(b+1,b+n+1);
for(int i=1;i<=n;i++)
{
if(b[i]!=b[i-1])
{
d[++m]=b[i];
}
}
for(int i=1;i<=n;i++)t[i]=p(t[i]);
for(int i=1;i<=n;i++)
{
root[i]=dijah(root[i-1],1,n,t[i],1);
}
int qw=0;
long long s=0;
for(int i=1;i<=n;i++)
{
for(int j=le[i];j<=i;j++)
{
qw=p(d[t[i]]/d[t[j]]);
s+=gaia(root[ri[i]],1,n,1,qw)-gaia(root[i-1],1,n,1,qw);
}
}
cout<<s;
return 0;
}
【CF474F】 Ant colony
题目描述
给出一个长度为 \(n\) 的序列 \(s\),\(q\) 组询问。
每次给定区间 \([l,r]\)。
如果 \(i,j \in [l,r]\), \(s_i|s_j\) 则 \(i\) 得一分。
问有多少个没有得到满分,即 \(r-l\)。(\(1 \le n,q \le 10^5\))
解题思路
首先,得到满分的数一定是整个序列的 \(gcd\) 。
我们可以通过 \(ST\) 表求出一段的 \(gcd\) ,如何查询一段数中有多少个数等于某个数呢?
这个问题可以通过 \(STL\) 解决,这里我用的是主席树。
时间复杂度 \(O(nlogn)\) 。
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
int v,lc,rc;
}a[3000005];
int n,b[100005],f[100005][21],p[21],lo[1000005],num,root[1000005],m;
set<int> l;
map<int,int> pp;
int build(int l,int r)
{
if(l==r)
{
return ++num;
}
int mid=(l+r)>>1,h=++num;
a[h].lc=build(l,mid);
a[h].rc=build(mid+1,r);
return h;
}
int dijah(int x,int l,int r,int k,int v)
{
if(l==r)
{
a[++num]=a[x];
a[num].v+=v;
return num;
}
int mid=(l+r)>>1,h=++num;
a[h]=a[x];
if(k<=mid)a[h].lc=dijah(a[x].lc,l,mid,k,v);
else a[h].rc=dijah(a[x].rc,mid+1,r,k,v);
a[h].v=a[a[h].lc].v+a[a[h].rc].v;
return h;
}
int gaia(int x,int l,int r,int ql,int qr)
{
if(ql<=l&&r<=qr)return a[x].v;
int mid=(l+r)>>1,h=0;
if(ql<=mid)h+=gaia(a[x].lc,l,mid,ql,qr);
if(qr>mid)h+=gaia(a[x].rc,mid+1,r,ql,qr);
return h;
}
int query(int x,int y)
{
int z=lo[y-x+1];
return __gcd(f[x][z],f[y-p[z]+1][z]);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%d",&b[i]);
l.insert(b[i]);
f[i][0]=b[i];
}
p[0]=1;
lo[0]=-1;
for(int i=1;i<=20;i++)p[i]=p[i-1]<<1;
for(int i=1;i<=n;i++)lo[i]=lo[i>>1]+1;
for(int i=1;p[i]<=n;i++)
{
for(int j=1;j+p[i]-1<=n;j++)
{
f[j][i]=__gcd(f[j][i-1],f[j+p[i-1]][i-1]);
}
}
int g=0;
set<int>::iterator q=l.begin();
for(;q!=l.end();q++)
{
pp[*q]=++g;
}
for(int i=1;i<=n;i++)b[i]=pp[b[i]];
root[0]=build(1,n);
for(int i=1;i<=n;i++)root[i]=dijah(root[i-1],1,n,b[i],1);
scanf("%d",&m);
int x,y,z;
for(int i=1;i<=m;i++)
{
scanf("%d%d",&x,&y);
z=pp[query(x,y)];
if(z==0)printf("%d\n",y-x+1);
else printf("%d\n",y-x+1-gaia(root[y],1,n,1,z)+gaia(root[x-1],1,n,1,z));
}
return 0;
}
DP
【CF788C】 The Great Mixing
题目描述
有k种可乐,第i瓶可乐的CO2浓度是ai/1000,问要配置出浓度n/1000的可乐,最少需要几瓶可乐。
输入:
第一行n和k。
第二行k个整数,第i个整数表示ai。
输出:一行,表示最少需要几瓶,无解输出-1。
(\(1 \le n \le 10^3,1 \le k \le 10^6\))
解题思路
处理平均数的一个方法:将所有 \(a_i\) 减去 \(n\) ,找出一些数他们的平均数为 \(0\) ,即他们的和为 \(0\) 。
\(a_i\) 虽然有 \(10^6\) 个,但本质不同的数量级在 \(10^3\) 。
如何找呢,因为实际最小最大到不了 \(10^6\) ,开 \(01\) 背包都能过。
正解:分析可得,最后构成答案的数按一定顺序排列,存在他们的前缀和的绝对值都小于等于 \(1000\) ,\(bfs\) 就可以了。
Code
#include<bits/stdc++.h>
using namespace std;
int n,m,a[1000005];
int f[4000005];
int main()
{
ios::sync_with_stdio(false);
memset(f,1,sizeof(f));
cin>>n>>m;
for(int i=1;i<=m;i++)cin>>a[i],a[i]-=n;
sort(a+1,a+m+1);
int k=unique(a+1,a+m+1)-a-1;
for(int i=1;i<=k;i++)
{
f[2000000+a[i]]=1;
if(a[i]>0)
{
for(int j=-500000;j<=500000;j++)f[j+2000000]=min(f[j+2000000],f[j+2000000-a[i]]+1);
}
else
{
for(int j=500000;j>=-500000;j--)f[j+2000000]=min(f[j+2000000],f[j+2000000-a[i]]+1);
}
}
if(f[2000000]<10000000)cout<<f[2000000];
else cout<<-1;
return 0;
}
【luogu P5851】 [USACO19DEC] Greedy Pie Eaters P
题目描述
Farmer John 有 \(M\) 头奶牛,为了方便,编号为 \(1,\dots,M\)。这些奶牛平时都吃青草,但是喜欢偶尔换换口味。Farmer John 一天烤了 \(N\) 个派请奶牛吃,这 \(N\) 个派编号为 \(1,\dots,N\)。第 \(i\) 头奶牛喜欢吃编号在 \(\left[ l_i,r_i \right]\) 中的派(包括两端),并且没有两头奶牛喜欢吃相同范围的派。第 \(i\) 头奶牛有一个体重 \(w_i\),这是一个在 \(\left[ 1,10^6 \right]\) 中的正整数。
Farmer John 可以选择一个奶牛序列 \(c_1,c_2,\dots,c_K\),并让这些奶牛按这个顺序轮流吃派。不幸的是,这些奶牛不知道分享!当奶牛 吃派时,她会把她喜欢吃的派都吃掉——也就是说,她会吃掉编号在 \([l_{c_i},r_{c_i}]\) 中所有剩余的派。Farmer John 想要避免当轮到一头奶牛吃派时,她所有喜欢的派在之前都被吃掉了这样尴尬的情况。因此,他想让你计算,要使奶牛按 \(c_1,c_2,\dots,c_K\) 的顺序吃派,轮到这头奶牛时她喜欢的派至少剩余一个的情况下,这些奶牛的最大可能体重(\(w_{c_1}+w_{c_2}+\ldots+w_{c_K}\))是多少。(\(1 \le N \le 300\))
解题思路
区间DP,\(f_{i,j}\)表示 \(i\) 到 \(j\) 中允许没有空余所取得的价值最大值,有 \(f_{i,j}=max(f_{i,k}+f_{k+2,j}+v_{k,i,j})\),其中 \(i-1 \le k \le j-1\) ,\(v_{k,i,j}\) 表示在 \(i\) 到 \(j\) 内且包含 \(k\) 的奶牛的 \(w_i\) 最大值,注意处理 \(f_{i,j}\) 中 \(i>j\) 的特殊情况。
\(v_{k,i,j}\) 要怎么处理呢,容易发现有 \(v_{k,i,j}=max(v_{k,i+1,j},v_{k,i,j-1})\) 。
时间复杂度 \(O(n^3)\) 。
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long l,r,v;
}a[900005];
long long n,m,f[305][305],p[305][305][305];
int main()
{
long long k;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++)
{
scanf("%lld%lld%lld",&a[i].v,&a[i].l,&a[i].r);
for(int j=a[i].l;j<=a[i].r;j++)
{
p[j][a[i].l][a[i].r]=max(p[j][a[i].l][a[i].r],a[i].v);
}
}
for(int i=1;i<=n;i++)
{
for(int j=2;j<=n;j++)
{
for(int u=1;u<=i;u++)
{
k=j+u-1;
if(k<i||k>n)continue;
p[i][u][k]=max(max(p[i][u+1][k],p[i][u][k-1]),p[i][u][k]);
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n-i+1;j++)
{
k=i+j-1;
f[j][k]=max(p[j][j][k]+f[j+1][k],p[k][j][k]+f[j][k-1]);
for(int u=j;u<k;u++)f[j][k]=max(f[j][k],f[j][u]+f[u+1][k]);
for(int u=j+1;u<k;u++)f[j][k]=max(f[j][k],f[j][u-1]+f[u+1][k]+p[u][j][k]);
}
}
cout<<f[1][n];
return 0;
}
概率
【CF1753C】Wish I Knew How to Sort
题目描述
给定一个长度为 \(n\) 的 01 序列 \(a\) 和一种操作,你需要用这种操作将序列从小到大排序。
操作如下:
- 等概率随机选取两个位置 \(i,j\ (i<j)\),若 \(a_i>a_j\),则交换 \(a_i,a_j\)。
注意:当 \(a_i\le a_j\) 时,交换失败,也算作一次操作。
请你求出操作被执行的 期望次数。对 998244353 取模。\(1\le n\le2\times 10^5,\ a_i\in \{0,1\}\)。
解题思路
设序列中一共有 \(k\) 个 \(0\) ,那么,最终结果就是前面 \(k\) 个 \(0\) ,后面 \(n-k\) 个 \(1\) 。
设原序列前 \(k\) 项有 \(x\) 个 \(1\) ,所以就是要将这 \(x\) 个 \(0\) 换到后面去。
只有和后面的 \(x\) 个 \(0\) 交换才有效,每次选两个数,选到左边为 \(1\) ,右边是 \(1\) 的概率为 \(p=2 \times x^2/(n-1)n\) 。
设成功一次的期望为 \(q\) ,那么,有 \(q=p+(1-p)(q+1)\) ,得 \(pq=1\) , \(q=1/p=(n-1)n/x^2/2\)。
求和即可。
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=998244353;
long long dijah(long long x,long long y)
{
long long h=1;
while(y)
{
if(y&1)
{
h*=x;
h%=mod;
}
x*=x;
y>>=1;
x%=mod;
}
return h;
}//快速幂
long long n,a[200005];
void poi()
{
long long k=0,m=0,s=0;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
if(!a[i])k++;
}
for(int j=1;j<=k;j++)
{
if(a[j])m++;
}
for(long long i=1;i<=m;i++)
{
s=(s+dijah(i*i%mod,mod-2))%mod;//求和
}
s*=n;
s%=mod;
s*=(n-1);
s%=mod;
s*=dijah(2,mod-2);
s%=mod;
printf("%lld\n",s);
return;
}
int main()
{
long long qwe;
scanf("%lld",&qwe);
for(int i=1;i<=qwe;i++)poi();
return 0;
}
整体二分
【luogu P3242】[HNOI2015] 接水果
题目描述
风见幽香非常喜欢玩一个叫做 osu! 的游戏,其中她最喜欢玩的模式就是接水果。由于她已经 DT FC 了 The big black,她觉得这个游戏太简单了,于是发明了一个更加难的版本。
首先有一个地图,是一棵由 \(n\) 个顶点,\(n-1\) 条边组成的树。
这颗树上有 \(p\) 个盘子,每个盘子实际上是一条路径,并且每个盘子还有一个权值。第 \(i\) 个盘子就是顶点 \(a_i\) 到顶点 \(b_i\) 的路径(由于是树,所以从 \(a_i\) 到 \(b_i\) 的路径是唯一的),权值为 \(c_i\)。
接下来依次会有 \(q\) 个水果掉下来,每个水果本质上也是一条路径,第 \(i\) 个水果是从顶点 \(u_i\) 到顶点 \(v_i\) 的路径。
幽香每次需要选择一个盘子去接当前的水果:一个盘子能接住一个水果,当且仅当盘子的路径是水果的路径的子路径。这里规定:从 \(a\) 到 \(b\) 的路径与从 \(b\) 到 \(a\) 的路径是同一条路径。
当然为了提高难度,对于第 \(i\) 个水果,你需要选择能接住它的所有盘子中,权值第 \(k_i\) 小的那个盘子,每个盘子可重复使用(没有使用次数的上限:一个盘子接完一个水果后,后面还可继续接其他水果,只要它是水果路径的子路径)。幽香认为这个游戏很难,你能轻松解决给她看吗?
\(1\leq n,p,q \leq4\times 10^4\),\(0 \le c \le 10^9\)。
解题思路
分类讨论盘子能接住水果的情况,设盘子从 \(u\) 到 \(v\),\(dfn_u<dfn_v\)。
- \(LCA(u,v)!=u\)
那么,只要水果的一端在 \(dfn_u\) 到 \(out_u\) 内,另外一端在 \(dfn_v\) 到 \(out_v\) 内就能满足要求 。 - \(LCA(u,v)=u\)
设 \(z\) 为 \(u\) 的一个子节点,且 \(z\) 包含 \(v\) 。
水果的一端在 \(1\) 到 \(dfn_z -1\) 或 \(out_u+1\) 到 \(n\) 内,另外一端到 \(dfn_v\) 到 \(out_v\) 内就满足要求。
那么我们每个水果可以看成一个点,每个盘子看成一个或两个矩形(第一种或第二种情况),那就是查询包含一个点的所有矩形的第 \(k\) 小权值是多少了。
看到第 \(k\) 小,我们就可以用整体二分来做了。
在整体二分里面将矩形信息加进数据结构中时,我们可以用扫描线来做,每次存储一列的信息,最后查询即可。
时间复杂度 \(O(nlog^2n)\) 。
Code
#include<bits/stdc++.h>
using namespace std;
struct data
{
long long x,y,v,k;
}a[40005];
struct datay
{
long long x,y,k,v,z;
bool ok;
}b[40005],b1[40005],b2[40005];
struct line
{
long long x,l,r,v;
}li[240005];
long long n,m1,m2,dfn[40005],out[40005],num,fa[40005][21],deep[40005],f[40005];
vector<long long> t[40005];
long long search(long long x,long long y)
{
swap(x,y);
for(int i=20;i>=0;i--)
{
if(deep[fa[x][i]]>deep[y])x=fa[x][i];
}
return x;
}
long long lowbit(long long x)
{
return x&(-x);
}
void dijah(long long x,long long y)
{
for(int i=x;i<=n+1;i+=lowbit(i))f[i]+=y;
return;
}
long long gaia(long long x)
{
long long h=0;
while(x)
{
h+=f[x];
x-=lowbit(x);
}
return h;
}
bool LCA(long long x,long long y)
{
if(deep[x]<deep[y])swap(x,y);
for(int i=20;i>=0;i--)
{
if(deep[fa[x][i]]>=deep[y])x=fa[x][i];
}
if(x==y)return true;
return false;
}
bool cmp1(data q,data w)
{
return q.v<w.v;
}
bool cmp2(datay q,datay w)
{
return q.z<w.z;
}
bool cmp3(datay q,datay w)
{
return dfn[q.x]<dfn[w.x];
}
bool cmp4(line q,line w)
{
return q.x<w.x;
}
void dfs1(long long x,long long y)
{
deep[x]=deep[y]+1;
fa[x][0]=y;
dfn[x]=++num;
for(int i=0;i<t[x].size();i++)
{
if(t[x][i]==y)continue;
dfs1(t[x][i],x);
}
out[x]=num;
return;
}
void solve(long long l,long long r,long long L,long long R)
{
long long mid=(L+R)>>1,m3=0,q=0,q1=0,q2=0,qw=0;
for(int i=L;i<=mid;i++)
{
if(!a[i].k)
{
li[++m3].x=dfn[a[i].x];
li[m3].l=dfn[a[i].y];
li[m3].r=out[a[i].y];
li[m3].v=1;
li[++m3].x=out[a[i].x]+1;
li[m3].l=dfn[a[i].y];
li[m3].r=out[a[i].y];
li[m3].v=-1;
}
else
{
li[++m3].x=1;
li[m3].l=dfn[a[i].y];
li[m3].r=out[a[i].y];
li[m3].v=1;
li[++m3].x=dfn[a[i].k];
li[m3].l=dfn[a[i].y];
li[m3].r=out[a[i].y];
li[m3].v=-1;
if(out[a[i].y]==n)continue;
li[++m3].x=dfn[a[i].y];
li[m3].l=out[a[i].k]+1;
li[m3].r=n;
li[m3].v=1;
li[++m3].x=out[a[i].y]+1;
li[m3].l=out[a[i].k]+1;
li[m3].r=n;
li[m3].v=-1;
}
}
sort(li+1,li+m3+1,cmp4);
for(int i=l;i<=r;i++)
{
while(q<m3&&li[q+1].x<=dfn[b[i].x])dijah(li[q+1].l,li[q+1].v),dijah(li[q+1].r+1,-li[q+1].v),q++;
qw=gaia(dfn[b[i].y]);
if(qw==b[i].k)b1[++q1]=b[i],b1[q1].ok=true;
else if(qw>b[i].k)b1[++q1]=b[i];
else b2[++q2]=b[i],b2[q2].k-=qw;
}
for(int i=l;i<=l+q1-1;i++)b[i]=b1[i-l+1];
for(int i=l+q1;i<=r;i++)b[i]=b2[i-l-q1+1];
for(int i=1;i<=q;i++)dijah(li[i].l,-li[i].v),dijah(li[i].r+1,li[i].v);
if(L==R)
{
for(int i=l;i<=r;i++)b[i].v=L;
return;
}
solve(l,l+q1-1,L,mid);
solve(l+q1,r,mid+1,R);
return;
}
int main()
{
long long x,y;
scanf("%lld%lld%lld",&n,&m1,&m2);
for(int i=1;i<n;i++)
{
scanf("%lld%lld",&x,&y);
t[x].push_back(y);
t[y].push_back(x);
}
dfs1(1,0);
for(int i=1;i<=20;i++)
{
for(int j=1;j<=n;j++)fa[j][i]=fa[fa[j][i-1]][i-1];
}
for(int i=1;i<=m1;i++)
{
scanf("%lld%lld%lld",&a[i].x,&a[i].y,&a[i].v);
if(dfn[a[i].x]>dfn[a[i].y])swap(a[i].x,a[i].y);
if(LCA(a[i].x,a[i].y))a[i].k=search(a[i].x,a[i].y);
}
sort(a+1,a+m1+1,cmp1);
for(int i=1;i<=m2;i++)
{
scanf("%lld%lld%lld",&b[i].x,&b[i].y,&b[i].k);
b[i].z=i;
if(dfn[b[i].x]>dfn[b[i].y])swap(b[i].x,b[i].y);
}
sort(b+1,b+m2+1,cmp3);
solve(1,m2,1,m1);
sort(b+1,b+m2+1,cmp2);
for(int i=1;i<=m2;i++)printf("%lld\n",(b[i].ok==0?-1:a[b[i].v].v));
return 0;
}
线段树
【CF558E】A Simple Task
题目描述
题目大意: 给定一个长度不超过10^5的字符串(小写英文字母),和不超过50000个操作。
每个操作 L R K 表示给区间[L,R]的字符串排序,K=1为升序,K=0为降序。
最后输出最终的字符串。
解题思路
线段树的一类应用。
开 \(26\) 个线段树,记录每个字母出现次数。
每次修改时查询范围内的每个字母出现次数,然后直接26次区间赋值即可。
最后一位一位查询输出即可。
Code
#include<bits/stdc++.h>
using namespace std;
int n,m,f[26][400005],d[26][400005],t[26];
void galaxy(int p,int x,int l,int r,int v)
{
f[p][x]=(r-l+1)*v;
d[p][x]=v;
return;
}
void pushdown(int p,int x,int l,int r)
{
if(d[p][x]==-1)return;
int mid=(l+r)>>1;
galaxy(p,x<<1,l,mid,d[p][x]),galaxy(p,(x<<1)|1,mid+1,r,d[p][x]);
d[p][x]=-1;
return;
}
void dijah(int p,int x,int l,int r,int ql,int qr,int v)
{
if(ql<=l&&r<=qr)
{
galaxy(p,x,l,r,v);
return;
}
pushdown(p,x,l,r);
int mid=(l+r)>>1,lc=(x<<1),rc=(x<<1)|1;
if(ql<=mid)dijah(p,lc,l,mid,ql,qr,v);
if(qr>mid)dijah(p,rc,mid+1,r,ql,qr,v);
f[p][x]=f[p][lc]+f[p][rc];
return;
}
int gaia(int p,int x,int l,int r,int ql,int qr)
{
if(ql<=l&&r<=qr)return f[p][x];
pushdown(p,x,l,r);
int lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1,h=0;
if(ql<=mid)h+=gaia(p,lc,l,mid,ql,qr);
if(qr>mid)h+=gaia(p,rc,mid+1,r,ql,qr);
return h;
}
int main()
{
memset(d,-1,sizeof(d));
int l,r,v;
string x;
scanf("%d%d",&n,&m);
cin>>x;
for(int i=0;i<x.size();i++)dijah(x[i]-'a',1,1,n,i+1,i+1,1);
for(int i=1;i<=m;i++)
{
memset(t,0,sizeof(t));
scanf("%d%d%d",&l,&r,&v);
for(int j=0;j<26;j++)t[j]=gaia(j,1,1,n,l,r);
if(v==1)
{
for(int j=0;j<26;j++)dijah(j,1,1,n,l,r,0);
for(int j=0;j<26;j++)
{
if(t[j]==0)continue;
dijah(j,1,1,n,l,l+t[j]-1,1);
l+=t[j];
}
}
else
{
for(int j=0;j<26;j++)dijah(j,1,1,n,l,r,0);
for(int j=25;j>=0;j--)
{
if(t[j]==0)continue;
dijah(j,1,1,n,l,l+t[j]-1,1);
l+=t[j];
}
}
}
for(int i=1;i<=n;i++)
{
for(int j=0;j<26;j++)
{
if(gaia(j,1,1,n,i,i)>=1)
{
cout<<char(j+'a');
}
}
}
return 0;
}
数学
【luogu P8883】幻想中成为原神
题目描述
其中一个问题是这样的:定义一个丘丘人是可以被击杀的,当且仅当存在一个大于 \(1\) 的完全平方数能够整除它的编号。比如,\(12\) 号丘丘人就是可以被击杀的,因为它能够被 \(4\) 整除;\(15\) 号丘丘人则不能被击杀。请计算编号为 \(1\sim n\) 中的丘丘人中能够被击杀的个数。由于钟离秉承着“差不多得了”的做事理念,因此,他允许你的答案与真正的答案有着不超过 \(2\times10^4\) 的绝对误差。(\(1 \le n \le 10^{18}\))
解题思路
正解时整除分块,难打,讲一下另一种解法。
注意到题目中有一句话叫“你的答案与真正的答案有着不超过 \(2\times10^4\) 的绝对误差”,由此,我们可以估计一下 \(n\) 中这些出现数的概率。
逆向思维,不含平方因子的概率为 \(\prod_{p}1-p^2\)。
由欧拉乘积公式 \(\sum_{n}n^s=\prod_{p}(1-p^{-s})^{-1}\),原式为 \(\frac{1}{\sum^{ }_{\infty}\frac{1}{i^2}}=\frac{6}{\pi ^2}\) 。
直接把 \(n\) 乘上 \(1- \frac{6}{\pi^2}\) 。
Code
#include<bits/stdc++.h>
using namespace std;
long long n,x;
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&x);
long long p=x*(1.0)*(0.39207289814597);
printf("%lld\n",p);
}
return 0;
}
【luogu P10083】 [GDKOI2024 提高组] 不休陀螺
题目描述
有 \(n\) 张牌组成一个序列,每张牌用一个二元组 \((a_i
, b_i)\) 表示,意味着打出这张牌需要消耗 \(a_i\) 点费用,打出后可以获得 \(b_i\) 点费用。
接下来你可以选择一个区间 \([l, r]\) 将这个区间中的卡取出来作为你的卡组。
开始时你的卡组会按照随机顺序排列并且你有 \(E\) 点费用,然后你会依次从前往后打出这个排列中的卡。
当你打完这个排列中的卡后你的卡组又会重新随机排列然后你再依次打出,直到你无法再打出下一张牌(当前费用小于下一张牌需要消耗的费用)时停止。
如果一个卡组无论在什么情况下都能够无限打下去,我们则称这卡组可以“陀螺无限”。
现在求有多少个区间组成的卡组能够“陀螺无限”。
解题思路
若一个卡组打完一遍后费用还少了,肯定不行,所以每打完一次你的费用要增加,这个可以用前缀和解决。
考虑极限情况,先加到最少,再加回来,最少点前加的要最多,后面减得也要越多,这样才最有可能减到 \(0\) 。
最小点的值可以用前缀和解决,设最小点为 \(E\) ,前面一个为 \(x\) ,后面一个为 \(y\) ,有 \(a_x>b_x,a_y<b_y\) ,若 \(E-max(b_x,a_y)<0\) ,则不行。
上面的数字可以看成 \(E-max(min(a_x,b_x),min(a_y,b_y))\),可以用ST表维护。
小于 \(0\) 的是有一定范围的,维护这里面的前缀和满足要求的即可。
Code
#include<bits/stdc++.h>
using namespace std;
long long n,m,a[1000005],b[1000005],f1[1000005],f[1000005][21],p[21],lo[1000005],f2[1000005],d[1000005],s;
set<long long> ll;
map<long long,long long> pp;
long long gaia(long long x,long long y)
{
long long z=lo[y-x+1];
return max(f[x][z],f[y-p[z]+1][z]);
}
long long lowbit(long long x)
{
return x&(-x);
}
void dijah(long long x,long long y)
{
for(int i=x;i<=n;i+=lowbit(i))d[i]+=y;
return;
}
long long gaia1(long long x)
{
long long h=0;
while(x)
{
h+=d[x];
x-=lowbit(x);
}
return h;
}
int main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)scanf("%lld",&b[i]),f[i][0]=min(a[i],b[i]);
ll.insert(0);
long long s=0;
for(int i=1;i<=n;i++)f1[i]+=b[i]-a[i]+f1[i-1],ll.insert(f1[i]);
p[0]=1,lo[0]=-1;
for(int i=1;i<=20;i++)p[i]=p[i-1]<<1;
for(int i=1;i<=n;i++)lo[i]=lo[i>>1]+1;
for(int i=1;p[i]<=n;i++)
{
for(int j=1;j+p[i]-1<=n;j++)f[j][i]=max(f[j][i-1],f[j+p[i-1]][i-1]);
}
for(int i=1;i<=n;i++)
{
f2[i]=f2[i-1];
if(b[i]<a[i])f2[i]+=a[i]-b[i];
}
long long g=0;
set<long long>::iterator q=ll.begin();
for(;q!=ll.end();q++)pp[*q]=++g;
f1[0]=pp[0];
for(int i=1;i<=n;i++)f1[i]=pp[f1[i]];
long long r=0;
for(int i=1;i<=n;i++)
{
while(r<n)
{
if(gaia(i,r+1)+f2[r+1]-f2[i-1]<=m)r++,dijah(f1[r],1);
else break;
}
if(r>=i)s+=r-i-gaia1(f1[i-1]-1)+1;
dijah(f1[i],-1);
}
cout<<s;
return 0;
}
网络流
【luogu P10080】 [GDKOI2024 提高组] 匹配
题目描述
给定一个 \(2n\) 个点 \(m\) 条边的二分图,左部点编号为 \(1 \sim n\),右部点编号为 \(n + 1 \sim 2n\)。
给定每条边为黑色或白色,你需要找到一个完美匹配,使得匹配里的黑色边数恰好为偶数。
如果你对二分图的定义有疑问:
- 二分图是一个无向图,点分为左右两部分,每部分各 \(n\) 个点,每条边都连接两个属于不同部分的点。
- 一个完美匹配是一个大小为 \(n\) 的边的集合,使得每个点都恰好与集合里的一条边相连。
(\(1 \le n \le 500\))
解题思路
如果没有黑色边数恰好为偶数的限制,那么直接用网络流跑即可。
我们求出一个完美匹配方案,黑色边方案为偶数直接输出,为奇数就进行调整。
因为网络流跑满的边在残量网络中会反过来,利用这条性质,我们可以发现,若网络流残量网络上存在一个环,且环上的黑色边数为奇数,那么将这个环上的边全部反过来就可以了,黑色边数会变成偶数。
找奇环每个点只用 \(dfs\) 一次,注意要拆点,将每个点拆成到此点的黑色边数分别为奇数与偶数两种情况来存储。
最后输出方案即可。
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
int nex,to,maxx;
}a[1000005];
int n,m,start,endd,head[10005],num=-1,d[10005],now[10005],b[1000005],r,v1[1000005],vv[10005][2],v2[10005][2];
queue<int> l;
bool can;
void add(int x,int y,int z)
{
a[++num].to=y,a[num].maxx=z,a[num].nex=head[x];
head[x]=num;
return;
}
void edge(int x,int y,int z)
{
add(x,y,z),add(y,x,0);
return;
}
bool bfs()
{
memset(d,-1,sizeof(d));
queue<int> l;
l.push(start),d[start]=0,now[start]=head[start];
int x,u,v;
while(l.size())
{
x=l.front();
l.pop();
for(int i=head[x];i!=-1;i=a[i].nex)
{
u=a[i].to,v=a[i].maxx;
if(v>0&&d[u]==-1)d[u]=d[x]+1,now[u]=head[u],l.push(u);
}
}
if(d[endd]==-1)return false;
return true;
}
int dfs(int x,int y)
{
if(x==endd)return y;
int now_y=y,u,v,fl;
for(;now[x]!=-1;now[x]=a[now[x]].nex)
{
if(now_y==0)break;
u=a[now[x]].to,v=a[now[x]].maxx;
if(v>0&&d[u]==d[x]+1)
{
fl=dfs(u,min(v,now_y));
now_y-=fl,a[now[x]].maxx-=fl,a[now[x]^1].maxx+=fl;
}
}
return y-now_y;
}
int dinic()
{
int s=0;
while(bfs())s+=dfs(start,1e9+5);
return s;
}
void dijah(int x,int y)
{
if(vv[x][!(y&1)]!=-1)
{
y=x;
for(int i=r;i>=1;i--)
{
a[b[i]].maxx^=1,a[b[i]^1].maxx^=1;
x=a[b[i]^1].to;
if(x==y)break;
}
can=true;
return;
}
if(vv[x][(y&1)]!=-1)return;
vv[x][y&1]=y;
for(int i=head[x];i!=-1;i=a[i].nex)
{
if(a[i].maxx>0)
{
b[++r]=i;
if(!v2[a[i].to][(y+v1[i/2+1])&1])dijah(a[i].to,(y+v1[i/2+1])&1);
if(can)return;
r--;
}
}
vv[x][y&1]=-1,v2[x][y&1]=1;
return;
}
bool check()
{
int h=0;
for(int i=0;i<2*m;i+=2)
{
if(!a[i].maxx)h+=v1[i/2+1];
}
if(h&1)return false;
return true;
}
void put()
{
for(int i=0;i<2*m;i+=2)
{
if(!a[i].maxx)printf("%d ",i/2+1);
}
printf("\n");
return;
}
void poi()
{
memset(vv,-1,sizeof(vv));
memset(v1,0,sizeof(v1));
memset(head,-1,sizeof(head));
int x,y,z;
scanf("%d%d",&n,&m);
can=false,r=0,num=-1,start=2*n+1,endd=2*n+2;
for(int i=1;i<=m;i++)
{
scanf("%d%d%d",&x,&y,&v1[i]);
if(x>y)swap(x,y);
edge(x,y,1);
}
for(int i=1;i<=n;i++)edge(start,i,1),edge(i+n,endd,1);
int p=dinic();
if(p<n)
{
cout<<"-1\n";
return;
}
if(check())
{
put();
return;
}
memset(v2,0,sizeof(v2));
for(int i=1;i<=endd;i++)
{
if(!v2[i][0])dijah(i,0);
if(can)break;
}
if(!can)
{
cout<<"-1\n";
return;
}
put();
return;
}
int main()
{
int qwe;
scanf("%d",&qwe);
for(int i=1;i<=qwe;i++)poi();
return 0;
}
贪心
【GJOI 2024.2.16 T1】 楼房搭建
题目描述
一个长度为 \(n\) 的序列 \(a\),初始时全部为 \(0\) ,每次操作可以使一个位置上的 \(a_i\) \(+1\) ,它旁边的一个位置 \(a_{i+1}/a_{i-1}\) \(+1\) ,问至少多少次操作能使任意 \(1 \le i \le n\) 使得 \(a_i \ge h_i\) , \(h\)为给出的序列。 (\(1 \le n \le 10^6\))
解题思路
贪心。
使前面的所用次数最少,但这样做很明显是错的。
考虑反悔贪心。
- 若 \(a_i+2,a_{i+1}+1\) ,现在变成 \(2\) 次 \(a_i+1,a_{i+1}+2\) ,相当于用一次操作给 \(a_{i+1}+3\) 。
- 若 \(a_i+3\),变成 \(a_i+1,a_{i+1}+2\) \(a_i+2,a_{i+1}+1\) ,即 \(a_i+3,a_{i+1}+3\),相当于用一次操作给 \(a_{i+1}+3\) 。
- 若 \(a_i+3\),变成三次 \(a_i+1,a_{i+1}+2\) ,相当于两次操作给 \(a_{i+1}+2 \times 3\) 。
所以,对于一个 \(i\) ,若前面的 \(a_{i-1}\) 有 \(x\) 次 \(a_{i-1}+2,a_i+1\) ,有 \(y\) 次 \(a_{i-1}+3\) ,那么 \(a_i\) 就可以加 \(x+2 \times y\) 次 \(3\) 。
让 \(a_i\) 尽可能的 \(+3\) ,若有剩余,就按原来贪心的做。
Code
#include<bits/stdc++.h>
using namespace std;
long long n,a[1000005],x,y,s,f[1000005];
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)
{
if(a[i]<0)a[i]=0;
x=min(a[i]/3,f[i]);
a[i]-=3*x;
y=a[i]/2,a[i]-=2*y;
s+=x+y+a[i];
f[i+1]+=x*2+y,a[i+1]-=a[i]*2+y;
}
cout<<s;
return 0;
}
容斥
【GJOI 2024.2.16 T3】 数字收藏
题目描述
有 \(n\) 次操作,每次加入或删除一个数,问每次操作后序列中有多少对数,他们的最大公因数为 \(k\) 。(\(1 \le n,z \le 10^5\),\(z\) 为加入数的最大值)
解题思路
考虑加入的数或删除的数跟原数集的数有多少个的最大公因数为 \(k\) 即可。
若 \(k\) 不整除该数,那不用考虑。
否则将该数除以 \(k\) ,设为 \(x\) ,考虑数集中有多少个数跟 \(x\) 互质。
设 \(i\) 出现了 \(cnt_i\) 次,根据容斥原理,\(ans=\sum^{}_{d|x}mu_d\times cnt_d\) 。
\(mu\) 为莫比乌斯函数。
时间复杂度 \(O(n\sqrt{z})\) 。
Code
#include<bits/stdc++.h>
using namespace std;
long long n,m,s,mu[200005],prime[200005],l,f[200005],b[200005];
bool v[200005];
vector<long long> a[100005];
int main()
{
mu[1]=1;
for(int i=2;i<=100000;i++)
{
if(!v[i])prime[++l]=i,mu[i]=-1;
for(int j=1;j<=l;j++)
{
if(i*prime[j]>100000)break;
v[i*prime[j]]=1;
if(i%prime[j]==0)break;
mu[i*prime[j]]=-mu[i];
}
}
for(int i=1;i<=100000;i++)
{
for(int j=i;j<=100000;j+=i)a[j].push_back(i);
}
long long x,y;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)
{
scanf("%lld%lld",&x,&y);
if(y%m!=0)
{
printf("%lld\n",s);
continue;
}
y/=m;
if(x==1)
{
b[y]++;
for(int j=0;j<a[y].size();j++)s+=mu[a[y][j]]*f[a[y][j]];
for(int j=0;j<a[y].size();j++)f[a[y][j]]++;
}
else
{
if(b[y]!=0)
{
b[y]--;
for(int j=0;j<a[y].size();j++)f[a[y][j]]--;
for(int j=0;j<a[y].size();j++)s-=mu[a[y][j]]*f[a[y][j]];
}
}
printf("%lld\n",s);
}
return 0;
}
【GJOI 2024.2.17 T2】 赢家
题目描述
给你一个 \(n\) 点 \(m\) 边的无向图,现在将边定向,问你有多少种方案使得 \(1,2\) 点都能到达一个点。(\(1 \le n \le 15,1 \le m \le 120\))
解题思路
考虑用总方案 \(2^m\) 减去不行的方案。
枚举包含 \(1\) 的点集 \(x\) 与包含 \(2\) 的点集 \(y\),\(x\) 与 \(y\) 不能相交且不能有边相连,设 \(1\) 已能到 \(x\) 里面的所有点,方案数为 \(f_x\) ,\(y\) 也同理,方案数为 \(g_y\),设 \(z\) 为除了 \(x,y\) 的其他点,那么要使 \(z\) 到 \(x,y\) 的所有边都指向 \(x,y\) ,方案数为 \(f_x \times g_y \times cnt_z\)。(\(cnt_z\) 为 \(z\) 包含的边的数量)
如何求 \(f_x,g_y\) ?以 \(f_x\) 为例,还是减去不行的方案,只用枚举 \(x\) 的子集设为 \(y\) ,剩余的点设为 \(z\) ,将 \(y\) 与 \(z\) 之间的边全部定为指向 \(y\) 的,为 \(f_y \times cnt_z\) 最后用 \(2_{cnt_x}\) 减去即可。
枚举子集的方法:令 \(y=x\) ,让 \(y-1\) 同时 \(y\) & $ =x$ 即可枚举子集。
总时间复杂度 \(O(3^n)\) 。
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;
long long n,m,qwe,b[25][25],d[40005],p[1005],f1[40005],f2[40005],s;
int main()
{
long long x,y;
scanf("%lld%lld%lld",&n,&m,&qwe);
for(int i=1;i<=m;i++)
{
scanf("%lld%lld",&x,&y);
b[x][y]=b[y][x]=1;
}
p[0]=1;
for(int i=1;i<=n*n;i++)p[i]=(p[i-1]*2)%mod;
for(int i=0;i<p[n];i++)
{
for(int j=1;j<=n;j++)
{
if(((1<<(j-1))&i))
{
d[i]=d[i^(1<<(j-1))];
for(int u=1;u<=n;u++)
{
if((1<<(u-1))&(i^(1<<(j-1))))d[i]+=b[j][u];
}
break;
}
}
}
for(int i=0;i<p[n];i++)
{
if(i&1)
{
f1[i]=p[d[i]];
for(int j=(i-1)&i;j>0;j=(j-1)&i)f1[i]-=(f1[j]*p[d[j^i]])%mod;
f1[i]%=mod,f1[i]+=mod,f1[i]%=mod;
}
if(i&2)
{
f2[i]=p[d[i]];
for(int j=(i-1)&i;j>0;j=(j-1)&i)f2[i]-=(f2[j]*p[d[j^i]])%mod;
f2[i]%=mod,f2[i]+=mod,f2[i]%=mod;
}
}
s=p[m];
for(int i=0;i<p[n];i++)
{
if(i&1)
{
for(int j=(i+1)|i;j<p[n];j=(j+1)|i)
{
if((i^j)&2)
{
x=p[n]-1-j;
if(d[i^x]+d[i^j^x]-d[x]==m)s-=((f1[i]*f2[i^j])%mod)*p[d[x]]%mod;
}
}
}
}
s%=mod,s+=mod,s%=mod;
cout<<s;
return 0;
}