2021牛客暑期多校训练营2 L-WeChat Walk(分块)

L-WeChat Walk

在这里插入图片描述

在这里插入图片描述

每个大点记录一下邻接点的最大步数

每次修改的时候,枚举修改点的邻接的大点来更新

修改大点的时候直接判是不是比邻接点都大


代码抄的std好不容易才看懂~

Code1
#include<bits/stdc++.h>
using namespace std;
template <class T=int> T rd()
{
    T res=0;
    char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res;
}
const int N=200010;
vector<int> d[N],g[N]; // d原图 g大点
int n,m,q,Bs;
int id[N],cnt;
int a[N];
int chmp[N],mx[N];
int h[550][20010];
int ne[N<<4],e[N<<4],idx;
int ans[N];
void add(int u)
{
    for(int v:g[u])
    {
        int s=id[v];
        e[idx]=u,ne[idx]=h[s][a[u]],h[s][a[u]]=idx++;;
    }
}
int main()
{
    n=rd(),m=rd(),q=rd();Bs=2*sqrt(m)+1;
    memset(h,-1,sizeof h);
    for(int i=1;i<=m;i++)
    {
        int u=rd(),v=rd();
        d[u].push_back(v);
        d[v].push_back(u);
    }
    // 一个点周围的大点
    for(int i=1;i<=n;i++) for(int v:d[i]) if(d[v].size()>=Bs) g[i].push_back(v);
    
    for(int i=1;i<=n;i++) if(d[i].size()>=Bs) id[i]=++cnt;// 大点编号
    
    for(int i=1;i<=q;i++)
    {
        int u=rd(),w=rd(); a[u]+=w;
        if(chmp[u]) {add(u);for(int v:g[u]) mx[v]=max(mx[v],a[u]);continue;}
        if(!id[u])// 由于a的增加使得周围某些点冠军状态被破坏 
        {
            while(1)
            {
                int k=0,num=0;
                for(int v:d[u])
                {
                    num=max(num,a[v]);
                    if(chmp[v]&&(k==0||a[v]<a[k])) k=v;// 找到步数最小的冠军
                }
                if(!k)// 周围已经没有冠军 
                {
                    if(num<a[u]) chmp[u]=i,add(u); // 看看自己是不是冠军
                    break;
                }
                if(a[k]>a[u]) break;// 如果步数最小的冠军大于a[u]那么已经不会打破周围点的冠军状态
                ans[k]+=i-chmp[k];chmp[k]=0;// 更新周围点的冠军状态 累计答案
            }
        }
        else
        {
            int s=id[u];
            for(int t=a[u]-w+1;t<=a[u];t++) // a[u]+1~a[u]+w
            {
                for(int j=h[s][t];j!=-1;j=ne[j])
                {
                    int v=e[j];
                    if(a[v]==t&&chmp[v]) ans[v]+=i-chmp[v],chmp[v]=0;
                }
            }
            if(mx[u]<a[u]) chmp[u]=i,add(u);
        }
        
        for(int v:g[u]) mx[v]=max(mx[v],a[u]);
    }
    
    for(int i=1;i<=n;i++) if(chmp[i]) ans[i]+=q-chmp[i];
    for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
    return 0;
}
Code2

KeHe大佬题解配合jiangly giegie的代码
考虑按步数 w w w 从大到小枚举

f u f_u fu表示 u u u 最近一次更新步数的时刻, last u \text{last}_u lastu表示 u u u 上一次更新步数的时刻,初值均为最终时刻 q q q

u u u 为小点直接暴力算周围的点。

u u u 为大点,考虑直接维护这个结果(记为 mn v \text{mn}_v mnv)即每个点步数更新时,枚举其周围所有大点 v v v 来更新 mn v \text{mn}_v mnv,由于每个点周围的大点的个数不超过 m Bs \frac{m}{\text{Bs}} Bsm,复杂度可行

#include<bits/stdc++.h>
using namespace std;
template <class T=int> T rd()
{
    T res=0;
    char ch=getchar();
    while(!isdigit(ch)) ch=getchar();
    while( isdigit(ch)) res=(res<<1)+(res<<3)+(ch^48),ch=getchar();
    return res;
}
const int N=200010;
int n,m,q,Bs;
vector<int> e[N],big[N];
int walk[N],mx[N];
int ans[N];
vector<pair<int,int>> event[10005];
int main()
{
    n=rd(),m=rd(),q=rd();Bs=2*sqrt(m)+1;
    
    for(int i=1;i<=m;i++)
    {
        int u=rd(),v=rd();
        e[u].push_back(v);
        e[v].push_back(u);
    }
    for(int i=1;i<=n;i++) for(int v:e[i]) if(e[v].size()>=Bs) big[i].push_back(v);
    
    for(int i=1;i<=q;i++)
    {
        int u=rd(),w=rd();
        walk[u]+=w;
        event[walk[u]].push_back({u,i});
    }
    vector<int> f(n+1,q),last(n+1,q),mn(n,q);
    for(int w=10000;w>=1;w--)
    {
        for(auto [u,t]:event[w]) 
        {
            for(auto v:big[u])
                mn[v]=min(mn[v],t);
            
            last[u]=f[u],f[u]=t;
        }
        for(auto [u,t]:event[w])
        {
            if(e[u].size()<Bs)
            {
                int r=last[u];
                for(auto v:e[u]) r=min(r,f[v]);
                ans[u]+=max(0,r-t);
            }
            else
            {
                ans[u]+=max(0,min(last[u],mn[u])-t);
            }
        }
    }
    for(int i=1;i<=n;i++) printf("%d\n",ans[i]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值