2024.2 总结
树
【CF771C】 Bear and Tree Jumps
题目描述
给出一个
解题思路
考虑枚举每个点对的
设
很明显,这些东西可以东西可以通过
已知这些东西后,我们可以考虑答案了。
我们可知:
把他们拆开,考虑
Code
#include<bits/stdc++.h>
using namespace std;
long long n,m,f[200005][10],d[200005][10],s,siz[200005];
vector<long long> a[200005];
void dijah(long long x,long long y)
{
siz[x]=1,d[x][0]++;
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
dijah(a[x][i],x);
for(int j=0;j<m;j++)
{
for(int u=1;u<m;u++)
{
if(j+u+1<=m)s-=d[a[x][i]][j]*d[x][u];
}
}
for(int j=0;j<m;j++)d[x][(j+1)%m]+=d[a[x][i]][j];
for(int j=0;j<m;j++)f[x][(j+1)%m]+=f[a[x][i]][j];
f[x][1%m]+=d[a[x][i]][0],siz[x]+=siz[a[x][i]];
}
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
for(int j=0;j<m;j++)s+=(siz[x]-siz[a[x][i]])*(f[a[x][i]][j]+(j==0?d[a[x][i]][j]:0));
}
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);
}
dijah(1,0);
cout<<s;
return 0;
}
【CF1009F】 Dominant Indices
题目描述
给定一棵以
对于每个点,求最小的
解题思路
树上启发式合并板子题。
Code
#include<bits/stdc++.h>
using namespace std;
long long n,siz[1000005],deep[1000005],son[1000005],fa[1000005],f[1000005],dfn[1000005],out[1000005],num,re[1000005],d[1000005];
vector<long long> a[1000005];
void dfs1(long long x,long long y)
{
siz[x]=1,deep[x]=deep[y]+1,fa[x]=y;
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(long long x,long long y)
{
dfn[x]=++num,re[num]=x;
if(son[x]!=0)dfs2(son[x],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);
}
out[x]=num;
return;
}
void dfs3(long long x,long long y)
{
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y||a[x][i]==son[x])continue;
dfs3(a[x][i],x);
}
if(son[x]!=0)
{
dfs3(son[x],x);
d[x]=d[son[x]],f[deep[x]]=1;
if(f[d[x]]==1)d[x]=deep[x];
for(int i=out[son[x]]+1;i<=out[x];i++)
{
f[deep[re[i]]]++;
if(f[deep[re[i]]]==f[d[x]])d[x]=min(d[x],deep[re[i]]);
if(f[deep[re[i]]]>f[d[x]])d[x]=deep[re[i]];
}
}
else f[deep[x]]++,d[x]=deep[x];
if(son[fa[x]]!=x)
{
for(int i=dfn[x];i<=out[x];i++)f[deep[re[i]]]--;
}
return;
}
int main()
{
long long x,y;
scanf("%lld",&n);
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),dfs2(1,0),dfs3(1,0);
for(int i=1;i<=n;i++)printf("%lld\n",d[i]-deep[i]);
return 0;
}
【Luogu P2634】 聪聪可可
题目描述
给出一棵
解题思路
点分治模板题。
Code
#include<bits/stdc++.h>
using namespace std;
long long n,size[100005],minn[100005],root,maxx,all,f[3],f1[3],s;
bool v[100005];
vector<long long> a[100005],t[100005];
void search(long long x,long long y)
{
size[x]=1;
long long minn=0;
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y||v[a[x][i]])continue;
search(a[x][i],x);
size[x]+=size[a[x][i]],minn=max(minn,size[a[x][i]]);
}
minn=max(minn,all-size[x]);
if(minn<maxx)root=x,maxx=minn;
return;
}
void dfs(long long x,long long y,long long z)
{
f1[z%3]++;
for(int i=0;i<a[x].size();i++)
{
if(v[a[x][i]]||a[x][i]==y)continue;
dfs(a[x][i],x,z+t[x][i]);
}
return;
}
void gaia(long long x)
{
memset(f,0,sizeof(f));
f[0]=1;
for(int i=0;i<a[x].size();i++)
{
if(v[a[x][i]])continue;
memset(f1,0,sizeof(f));
dfs(a[x][i],0,t[x][i]);
for(int j=0;j<3;j++)s+=f[j]*f1[(3-j)%3];
for(int j=0;j<3;j++)f[j]+=f1[j];
}
return;
}
void dijah(long long x)
{
v[x]=true,gaia(x);
for(int i=0;i<a[x].size();i++)
{
if(v[a[x][i]])continue;
maxx=1e9+5,all=size[a[x][i]];
search(a[x][i],0),dijah(root);
}
return;
}
int main()
{
long long x,y,z;
scanf("%lld",&n);
for(int i=1;i<n;i++)
{
scanf("%lld%lld%lld",&x,&y,&z),z%=3;
a[x].push_back(y),a[y].push_back(x);
t[x].push_back(z),t[y].push_back(z);
}
maxx=1e9+5,all=n;
search(1,0),dijah(root);
s=s*2+n,z=__gcd(s,n*n);
printf("%lld/%lld",s/z,n*n/z);
return 0;
}
【Luogu P4886】 快递员
题目描述
给出一个
解题思路
首先,我们可以任意选择一个点作为
容易发现,若最大贡献点对连成的路径经过了当前
同时,将
否则,我们可以将
但不能一条边一条边的移动,这样会达到
我们可以用点分治的思想,每次跳到重心上,这样的时间复杂度就为
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long x,y,z;
}b[100005];
long long n,m,deep[100005],fa[100005],s,all,size[100005],root,qwe,s1=1e9+5;
vector<long long> a[100005],t[100005];
bool v[100005];
bool cmp1(datay q,datay w)
{
return q.z>w.z;
}
void dfs1(long long x,long long y)
{
fa[x]=fa[y];
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
deep[a[x][i]]=deep[x]+t[x][i];
dfs1(a[x][i],x);
}
return;
}
void dfs2(long long x,long long y)
{
long long maxx=0;
size[x]=1;
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y||v[a[x][i]])continue;
dfs2(a[x][i],x);
size[x]+=size[a[x][i]],maxx=max(maxx,size[a[x][i]]);
}
maxx=max(maxx,all-size[x]);
if(maxx<qwe&&(!v[x]))qwe=maxx,root=x;
return;
}
void dijah(long long x)
{
memset(deep,0,sizeof(deep));
for(int i=0;i<a[x].size();i++)fa[x]=a[x][i],deep[a[x][i]]=t[x][i],dfs1(a[x][i],x);
for(int i=1;i<=m;i++)b[i].z=deep[b[i].x]+deep[b[i].y];
sort(b+1,b+m+1,cmp1);
s1=min(s1,b[1].z);
for(int i=1;i<=m;i++)
{
if(b[i].x==x)fa[x]=fa[b[i].y];
if(b[i].y==x)fa[x]=fa[b[i].x];
if(fa[b[i].x]!=fa[b[i].y])
{
s=b[i].z;
return;
}
if(b[i].z!=b[i+1].z)break;
if(fa[b[i].x]!=fa[b[i+1].x])
{
s=b[i].z;
return;
}
}
if(v[fa[b[1].x]])
{
s=b[1].z;
return;
}
v[x]=true,all=size[fa[b[1].x]],qwe=1e9+5;
dfs2(fa[b[1].x],x),dijah(root);
return;
}
int main()
{
long long x,y,z;
scanf("%lld%lld",&n,&m);
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);
}
for(int i=1;i<=m;i++)scanf("%lld%lld",&b[i].x,&b[i].y);
all=n,qwe=1e9+5;
dfs2(1,0),dijah(root);
printf("%lld",s1);
return 0;
}
图论
【Luogu P5590】 赛车游戏
题目描述
给你一个
解题思路
我们可以这样考虑:求出一条
设
这就是一个差分约束问题,用
注意可以删去不在
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long x,y;
}ll[10005];
long long n,m,f[1005];
bool v[1005],v1[1005],can;
vector<long long> b[1005],a[1005],t[1005];
queue<long long> l;
bool dfs1(long long x,long long y)
{
v1[x]=true;
for(int i=0;i<b[x].size();i++)
{
if(b[x][i]==y)continue;
if(!v1[b[x][i]])dfs1(b[x][i],x);
v[x]|=v[b[x][i]];
}
return v[x];
}
void dfs2(long long x,long long y)
{
v1[x]=true;
for(int i=0;i<b[x].size();i++)
{
if(b[x][i]==y||v[b[x][i]])continue;
if(v1[b[x][i]])can=true;
dfs2(b[x][i],x);
}
v1[x]=false,v[x]=true;
return;
}
void edge(long long x,long long y,long long z)
{
a[x].push_back(y),t[x].push_back(z);
return;
}
int main()
{
long long x,y;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=m;i++)scanf("%lld%lld",&ll[i].x,&ll[i].y),b[ll[i].x].push_back(ll[i].y);
memset(v,false,sizeof(v));
v[n]=true,dfs1(1,0);
if(can||(!v[1]))
{
printf("-1\n");
return 0;
}
for(int i=1;i<=n;i++)
{
if(v[i])
{
for(int j=0;j<b[i].size();j++)
{
if(v[b[i][j]])edge(i,b[i][j],9),edge(b[i][j],i,-1);
}
}
}
for(int i=1;i<=n;i++)v1[i]=v[i];
memset(v,false,sizeof(v)),memset(f,1,sizeof(f));
f[1]=0,v[1]=true,l.push(1);
for(int i=1;l.size();i++)
{
if(i>=2000000)
{
printf("-1\n");
return 0;
}
x=l.front(),l.pop(),v[x]=false;
for(int i=0;i<a[x].size();i++)
{
if(f[a[x][i]]>f[x]+t[x][i])
{
f[a[x][i]]=f[x]+t[x][i];
if(!v[a[x][i]])v[a[x][i]]=true,l.push(a[x][i]);
}
}
}
printf("%lld %lld\n",n,m);
for(int i=1;i<=m;i++)
{
if(v1[ll[i].x]&&v1[ll[i].y])printf("%lld %lld %lld\n",ll[i].x,ll[i].y,f[ll[i].y]-f[ll[i].x]);
else printf("%lld %lld 1\n",ll[i].x,ll[i].y);
}
return 0;
}
【Luogu P3039】 Delivery Route S
题目描述
给出
解题思路
很明显,从
直接暴力复杂度能到
可以发现,一个点到另一个点走的一定是直线,若路程中有点挡着,就应该从他旁边经过绕开。
所以,我们可以给每个点旁边的
直接跑
注意直线路径长度指的是曼哈顿距离。
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long x,y,p;
}b[105],a[505];
long long n,num,dx[5]={0,1,-1,0,0},dy[5]={0,0,0,1,-1},f[505][505],d[505][505];
bool check1(long long x,long long q,long long w)
{
for(int i=1;i<=n;i++)
{
if(b[i].x==x&&b[i].y>=q&&b[i].y<=w)return true;
}
return false;
}
bool check2(long long y,long long q,long long w)
{
for(int i=1;i<=n;i++)
{
if(b[i].y==y&&b[i].x>=q&&b[i].x<=w)return true;
}
return false;
}
long long edge(long long qx,long long qy,long long wx,long long wy)
{
if(qx==wx)
{
if(qy>wy)swap(qy,wy);
return check1(qx,qy+1,wy-1)?(1e13+5):wy-qy;
}
if(qy==wy)
{
if(qx>wx)swap(qx,wx);
return check2(qy,qx+1,wx-1)?(1e13+5):wx-qx;
}
if(qx>wx)swap(qx,wx),swap(qy,wy);
if(qy>wy)return ((check1(qx,wy,qy-1)||check2(wy,qx,wx-1))&&(check2(qy,qx+1,wx)||check1(wx,wy+1,qy)))?(1e13+5):wx-qx+qy-wy;
return ((check2(qy,qx+1,wx)||check1(wx,qy,wy-1))&&(check1(qx,qy+1,wy)||check2(wy,qx,wx-1)))?(1e13+5):wx-qx+wy-qy;
}
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld%lld",&b[i].x,&b[i].y);
for(int i=1;i<=n;i++)
{
for(int u=1;u<=4;u++)
{
bool qwe=false;
for(int j=1;j<=n;j++)
{
if(b[i].x+dx[u]==b[j].x&&b[i].y+dy[u]==b[j].y)qwe=true;
}
if(!qwe)a[++num].x=b[i].x+dx[u],a[num].y=b[i].y+dy[u],a[num].p=i;
}
}
for(int i=1;i<=num;i++)
{
for(int j=1;j<=num;j++)
{
if(i==j)continue;
f[i][j]=edge(a[i].x,a[i].y,a[j].x,a[j].y);
}
}
for(int i=1;i<=num;i++)
{
for(int j=1;j<=num;j++)
{
for(int u=1;u<=num;u++)f[j][u]=min(f[j][u],f[j][i]+f[i][u]);
}
}
memset(d,1,sizeof(d));
long long s=0;
for(int i=1;i<=num;i++)
{
for(int j=1;j<=num;j++)d[a[i].p][a[j].p]=min(d[a[i].p][a[j].p],f[i][j]+2);
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)d[i][j]=min(d[i][j],edge(b[i].x,b[i].y,b[j].x,b[j].y));
}
for(int i=1;i<n;i++)s+=d[i][i+1];
printf("%lld",s+d[n][1]>1e13?-1:s+d[n][1]);
return 0;
}
【Luogu P3965】 循环格
题目描述
给出一个
解题思路
图论中出现这种数据范围小的题,不是状压就是网络流,这题显然是网络流。
分析一下题目,发现若将连边,将会有
若有一点的入度
所以修改后的每个点的入度与出度都为
转化为一个二分图完美匹配问题,直接跑费用流即可。
Code
#include<bits/stdc++.h>
using namespace std;
struct edge
{
long long to,val,maxx,nex;
}a[100005];
long long num=-1,head[100005],start,endd,f[1005],n,d[1005],t[1005],val=0,now[1005],m;
queue<long long> l;
bool v[1005];
void add(long long x,long long to,long long maxx,long long val)
{
a[++num].to=to,a[num].val=val,a[num].maxx=maxx,a[num].nex=head[x];
head[x]=num;
return;
}
void edge(long long x,long long to,long long val,long long maxx)
{
add(x,to,maxx,val),add(to,x,0,-val);
return;
}
bool spfa()
{
memset(f,1,sizeof(f)),memset(v,false,sizeof(v)),memset(now,-1,sizeof(now));
while(l.size()!=0)l.pop();
l.push(start);
f[start]=0,v[start]=true,now[start]=head[start];
long long x,u,vv,maxx;
while(l.size())
{
x=l.front(),l.pop(),v[x]=false;
for(int i=head[x];i!=-1;i=a[i].nex)
{
vv=a[i].val,u=a[i].to,maxx=a[i].maxx;
if(maxx>0&&f[u]>f[x]+vv)
{
f[u]=f[x]+vv,now[u]=head[u];
if(!v[u])v[u]=true,l.push(u);
}
}
}
if(f[endd]>=1e9+5)return false;
return true;
}
long long dfs(long long x,long long y)
{
if(x==endd)
{
val+=y*f[endd];
return y;
}
v[x]=true;
long long u,vv,maxx,fl,now_y=y;
for(;now[x]!=-1;now[x]=a[now[x]].nex)
{
if(now_y==0)break;
u=a[now[x]].to,vv=a[now[x]].val,maxx=a[now[x]].maxx;
if(!v[u]&&maxx&&(f[u]==f[x]+vv))
{
fl=dfs(u,min(now_y,maxx));
now_y-=fl,a[now[x]].maxx-=fl,a[now[x]^1].maxx+=fl;
}
}
v[x]=false;
return y-now_y;
}
long long dinic()
{
long long qwe=0;
while(spfa())
{
memset(v,false,sizeof(v));
qwe+=f[endd]*dfs(start,1e9+5);
}
return qwe;
}
long long point(long long x,long long y)
{
if(x==0)x=n;
if(x==n+1)x=1;
if(y==0)y=m;
if(y==m+1)y=1;
return (x-1)*m+y;
}
int main()
{
string p;
memset(head,-1,sizeof(head));
scanf("%lld%lld",&n,&m);
start=2*n*m+1,endd=2*n*m+2;
for(int i=1;i<=n;i++)
{
cin>>p;
p=' '+p;
for(int j=1;j<p.size();j++)
{
if(p[j]=='U')edge(point(i,j),point(i-1,j)+n*m,0,1),edge(point(i,j),point(i+1,j)+n*m,1,1),edge(point(i,j),point(i,j+1)+n*m,1,1),edge(point(i,j),point(i,j-1)+n*m,1,1);
else if(p[j]=='D')edge(point(i,j),point(i-1,j)+n*m,1,1),edge(point(i,j),point(i+1,j)+n*m,0,1),edge(point(i,j),point(i,j+1)+n*m,1,1),edge(point(i,j),point(i,j-1)+n*m,1,1);
else if(p[j]=='R')edge(point(i,j),point(i-1,j)+n*m,1,1),edge(point(i,j),point(i+1,j)+n*m,1,1),edge(point(i,j),point(i,j+1)+n*m,0,1),edge(point(i,j),point(i,j-1)+n*m,1,1);
else if(p[j]=='L')edge(point(i,j),point(i-1,j)+n*m,1,1),edge(point(i,j),point(i+1,j)+n*m,1,1),edge(point(i,j),point(i,j+1)+n*m,1,1),edge(point(i,j),point(i,j-1)+n*m,0,1);
}
}
for(int i=1;i<=n*m;i++)edge(start,i,0,1),edge(i+n*m,endd,0,1);
cout<<dinic();
return 0;
}
【Luogu P8328】Usmjeravanje
题目描述
一个
解题思路
模拟一下题目,发现有贡献的情况最终只有
所以我们可以据此确定边的方向,最后直接求强联通分量个数。
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long x,y,p,v;
}b[200005];
long long n,m,k,dfn[400005],num,low[400005],s,v[400005];
vector<long long> a[400005];
stack<long long> l;
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.p<w.p;
}
void dijah(long long x)
{
v[x]=true,l.push(x),dfn[x]=low[x]=++num;
for(int i=0;i<a[x].size();i++)
{
if(!dfn[a[x][i]])dijah(a[x][i]),low[x]=min(low[x],low[a[x][i]]);
else if(v[a[x][i]])low[x]=min(low[x],dfn[a[x][i]]);
}
if(dfn[x]==low[x])
{
s++;
while(l.top()!=x)v[l.top()]=false,l.pop();
v[x]=false,l.pop();
}
return;
}
int main()
{
long long x=0;
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=1;i<=k;i++)scanf("%lld%lld",&b[i].x,&b[i].y),b[i].p=i,b[i].y+=n;
sort(b+1,b+k+1,cmp1);
for(int i=1;i<=k;i++)
{
if(x<b[i].y)b[i].v=1,x=b[i].y,a[b[i].y].push_back(b[i].x);
else a[b[i].x].push_back(b[i].y);
}
for(int i=1;i<n;i++)a[i].push_back(i+1);
for(int i=1;i<m;i++)a[i+n].push_back(i+n+1);
for(int i=1;i<=n+m;i++)
{
if(!dfn[i])dijah(i);
}
sort(b+1,b+k+1,cmp2);
printf("%lld\n",s);
for(int i=1;i<=k;i++)printf("%lld ",b[i].v);
return 0;
}
数学/容斥/二项式反演
【Luogu P6576】Plus Minus
题目描述
给出一个
解题思路
若确定第一行与第一列的情况,那么我们可以确定出整个矩阵。
模拟一下,我们可以发现一个性质:第一行第一列中至少有一个是
那么我们可以考虑容斥,答案为第一行
第三种情况好做,以第一种情况为例讲一下第一二种情况如何求解。
若某一行没有限定,那么他有
注意特判
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
struct datay
{
long long x,y;
bool v;
}a[100005];
long long k,n,m,s1=1,s2=1,p,l1,l2;
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;
}
bool cmp1(datay q,datay w)
{
return q.x<w.x;
}
bool cmp2(datay q,datay w)
{
return q.y<w.y;
}
int main()
{
char c;
scanf("%lld%lld%lld",&n,&m,&k);
l1=n,l2=m;
for(int i=1;i<=k;i++)
{
cin>>c,scanf("%lld%lld",&a[i].x,&a[i].y);
if(c=='+')a[i].v=1;
else a[i].v=0;
}
sort(a+1,a+k+1,cmp1),p=1;
for(int i=1;i<=k;i++)
{
if(a[i].x!=a[i+1].x)
{
l1--;
for(int j=p;j<=i;j++)if(a[j].v==0)a[j].y++;
for(int j=p+1;j<=i;j++)if((a[j-1].y%2)!=(a[j].y%2))s1=0;
for(int j=p;j<=i;j++)if(a[j].v==0)a[j].y--;
p=i+1;
}
}
s1*=dijah(2,l1);
sort(a+1,a+k+1,cmp2),p=1;
for(int i=1;i<=k;i++)
{
if(a[i].y!=a[i+1].y)
{
l2--;
for(int j=p;j<=i;j++)if(a[j].v==0)a[j].x++;
for(int j=p+1;j<=i;j++)if((a[j-1].x%2)!=(a[j].x%2))s2=0;
p=i+1;
}
}
s2*=dijah(2,l2);
long long qwe=1;
for(int i=1;i<k;i++)if(((a[i].x+a[i].y)%2)!=((a[i+1].x+a[i+1].y)%2))qwe=0;
if(k==0)s1--;
printf("%lld",(s1+s2-qwe)%mod);
return 0;
}
【Luogu P4859】 已经没有什么好害怕的了
题目大意
给出长度为
解题思路
看到恰好,我们可以考虑用二项式反演
将
最后套二项式反演公式,用
Code
#include<bits/stdc++.h>
#define max(a,b) (a>b?a:b)
using namespace std;
const long long mod=1e9+9;
long long n,m,a[2005],b[2005],d[2005],f[2005][2005],C[2005][2005],s,p[2005];
int main()
{
long long r=1;
scanf("%lld%lld",&n,&m);
if((n+m)&1)
{
printf("0");
return 0;
}
m=(n+m)/2;
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
for(int i=1;i<=n;i++)scanf("%lld",&b[i]);
sort(a+1,a+n+1),sort(b+1,b+n+1);
for(int i=1;i<=n;i++)
{
while(b[r]<a[i]&&r<=n)r++;
d[i]=r-1;
}
f[0][0]=1;
for(int i=1;i<=n;i++)
{
f[i][0]=1;
for(int j=1;j<=i;j++)f[i][j]=(f[i-1][j]+max(d[i]-j+1,0)*f[i-1][j-1])%mod;
}
for(int i=0;i<=n;i++)C[i][0]=C[i][i]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
p[0]=1;
for(int i=1;i<=n;i++)p[i]=(p[i-1]*i)%mod;
for(int i=1;i<=n;i++)f[n][i]=(f[n][i]*p[n-i])%mod;
for(int i=m;i<=n;i++)s=(s+(((i-m)&1)?-1:1)*C[i][m]*f[n][i])%mod;
printf("%lld",(s+mod)%mod);
return 0;
}
【CF1228E】Another Filling the Grid
题目描述
给定一个
解题思路
套二项式反演,考虑如何求最多
设
最多满足
最后套公式即可。
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
long long n,m,d[255],C[255][255],s;
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;
}
int main()
{
scanf("%lld%lld",&n,&m);
for(int i=0;i<=n;i++)C[i][0]=C[i][i]=1;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
d[0]=1;
for(int i=1;i<=n;i++)d[i]=dijah(m,i)-dijah(m-1,i);
for(int i=1;i<=n;i++)s=(s+(((n-i)&1)?-1:1)*(dijah(d[i],n)*dijah(m-1,(n-i)*n)%mod)*C[n][i]%mod)%mod;
printf("%lld",(s+mod)%mod);
return 0;
}
【Luogu P6076】染色问题
题目描述
给出一个
解题思路
设
设
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
long long n,m,k,C[405][405];
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 poi(long long l)
{
long long h=0;
for(int i=0;i<=n;i++)h=(h+(((n-i)&1)?-1:1)*C[n][i]%mod*dijah(dijah(l+1,i)-1,m)%mod)%mod;
return h;
}
int main()
{
long long s=0;
scanf("%lld%lld%lld",&n,&m,&k);
for(int i=0;i<=max(n,max(m,k));i++)C[i][0]=C[i][i]=1;
for(int i=1;i<=max(max(n,m),k);i++)
{
for(int j=1;j<=i;j++)C[i][j]=(C[i-1][j]+C[i-1][j-1])%mod;
}
for(int i=0;i<=k;i++)s=(s+(((k-i)&1)?-1:1)*C[k][i]*poi(i)%mod)%mod;
cout<<(s+mod)%mod;
return 0;
}
【CF997C】 Sky Full of Stars
题目描述
有一个
解题思路
设
答案即为
根据二项式反演公式,
我们要求的是
这就是 的情况了,所以答案转化为 。 或
很好讨论,设 ,即为 。
的情况同理 都不为
所有的贡献即为 。
到了 ,需要退一下式子。
考虑消掉一个 ,原式 。
后面的东西可以用二项式定理做 。
那么就可以
把他们都加起来就是答案。
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=998244353;
long long n,p[1000005];
long long dijah(long long x,long long y)
{
y=(y%(mod-1)+mod-1)%(mod-1);
long long h=1;
while(y)
{
if(y&1)h=(h*x)%mod;
x=(x*x)%mod,y>>=1;
}
return h;
}
long long C(long long x,long long y)
{
return p[x]*dijah(p[x-y],mod-2)%mod*dijah(p[y],mod-2)%mod;
}
long long poi(long long x)
{
return dijah(1-dijah(3,x-n),n)-1;
}
int main()
{
scanf("%lld",&n);
p[0]=1;
for(int i=1;i<=n;i++)p[i]=(p[i-1]*i)%mod;
long long s=0,h=0;
for(int i=1;i<=n;i++)s=(s+((i&1)?-1:1)*C(n,i)*dijah(3,-i*n)%mod*poi(i))%mod;
s=((s*(dijah(3,n*n+1)%mod))%mod+mod)%mod;
for(int i=1;i<=n;i++)h=(h+((i&1)?-1:1)*C(n,i)*dijah(3,i)%mod*dijah(3,n*(n-i)))%mod;
s=((-s-2*h)%mod+mod)%mod;
printf("%lld",s);
return 0;
}
【Luogu P6478】 游戏
题目大意
给出一棵
解题思路
看到恰好就想到二项式反演。
设
树上背包求出答案,然后套二项式反演公式即可。
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=998244353;
long long n,v[5005],f[5005][5005],size1[5005][2],pp[5005],rp[5005],d[5005];
vector<long long> a[5005];
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 dijah(long long x,long long y)
{
long long p=0;
f[x][0]=1,size1[x][v[x]]++;
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
dijah(a[x][i],x);
memset(d,0,sizeof(d));
for(int j=size1[x][0]+size1[x][1];j>=0;j--)
{
for(int u=size1[a[x][i]][0]+size1[a[x][i]][1];u>=0;u--)d[j+u]=(d[j+u]+f[x][j]*f[a[x][i]][u]%mod)%mod;
}
size1[x][0]+=size1[a[x][i]][0],size1[x][1]+=size1[a[x][i]][1];
for(int j=size1[x][0]+size1[x][1];j>=0;j--)f[x][j]=d[j];
}
for(int i=size1[x][v[x]^1]+1;i>=1;i--)f[x][i]=(f[x][i]+f[x][i-1]*(size1[x][v[x]^1]-i+1)%mod)%mod;
return;
}
long long C(long long x,long long y)
{
return (pp[x]*rp[x-y]%mod)*rp[y]%mod;
}
int main()
{
long long x,y;
string p;
scanf("%lld",&n);
pp[0]=1,rp[0]=1;
for(int i=1;i<=n;i++)pp[i]=(pp[i-1]*i)%mod;
for(int i=1;i<=n;i++)rp[i]=poww(pp[i],mod-2);
cin>>p;
for(int i=0;i<p.size();i++)v[i+1]=(p[i]=='1'?1:0);
for(int i=1;i<n;i++)scanf("%lld%lld",&x,&y),a[x].push_back(y),a[y].push_back(x);
dijah(1,0);
long long s=0;
for(int i=0;i<=n/2;i++)f[1][i]=(f[1][i]*pp[n/2-i])%mod;
for(int i=0;i<=(n/2);i++)
{
s=0;
for(int j=i;j<=n/2;j++)s=(s+(((j-i)&1)?-1:1)*C(j,i)*f[1][j]%mod)%mod;
printf("%lld\n",(s+mod)%mod);
}
return 0;
}
【Luogu P3935】 Calculating
题目描述
求
解题思路
答案为
每个因数
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=998244353;
long long gaia(long long n)
{
long long l=1,r,s=0;
for(;l<=n;l=r+1)r=(n/(n/l)),s=(s+(r-l+1)%mod*(n/l)%mod)%mod;
return s;
}
int main()
{
long long l,r;
scanf("%lld%lld",&l,&r);
printf("%lld\n",(gaia(r)-gaia(l-1)+mod)%mod);
return 0;
}
【CF1366D】 Two Divisors
题目描述
给定
解题思路
构造题。
一开始考虑直接用
在原来基础上更改,设
考虑证明,
所以这样构造即可。
Code
#include<bits/stdc++.h>
using namespace std;
bool b[10000005];
long long n,a[500005],d1[500005],d2[500005],prime[10000005],l,f1[10000005],p;
map<long long,bool> g;
int main()
{
for(int i=2;i<=10000000;i++)
{
if(!b[i])
{
prime[++l]=i;
if(i<=3500)for(long long j=i;j<=10000000&&j>0;j*=i)g[j]=true;
}
for(int j=1;j<=l;j++)
{
if(i*prime[j]>10000000)break;
b[i*prime[j]]=1,f1[i*prime[j]]=prime[j];
if(i%prime[j]==0)break;
}
}
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
if(g.count(a[i]))
{
d1[i]=d2[i]=-1;
continue;
}
if(!f1[a[i]])
{
d1[i]=d2[i]=-1;
continue;
}
d1[i]=p=f1[a[i]];
while(p*f1[a[i]]>0&&a[i]%(p*f1[a[i]])==0)p*=f1[a[i]];
d2[i]=a[i]/p;
}
for(int i=1;i<=n;i++)printf("%d ",d1[i]);
printf("\n");
for(int i=1;i<=n;i++)printf("%d ",d2[i]);
return 0;
}
【Luogu P2303】 Longge 的问题
题目描述
求
解题思路
暴力求
Code
#include<bits/stdc++.h>
using namespace std;
long long n,a[100005],t[100005],l,s,qwe,p;
void dfs(long long x,long long y,long long z)
{
if(x==l+1)
{
s+=(qwe/y)*z;
return;
}
dfs(x+1,y,z),z*=(a[x]-1),y*=a[x];
for(int i=1;i<=t[x];i++)dfs(x+1,y,z),z*=a[x],y*=a[x];
return;
}
int main()
{
scanf("%lld",&n);
qwe=n,p=sqrt(n);
for(int i=2;i<=p;i++)
{
if(n%i==0)
{
a[++l]=i;
while(n%i==0)n/=i,t[l]++;
}
}
if(n!=1)a[++l]=n,t[l]=1;
dfs(1,1,1),cout<<s;
return 0;
}
【CF1139D】 Steps to One
题目描述
给一个数列,每次随机选一个
解题思路
答案
一位一位的考虑,对于第
因为求一段序列的
将概率拆开,
准备套莫反,
提出
用等比数列求和公式,
由于
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=1e9+7;
long long n,s=0,prime[100005],l,mu[100005];
bool v[100005];
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;
}
int main()
{
scanf("%lld",&n);
mu[1]=1;
for(int i=2;i<=n;i++)
{
if(!v[i])prime[++l]=i,mu[i]=-1;
for(int j=1;j<=l;j++)
{
if(prime[j]*i>n)break;
v[i*prime[j]]=true;
if(i%prime[j]==0)
{
mu[i*prime[j]]=0;
break;
}
mu[i*prime[j]]=-mu[i];
}
}
for(int i=2;i<=n;i++)s=(s+mu[i]*(n/i)*dijah(n-n/i,mod-2))%mod;
printf("%lld",((-s+1)%mod+mod)%mod);
return 0;
}
【CF623B】 Array GCD
题目描述
给定长度为
- 对于整个阵列,您可以删除长度为
的间隔,代价是 ,该间隔只能操作一次。 - 对于每个元素,您可以花费
使其成为 或 ,并且每个元素只能操作一次。
通过上述运算,求出使剩余数的最大公约数大于
解题思路
一开始想到
因为不能删整个序列,改又只能改
那我们可以枚举出约数集合,然后考虑最小代价是多少即可。
对于枚举到
我们考虑对于
这样做一次时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
long long n,m1,m2,a[1000005],d[1005],l,s=1e16+5,f[1000005],f1[1000005],f2[1000005];
bool check(long long x)
{
for(int i=1;i<=l;i++)if(d[i]==x)return false;
return true;
}
void add(long long x)
{
long long p=sqrt(x);
for(int i=2;i<=p;i++)
{
if(x%i==0)
{
if(check(i))d[++l]=i;
while(x%i==0)x/=i;
}
}
if(x!=1&&check(x))d[++l]=x;
return;
}
void gaia(long long x)
{
for(int i=1;i<=n;i++)
{
if(a[i]%x==0)f[i]=0;
else if(a[i]%x==1||a[i]%x==(x-1))f[i]=m2;
else f[i]=1e11+5;
}
for(int i=1;i<=n;i++)f1[i]=f1[i-1]+f[i];
for(int i=n;i>=1;i--)f2[i]=f2[i+1]+f[i];
long long p=1e16+5,s1=1e16+5;
for(int i=n;i>=1;i--)p=min(p,f2[i+1]),p+=m1,s1=min(s1,f1[i-1]+p);
s=min(s,min(s1,f1[n]));
return;
}
int main()
{
scanf("%lld%lld%lld",&n,&m1,&m2);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
add(a[1]+1),add(a[1]-1),add(a[1]);
add(a[n]+1),add(a[n]-1),add(a[n]);
sort(d+1,d+l+1);
for(int i=1;i<=l;i++)gaia(d[i]);
cout<<s;
return 0;
}
DP
【Luogu P9272】 千岛之国
题目描述
给出
解题思路
看到
设
以
预处理出
实际上打记忆化搜索会更容易。
Code
#include<bits/stdc++.h>
using namespace std;
int n,a[2505][2505],cnt[2505][2505],lx[250005],ly[250005],a1[2505][2505],b1[2505][2505],a2[2505][2505],b2[2505][2505],f1[2505][2505],f2[2505][2505];
int gaia(int q,int w,int e,int r)
{
if(q>e||w>r)return 0;
return cnt[e][r]+cnt[q-1][w-1]-cnt[e][w-1]-cnt[q-1][r];
}
int gaia1(int x,int y)
{
if(gaia(1,y,x,2500)==0)f1[x][y]=0;
if(f1[x][y]!=-1)return f1[x][y];
f1[x][y]=gaia(1,y,x,2500)+gaia1(a1[x-1][y-1]>10000?x:a1[x-1][y-1],b2[x+1][y+1]==0?y:b2[x+1][y+1]);
return f1[x][y];
}
int gaia2(int x,int y)
{
if(gaia(x,1,2500,y)==0)f2[x][y]=0;
if(f2[x][y]!=-1)return f2[x][y];
f2[x][y]=gaia(x,1,2500,y)+gaia2(a2[x+1][y+1]==0?x:a2[x+1][y+1],b1[x-1][y-1]>10000?y:b1[x-1][y-1]);
return f2[x][y];
}
int main()
{
int x,y;
scanf("%d",&n);
if(n==1)
{
printf("0\n");
return 0;
}
for(int i=1;i<=n;i++)scanf("%d%d",&lx[i],&ly[i]),a[lx[i]][ly[i]]=1;
memset(a1,1,sizeof(a1)),memset(b1,1,sizeof(b1));
for(int i=1;i<=2500;i++)
{
for(int j=1;j<=2500;j++)
{
cnt[i][j]=a[i][j]+cnt[i-1][j]-cnt[i-1][j-1]+cnt[i][j-1];
if(a[i][j])a1[i][j]=min(i,a1[i-1][j]),b1[i][j]=min(j,b1[i][j-1]);
else a1[i][j]=min(a1[i-1][j],a1[i][j-1]),b1[i][j]=min(b1[i-1][j],b1[i][j-1]);
}
}
for(int i=2500;i>=1;i--)
{
for(int j=2500;j>=1;j--)
{
if(a[i][j])a2[i][j]=max(i,a2[i+1][j]),b2[i][j]=max(j,b2[i][j+1]);
else a2[i][j]=max(a2[i+1][j],a2[i][j+1]),b2[i][j]=max(b2[i+1][j],b2[i][j+1]);
}
}
memset(f1,-1,sizeof(f1)),memset(f2,-1,sizeof(f2));
for(int i=1;i<=n;i++)printf("%d\n",cnt[2500][2500]+gaia1(lx[i],ly[i])+gaia2(lx[i],ly[i])-3);
return 0;
}
【CF1242C】Sum Balance
题目描述
有
解题思路
我们可以求出最后每个盒子里面的数的和是多少,这样的话,我们就可以知道若取出一个数,应该放一个什么数进来。
由于每个数都不相同,所以每个点指向的节点唯一。
变成了一个内向基环树找环的问题,每次找出环后状压所在的盒子编号,然后跑状压
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long v,p,to;
}b[100005],t[25];
vector<long long> a[100005];
long long n,num,d[25],f[100005],l[100005],r,can=0,qw=0,ll[100005];
bool v1[100005],v[100005];
bool cmp(datay q,datay w)
{
return q.v<w.v;
}
bool cmp1(datay q,datay w)
{
return q.p<w.p;
}
void dijah(long long x)
{
if(v1[x])return;
if(v[x])
{
memset(d,0,sizeof(d));
for(int i=r;i>=1;i--)
{
if(l[i]==x)break;
if(d[b[l[i]].p])return;
d[b[l[i]].p]=1;
}
can+=(1<<(b[x].p-1));
for(int i=r;i>=1;i--)
{
if(l[i]==x)return;
can+=(1<<(b[l[i]].p-1));
}
return;
}
v[x]=true;
for(int i=0;i<a[x].size();i++)
{
l[++r]=x,dijah(a[x][i]),r--;
if(can)break;
}
v[x]=false,v1[x]=true;
return;
}
void check(long long x)
{
qw=0;
while(!v[x])
{
ll[++qw]=x,v[x]=true,x=a[x][0];
}
ll[++qw]=x;
for(int i=qw;i>=1;i--)
{
if(i!=qw&&ll[i]==x)return;
t[++r]=b[ll[i]],t[r].to=b[ll[i-1]].p;
}
return;
}
void dfs(long long x)
{
if(x==0)return;
check(f[x^l[x]]),dfs(l[x]);
return;
}
int main()
{
long long x,y,p=0,ll,rr,mid;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&x);
for(int j=1;j<=x;j++)b[++num].p=i,scanf("%lld",&b[num].v),p+=b[num].v,d[i]+=b[num].v;
}
sort(b+1,b+num+1,cmp),p/=n;
for(int i=1;i<=num;i++)
{
x=p-d[b[i].p]+b[i].v,y=0,ll=1,rr=num;
while(ll<=rr)
{
mid=(ll+rr)>>1;
if(b[mid].v<=x)y=max(y,mid),ll=mid+1;
else rr=mid-1;
}
if(b[y].v==x)a[i].push_back(y);
}
for(int i=1;i<=num;i++)can=0,r=0,dijah(i),f[can]=i;
memset(v,false,sizeof(v)),memset(l,-1,sizeof(l));
p=(1<<n),v[0]=true;
for(int i=1;i<p;i++)
{
for(int j=i;j>0;j=(j-1)&i)
{
if(f[j]&&v[i^j])
{
v[i]=true,l[i]=i^j;
break;
}
}
}
if(l[p-1]==-1)
{
printf("No");
return 0;
}
memset(v,false,sizeof(v)),memset(v1,false,sizeof(v1));
r=0,dfs(p-1);
sort(t+1,t+r+1,cmp1);
printf("Yes\n");
for(int i=1;i<=r;i++)printf("%lld %lld\n",t[i].v,t[i].to);
return 0;
}
【CF1295F】 Good Contest
题目描述
解题思路
个人认为将区间反过来,转化成单调不减的概率会好处理一些。
求概率就是可行方案数除以总方案数,问题变成求有多少可行方案。
我们就可以以区间为单位进行
由于以区间内每个数结尾的答案不一样,是一个高次函数的形式,所以我们要一整个区间的考虑。
设
转移考虑在
枚举开始转移的第
考虑
如果
其实这个东西还有一个更简单的思考方法:构造一个从
同时,原
其中
时间复杂度
注意组合数要在
Code
#include<bits/stdc++.h>
using namespace std;
const long long mod=998244353;
struct datay
{
long long x,y;
}a[505];
long long n,dd[1005],t[100005],f[505][1005],d[505][1005],qwe=1;
set<long long> l1;
map<long long,long long> p;
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;
}
int main()
{
long long x,y,z;
scanf("%lld",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld%lld",&a[i].x,&a[i].y);
qwe=qwe*(a[i].y-a[i].x+1)%mod,a[i].x=998244352-a[i].x,a[i].y=998244352-a[i].y,swap(a[i].x,a[i].y),a[i].y++;
l1.insert(a[i].x),l1.insert(a[i].y);
}
set<long long>::iterator q=l1.begin();
long long g=0;
for(;q!=l1.end();q++)p[*q]=++g,dd[g]=*q;
for(int i=1;i<=n;i++)a[i].x=p[a[i].x],a[i].y=p[a[i].y];
for(int i=1;i<=n;i++)t[i]=dijah(i,mod-2);
for(int i=0;i<=2*n;i++)d[0][i]=1;
for(int i=1;i<=n;i++)
{
for(int j=a[i].x;j<a[i].y;j++)
{
x=dd[j+1]-dd[j],y=1,z=0;
for(int u=i-1;u>=0;u--)
{
if(a[u+1].x<=j&&a[u+1].y>j)z++,y=(y*t[z]%mod)*(x+z-1)%mod;
else break;
f[i][j]=(f[i][j]+y*d[u][j-1])%mod;
}
}
for(int j=1;j<=2*n;j++)d[i][j]=(d[i][j-1]+f[i][j])%mod;
}
printf("%lld",d[n][2*n]*dijah(qwe,mod-2)%mod);
return 0;
}
【Luogu P4158】 粉刷匠
题目描述
有
解题思路
木板之间互不影响,我们可以对每条木板都做一次
同时,再做一次
卡一下就能过了。
Code
#include<bits/stdc++.h>
#define max(a,b) (a>b?a:b)
using namespace std;
int n,m,k,a[55][2505],f[55][55][2505],d[55][2505];
int main()
{
int q,w;
string p;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;i++)
{
cin>>p;
for(int j=0;j<p.size();j++)a[i][j+1]=a[i][j]+p[j]-'0';
}
if(k>=n*m)
{
printf("%d\n",n*m);
return 0;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=m;j++)
{
for(int u=0;u<j;u++)
{
for(int q=1;q<=m;q++)f[i][j][q]=max(f[i][j][q],f[i][u][q-1]+max(a[i][j]-a[i][u],j-u-a[i][j]+a[i][u]));
}
}
}
for(int i=1;i<=n;i++)
{
q=min(i*m,k);
for(int j=1;j<=q;j++)
{
for(int u=0;u<j;u++)d[i][j]=max(d[i][j],d[i-1][u]+f[i][m][j-u]);
}
}
printf("%d",d[n][k]);
return 0;
}
【CF868F】 Yet Another Minimization Problem
题目描述
给出一个长度为
解题思路
能很快列出一个
考虑优化,对于
解决决策单调性有二分栈/队列、分治等的方法, 第一个方法需要快速求出
将
分治,设做到的需要求决策点的区间是
如何快速计算
做
Code
#include<bits/stdc++.h>
using namespace std;
long long n,m,a[100005],k,f[25][100005],l=1,r=0,s=0,d[100005];
void add(long long x)
{
s+=d[x],d[x]++;
return;
}
void del(long long x)
{
d[x]--,s-=d[x];
return;
}
long long dijah(long long x,long long y)
{
while(l<x)del(a[l]),l++;
while(l>x)l--,add(a[l]);
while(r<y)r++,add(a[r]);
while(r>y)del(a[r]),r--;
return s;
}
void gaia(long long l,long long r,long long L,long long R)
{
long long mid=(l+r)>>1,p;
for(int i=L;i<=R;i++)
{
if(f[k][mid]>f[k-1][i-1]+dijah(i,mid))p=i,f[k][mid]=f[k-1][i-1]+dijah(i,mid);
}
if(l==r)return;
gaia(l,mid,L,p),gaia(mid+1,r,p,R);
return;
}
int main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
memset(f,1,sizeof(f));
f[0][0]=0;
for(int i=1;i<=m;i++)k=i,gaia(1,n,1,n);
cout<<f[m][n];
return 0;
}
【CF494C】 Helping People
题目描述
有一个长度为
解题思路
保证了区间不会相交,我们可以把这个问题转成一树上问题。
每个区间的父亲为包含它的最小那个区间,再建一个
考虑树形
只有加后的值高过原区间
最后
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long x,y,z;
double p;
}b[5005],d[100005];
long long n,m,v1[100005],fa[5005],r,maxx,f1[100005][21],l[100005],p1[21];
double f[5005][5005],s;
vector<long long> a[5005];
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.x>w.x;
}
void dijah(long long x,long long y)
{
f[x][0]=(1-b[x].p);
for(int i=0;i<a[x].size();i++)
{
if(a[x][i]==y)continue;
dijah(a[x][i],x);
f[x][0]*=f[a[x][i]][b[x].z-b[a[x][i]].z];
}
double q=1,w=1;
for(int i=1;i<=m;i++)
{
q=w=1;
for(int j=0;j<a[x].size();j++)
{
if(a[x][j]==y)continue;
q*=f[a[x][j]][min(i+b[x].z-b[a[x][j]].z-1,m)],w*=f[a[x][j]][min(i+b[x].z-b[a[x][j]].z,m)];
}
f[x][i]+=b[x].p*q+(1-b[x].p)*w;
}
return;
}
long long gaia(long long x,long long y)
{
long long z=l[y-x+1];
return max(f1[x][z],f1[y-p1[z]+1][z]);
}
int main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld",&v1[i]),f1[i][0]=v1[i];
l[0]=-1,p1[0]=1;
for(int i=1;i<=20;i++)p1[i]=p1[i-1]*2;
for(int i=1;i<=n;i++)l[i]=l[i>>1]+1;
for(int i=1;p1[i]<=n;i++)
{
for(int j=1;j+p1[i]-1<=n;j++)f1[j][i]=max(f1[j][i-1],f1[j+p1[i-1]][i-1]);
}
maxx=gaia(1,n);
for(int i=1;i<=m;i++)
{
scanf("%lld%lld",&b[i].x,&b[i].y);
cin>>b[i].p;
}
long long x;
sort(b+1,b+m+1,cmp1);
b[0].x=1,b[0].y=n,b[0].z=maxx;
for(int i=1;i<=m;i++)
{
b[i].z=gaia(b[i].x,b[i].y),x=i-1;
while(x!=0&&b[x].y<b[i].y)x=fa[x];
fa[i]=x,a[i].push_back(x),a[x].push_back(i);
}
f[0][0]=1,dijah(0,0);
double s=0;
for(int i=m-1;i>=0;i--)s+=(f[0][i+1]-f[0][i])*(i+maxx+1);
s+=f[0][0]*(maxx),printf("%.9lf",s);
return 0;
}
【Luogu P1973】 NOI 嘉年华
题目描述
给出
解题思路
考虑
我们可以先设
让最小值最大不好用
转移就很简单了,
倒着再来一遍上面的
上面的解法错的原因是因为钦定的区间可以和第二堆中的其他区间相交,所以我们还要枚举第一堆的结束位置与再次开始的位置,这样一次的时间复杂度为
写出式子:
设
但处理这东西的时间复杂度是
因为
所以我们枚举
总时间复杂度就为
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long x,y;
}a[205];
long long n,m,b[405][405],f1[405][405],f2[405][405],d[405][405],f[405][405];
set<long long> l;
map<long long,long long> p;
int main()
{
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld%lld",&a[i].x,&a[i].y),l.insert(a[i].x),l.insert(a[i].y+=a[i].x-1);
set<long long>::iterator q=l.begin();
for(;q!=l.end();q++)p[*q]=++m;
for(int i=1;i<=n;i++)a[i].x=p[a[i].x],a[i].y=p[a[i].y];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=a[i].x;j++)
{
for(int u=a[i].y;u<=m;u++)b[j][u]++;
}
}
memset(f1,-2,sizeof(f1)),memset(f2,-2,sizeof(f2));
f1[0][0]=0,f2[m+1][0]=0;
for(int i=1;i<=m;i++)
{
for(int j=1;j<=i;j++)
{
for(int u=0;u<b[j][i];u++)f1[i][u]=max(f1[i][u],f1[j-1][u]+b[j][i]);
for(int u=b[j][i];u<=n;u++)f1[i][u]=max(f1[i][u],max(f1[j-1][u]+b[j][i],f1[j-1][u-b[j][i]]));
}
}
for(int i=m;i>=1;i--)
{
for(int j=m;j>=i;j--)
{
for(int u=0;u<b[i][j];u++)f2[i][u]=max(f2[i][u],f2[j+1][u]+b[i][j]);
for(int u=b[i][j];u<=n;u++)f2[i][u]=max(f2[i][u],max(f2[j+1][u]+b[i][j],f2[j+1][u-b[i][j]]));
}
}
long long s=0,x;
for(int i=0;i<=n;i++)s=max(s,min(f1[m][i],(long long)i));
printf("%lld\n",s);
for(int i=1;i<=m;i++)
{
for(int j=i;j<=m;j++)
{
x=n;
for(int u=0;u<=n;u++)
{
while(x!=0&&f1[i-1][u]+f2[j+1][x]<x+u+b[i][j])x--;
f[i][j]=max(f[i][j],min(f1[i-1][u]+f2[j+1][x],x+u+b[i][j]));
}
}
}
for(int i=1;i<=n;i++)
{
s=0;
for(int j=1;j<=a[i].x;j++)
{
for(int u=a[i].y;u<=m;u++)s=max(s,f[j][u]);
}
printf("%lld\n",s);
}
return 0;
}
【CF1267G】 Game Relics
题目描述
有
- 花费
购买第 个物品。 - 花
抽从 到 中随机抽一个物品,抽到重复的返还 。
求拥有所有物品所需的最小代价,
解题思路
能得到性质:必定先抽后买,抽出相同数量元素的概率相同。
很容易想到一个假的做法:直接求出剩下
但模拟一下样例就发现,抽的东西不一样,接下来的策略也会不一样。
由于抽出相同数量元素概率相同,所以相同数量,我们只需要考虑抽出东西的价值和来决定接下来是继续抽还是买。
但买和抽的操作不统一,我们可以把买转换成抽:在剩下的物品中抽出一个并付出
这样我们就转化成了一个都是抽的问题。
对于
同时,我们考虑每一种情况下一步应该怎么走,对于
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
int n,k;
double m,a[105],d[105],g,s,f[105][10005];
int main()
{
cin>>n>>m;
for(int i=1;i<=n;i++)cin>>a[i],k+=a[i];
for(int i=1;i<=n;i++)d[i]=(m/2)*(double(n)/double(n-i+1)+1);
g=1.00,s=m,f[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=k;j>=a[i];j--)
{
for(int u=1;u<=n;u++)f[u][j]+=f[u-1][int(j-a[i])]*double(u)/double(n-u+1);
}
}
for(int i=1;i<n;i++)
{
for(int j=0;j<=k;j++)s+=double(f[i][j])*min(d[i+1],double(k-j)/(n-i));
}
printf("%.17f",s);
return 0;
}
线段树&树状数组&莫队
【CF817F】 MEX Queries
题目描述
给一个
- 将
设为 。 - 将
设为 。 - 将
里面的数 翻转。
解题思路
用线段树维护。
离散化后用线段树维护一个区间
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
struct que
{
long long p,l,r;
}b[100005];
long long n,dd[200005],min1[2000005],min0[2000005],d[2000005],re[2000005];
set<long long> l1;
map<long long,long long> p;
void galaxy1(long long x,long long l,long long r,long long v)
{
re[x]=0,d[x]=v;
if(v)min1[x]=l,min0[x]=1e9+5;
else min1[x]=1e9+5,min0[x]=l;
return;
}
void galaxy2(long long x,long long l,long long r)
{
if(d[x]!=-1)d[x]^=1;
re[x]^=1,swap(min1[x],min0[x]);
return;
}
void pushdown(long long x,long long l,long long r)
{
if(re[x]==0&&d[x]==-1)return;
long long lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
if(d[x]!=-1)
{
galaxy1(lc,l,mid,d[x]),galaxy1(rc,mid+1,r,d[x]),d[x]=-1,re[x]=0;
return;
}
galaxy2(lc,l,mid),galaxy2(rc,mid+1,r),re[x]=0;
return;
}
void build(long long x,long long l,long long r)
{
if(l==r)
{
min0[x]=l;
return;
}
long long lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
build(lc,l,mid),build(rc,mid+1,r);
min0[x]=l;
return;
}
void dijah1(long long x,long long l,long long r,long long ql,long long qr,long long v)
{
if(ql<=l&&r<=qr)
{
galaxy1(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)dijah1(lc,l,mid,ql,qr,v);
if(qr>mid)dijah1(rc,mid+1,r,ql,qr,v);
min1[x]=min(min1[x<<1],min1[(x<<1)|1]),min0[x]=min(min0[x<<1],min0[(x<<1)|1]);
return;
}
void dijah2(long long x,long long l,long long r,long long ql,long long qr)
{
if(ql<=l&&r<=qr)
{
galaxy2(x,l,r);
return;
}
pushdown(x,l,r);
long long lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
if(ql<=mid)dijah2(lc,l,mid,ql,qr);
if(qr>mid)dijah2(rc,mid+1,r,ql,qr);
min1[x]=min(min1[x<<1],min1[(x<<1)|1]),min0[x]=min(min0[x<<1],min0[(x<<1)|1]);
return;
}
int main()
{
scanf("%lld",&n);
l1.insert(1);
for(int i=1;i<=n;i++)scanf("%lld%lld%lld",&b[i].p,&b[i].l,&b[i].r),b[i].r++,l1.insert(b[i].l),l1.insert(b[i].r);
set<long long>::iterator q=l1.begin();
long long g=0;
for(;q!=l1.end();q++)p[*q]=++g,dd[g]=*q;
for(int i=1;i<=n;i++)b[i].l=p[b[i].l],b[i].r=p[b[i].r];
memset(d,0,sizeof(d)),memset(re,0,sizeof(re)),memset(min1,1,sizeof(min1));
build(1,1,2*n+1);
for(int i=1;i<=n;i++)
{
if(b[i].p==1)dijah1(1,1,2*n+1,b[i].l,b[i].r-1,1);
else if(b[i].p==2)dijah1(1,1,2*n+1,b[i].l,b[i].r-1,0);
else dijah2(1,1,2*n+1,b[i].l,b[i].r-1);
printf("%lld\n",(min0[1]>1e9?dd[g]:dd[min0[1]]));
}
return 0;
}
【Luogu P4479】 第k大斜率
题目描述
给出
解题思路
由于不要求精确,考虑二分。
设我们二分到
拆一下式子,保证
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long x,y;
}b[100005];
long long n,a[100005],d[100005],bb[100005],m,f[100005];
bool cmp1(datay q,datay w)
{
if(q.x!=w.x)return q.x<w.x;
return q.y>w.y;
}
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))f[i]+=y;
return;
}
long long gaia(long long x)
{
long long h=0;
while(x)h+=f[x],x-=lowbit(x);
return h;
}
long long check(long long k)
{
memset(bb,0,sizeof(bb));
long long x,l1,r1,mid1,q=0,s=0;
for(int i=1;i<=n;i++)d[i]=a[i]=b[i].y-k*b[i].x;
sort(d+1,d+n+1),d[0]=-1e13-5;
for(int i=1;i<=n;i++)bb[(d[i]!=d[i-1])?(++q):q]=d[i];
for(int i=1;i<=n;i++)
{
l1=1,r1=q,x=a[i],a[i]=0;
while(l1<=r1)
{
mid1=(l1+r1)>>1;
if(bb[mid1]<=x)a[i]=max(a[i],mid1),l1=mid1+1;
else r1=mid1-1;
}
}
for(int i=1;i<=n;i++)s+=gaia(a[i]),dijah(a[i],1);
for(int i=1;i<=n;i++)dijah(a[i],-1);
return s;
}
int main()
{
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;i++)scanf("%lld%lld",&b[i].x,&b[i].y);
sort(b+1,b+n+1,cmp1);
long long l=-2e8,r=2e8,mid,s=-1e9-5;
while(l<=r)
{
mid=(l+r)>>1;
if(check(mid)>=m)s=max(s,mid),l=mid+1;
else r=mid-1;
}
printf("%lld",s);
return 0;
}
【Luogu P3899】 更为厉害
题目描述
给出一棵
解题思路
分两类情况考虑,
而
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long lc,rc,v;
}a[10000005];
long long n,m,dfn[300005],out[300005],num1,size[300005],deep[300005],num,re[300005],root[300005];
vector<long long> t[300005];
long long build(long long l,long long r)
{
if(l==r)
{
a[++num].v=0;
return num;
}
long long h=++num,mid=(l+r)>>1;
a[h].lc=build(l,mid),a[h].rc=build(mid+1,r);
return h;
}
long long dijah(long long x,long long l,long long r,long long k,long long v)
{
if(l==r)
{
a[++num]=a[x],a[num].v+=v;
return num;
}
long long h=++num,mid=(l+r)>>1;
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;
}
long long gaia(long long x,long long l,long long r,long long ql,long long qr)
{
if(ql<=l&&r<=qr)return a[x].v;
long long 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;
}
void dfs(long long x,long long y)
{
deep[x]=deep[y]+1,dfn[x]=++num1,size[x]=1;
for(int i=0;i<t[x].size();i++)
{
if(t[x][i]==y)continue;
dfs(t[x][i],x),size[x]+=size[t[x][i]];
}
out[x]=num1,re[dfn[x]]=x;
return;
}
int main()
{
long long x,y;
scanf("%lld%lld",&n,&m);
for(int i=1;i<n;i++)scanf("%lld%lld",&x,&y),t[x].push_back(y),t[y].push_back(x);
dfs(1,0),root[0]=build(1,n);
for(int i=1;i<=n;i++)root[i]=dijah(root[i-1],1,n,deep[re[i]],size[re[i]]-1);
for(int i=1;i<=m;i++)
{
scanf("%lld%lld",&x,&y);
printf("%lld\n",min(y,deep[x]-1)*(size[x]-1)+gaia(root[out[x]],1,n,deep[x]+1,deep[x]+y)-gaia(root[dfn[x]-1],1,n,deep[x]+1,deep[x]+y));
}
return 0;
}
【CF526F】 Pudding Monsters
题目描述
给出一个
解题思路
因为每行每列恰有一个棋子,转化成一个长度为
变一下式子,变成
设
维护
问题是维护等于
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long minn,v;
}f[1200005];
long long n,a[300005],f1[300005],f2[300005],d[1200005];
stack<long long> l1,l2;
void galaxy(long long x,long long y)
{
f[x].minn+=y,d[x]+=y;
return;
}
void pushdown(long long x)
{
if(d[x]==0)return;
galaxy(x<<1,d[x]),galaxy((x<<1)|1,d[x]),d[x]=0;
return;
}
void build(long long x,long long l,long long r)
{
if(l==r)
{
f[x].minn=l,f[x].v=1;
return;
}
long long lc=(x<<1),rc=(x<<1)|1,mid=(l+r)>>1;
build(lc,l,mid),build(rc,mid+1,r);
f[x].minn=min(f[lc].minn,f[rc].minn),f[x].v=(f[lc].minn==f[x].minn?f[lc].v:0)+(f[rc].minn==f[x].minn?f[rc].v: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,v);
return;
}
pushdown(x);
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);
f[x].minn=min(f[lc].minn,f[rc].minn),f[x].v=(f[lc].minn==f[x].minn?f[lc].v:0)+(f[rc].minn==f[x].minn?f[rc].v:0);
return;
}
int main()
{
long long x,y;
scanf("%lld",&n);
for(int i=1;i<=n;i++)scanf("%lld%lld",&x,&y),a[x]=y;
x=y=0,build(1,1,n);
for(int i=1;i<=n;i++)
{
while(l1.size()>0&&a[l1.top()]<a[i])l1.pop();
while(l2.size()>0&&a[l2.top()]>a[i])l2.pop();
if(l1.size()!=0)f1[i]=l1.top();l1.push(i);
if(l2.size()!=0)f2[i]=l2.top();l2.push(i);
}
long long s=0;
for(int i=1;i<=n;i++)
{
while(x!=0&&a[x]<a[i])dijah(1,1,n,f1[x]+1,x,-a[x]),x=f1[x];
while(y!=0&&a[y]>a[i])dijah(1,1,n,f2[y]+1,y,a[y]),y=f2[y];
dijah(1,1,n,x+1,i,a[i]),dijah(1,1,n,y+1,i,-a[i]),dijah(1,1,n,1,n,-1);
s+=f[1].v,x=i,y=i;
}
printf("%lld",s);
return 0;
}
【Luogu P3245】 大数
题目描述
给出一个
解题思路
设
若
若
时间复杂度
Code
#include<bits/stdc++.h>
using namespace std;
struct datay
{
long long x,y,p,z;
}a[200005];
long long k,n,m,p,f[200005],d[200005],s;
set<long long> ll;
map<long long,long long> qwe;
string b;
bool cmp1(datay q,datay w)
{
if(q.x/p!=w.x/p)return q.x<w.x;
return q.y<w.y;
}
bool cmp2(datay q,datay w)
{
return q.p<w.p;
}
void add(long long x)
{
s+=d[x],d[x]++;
return;
}
void del(long long x)
{
d[x]--,s-=d[x];
return;
}
void gaia1()
{
for(int i=1;i<=n;i++)f[i]=f[i-1]+((b[i]-'0')%2==0),d[i]=d[i-1]+((b[i]-'0')%2==0)*i;
for(int i=1;i<=m;i++)printf("%lld\n",d[a[i].y]-d[a[i].x-1]-(f[a[i].y]-f[a[i].x-1])*(a[i].x-1));
return;
}
void gaia2()
{
for(int i=1;i<=n;i++)f[i]=f[i-1]+((b[i]-'0')%5==0),d[i]=d[i-1]+((b[i]-'0')%5==0)*i;
for(int i=1;i<=m;i++)printf("%lld\n",d[a[i].y]-d[a[i].x-1]-(f[a[i].y]-f[a[i].x-1])*(a[i].x-1));
return;
}
void gaia3()
{
p=sqrt(n);
sort(a+1,a+m+1,cmp1);
long long l=1,r=0,qw=1;
for(int i=n;i>=1;i--)f[i]=(f[i+1]+(b[i]-'0')*qw)%k,qw=(qw*10)%k;
for(int i=1;i<=n+1;i++)ll.insert(f[i]);
long long g=0;
set<long long>::iterator q=ll.begin();
for(;q!=ll.end();q++)qwe[*q]=++g;
for(int i=1;i<=n+1;i++)f[i]=qwe[f[i]];
for(int i=1;i<=m;i++)
{
while(r<a[i].y+1)r++,add(f[r]);
while(r>a[i].y+1)del(f[r]),r--;
while(l<a[i].x)del(f[l]),l++;
while(l>a[i].x)l--,add(f[l]);
a[i].z=s;
}
sort(a+1,a+m+1,cmp2);
for(int i=1;i<=m;i++)printf("%lld\n",a[i].z);
return;
}
int main()
{
scanf("%lld",&k),cin>>b,scanf("%lld",&m);
n=b.size(),b=' '+b;
for(int i=1;i<=m;i++)scanf("%lld%lld",&a[i].x,&a[i].y),a[i].p=i;
if(k==2)gaia1();
else if(k==5)gaia2();
else gaia3();
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
· 为什么 退出登录 或 修改密码 无法使 token 失效