早上打了一场手速赛收获很多
洛谷:最大数
这题做法很多啊:本来是线段树板子题但是发现树状数组和st表预处理也可以
先说一下树状数组的操作
我们可以定义tree[i]为(x-(x-lowbit(x)+1))的最大值这和求区间和的定义是不一样的
但是它再更新答案的时候和求区间的和的那个不一样
因为有可能对于一个数x它的管辖的最左侧的位置是x-lowbit(x)+1也就是是说这个x-lowbit(x)+1可能不在我们说的l-r的区间范围内,所以是不可以的
那怎么求区间最大值呢
我们可以这样想如果说r-lowbit(r)是大于l的那我们是不是可以把最大值更新成max(mx,tree[r])
反之我们只能用r这个节点自己的值来更新mx;每一次更新完y-=lowbit(y)
直到y<x我们就可以退出循环
ll query(ll x,ll y)
{
//求区间最大值
ll mx=-1;
while(y>=x)
{
mx=max(mx,a[y]);
--y;
//如果tree[y-lowbit(y)]小于x那只能拿它自己更新了
while((y-lowbit(y))>=x)
{
mx=max(mx,tree[y]);//那mx可以用tree[y]来更新
y-=lowbit(y);
}
}
return mx;
}
那修改操作呢?
其实和加的操作差不多也是要更改父节点的权值
因为x管辖的区间是x-(x-lowbit(x)+1)我们也只需要更新这个区间就可以了
void updata(ll x,ll v)
{
//把节点x的位置的权值更新成y
a[x]=v;
while(x<=N)
{
//要往上更新
tree[x]=a[x];
//一个子节点的最大值首先是它自己
//更新它的节点区间
//更新x-x-lowbit(x)
for(int i=1;i<lowbit(x);i<<=1)
{
tree[x]=max(tree[x],tree[x-i]);
}
x+=lowbit(x);
}
}
这里是i<<1其实也就是i+=lowbit(i)
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
ll tree[N];//i节点下的最大值
ll a[N];
int lowbit(int x)
{
return x&(-x);
}
ll query(ll x,ll y)
{
//求区间最大值
ll mx=-1;
while(y>=x)
{
mx=max(mx,a[y]);
--y;
//如果tree[y-lowbit(y)]小于x那只能拿它自己更新了
while((y-lowbit(y))>=x)
{
mx=max(mx,tree[y]);//那mx可以用tree[y]来更新
y-=lowbit(y);
}
}
return mx;
}
void updata(ll x,ll v)
{
//把节点x的位置的权值更新成y
a[x]=v;
while(x<=N)
{
//要往上更新
tree[x]=a[x];
//一个子节点的最大值首先是它自己
//更新它的节点区间
//更新x-x-lowbit(x)
for(int i=1;i<lowbit(x);i<<=1)
{
tree[x]=max(tree[x],tree[x-i]);
}
x+=lowbit(x);
}
}
void cf()
{
ll m,d;
cin>>m>>d;
ll cnt=0,t=0;
char op;
ll n;
for(int i=1;i<=m;i++)
{
cin>>op>>n;
if(op=='A')
{
updata(++cnt,(n+t)%d);
}
else
{
t=query(cnt-n+1,cnt);
cout<<t<<endl;
}
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t=1;
while(t--)
{
cf();
}
}
还有一种做法就是st表每一次插入一个新的数之后我们就更新一次st表
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
ll tree[N];//i节点下的最大值
ll a[N];
ll cnt;
ll f[N][30];
ll query(ll l,ll r)
{
ll k=log2(r-l+1);
return max(f[l][k],f[r-(1<<k)+1][k]);
}
void st(ll x)
{
f[++cnt][0]=x;
//插入一个新的数字
for(int j=1;(1<<j)<=cnt;j++)
{
ll q=cnt-(1<<j)+1;
//cnt是总的数组长度
f[q][j]=max(f[q][j-1],f[q+(1<<(j-1))][j-1]);
}
}
void cf()
{
ll m,d;
cin>>m>>d;
ll t=0;
char op;
ll n;
for(int i=1;i<=m;i++)
{
cin>>op>>n;
if(op=='A')
{
st((n+t)%d);
}
else
{
t=query(cnt-n+1,cnt);
cout<<t<<endl;
}
}
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t=1;
while(t--)
{
cf();
}
}
最便宜的构建
这是小白月赛的一道题目:
可以用二分来判断,我们直到如果最后的答案越大的话我们可以选的边就越多,所以这是具有二分特征的
我们每一次确定了一个答案之后我们就可以去跑一下存的点,如果边权小于mid我们就可以把这个边加进来,全部结束之后我们去判断每一个子集是不是联通的这个可以用并查集来判断
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+10;
struct node
{
int u,v,w;
}e[N];
int k,n,m;
vector<int>g[N];
int pre[N];
int find(int x)
{
while(x!=pre[x])
{
x=pre[x];
}
return x;
}
bool check(int mid)
{
for(int i=1;i<=n;i++)pre[i]=i;//初始化
for(int i=1;i<=m;i++)
{
int a=e[i].u,b=e[i].v,c=e[i].w;
if(c>mid)continue;
int f1=find(a),f2=find(b);
if(f1!=f2)
{
pre[f1]=f2;
}
}
for(int i=1;i<=k;i++)
{
for(int j=1;j<g[i].size();j++)
{
int a=find(g[i][j]);
int b=find(g[i][j-1]);
if(a!=b)return false;
}
}
return true;
}
void cf()
{
cin>>n>>m;
for(int i=1;i<=m;i++)
{
int u,v,w;
cin>>u>>v>>w;
e[i]={u,v,w};
}
cin>>k;
for(int i=1;i<=k;i++)
{
int c;
cin>>c;
for(int j=1;j<=c;j++)
{
int x;
cin>>x;
g[i].push_back(x);
}
}
int l=0,r=1e9;
while(l<r)
{
int mid=(l+r)/2;
if(check(mid))
{
r=mid;
}
else
{
l=mid+1;
}
}
cout<<l<<endl;
}
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
int t;
t=1;
while(t--)
{
cf();
}
}