7.19训练总结

考场错误:
今天的题目难度更高,在读了A题时,感觉数据范围很大,没看出来这是签到题,以为B是签到,结果浪费了一些时间,写出A后,又开始犹豫了B和C看起来都挺可做,反复徘徊,最后看了有人过了字符串的H,便去写H,想了个根号分治 O ( n n l o g n ) O(n\sqrt{n}log n) O(nn logn)需要卡快长,毒瘤题还卡自然溢出,遂修改了很久,最后半个小时会了C的贪心策略,去写树剖,因为int x,y; 结果没读入挂了,没查出来,最后也没能通过,因此今天仅通过了两道题,rk7

Gym - 101741C

贪心,这类题目需要多加练习,寻找感觉

考虑我们把给定的m条边按照lca的深度从大到小排序,然后依次检查,如果这段路径上没有点,那么就把lca选中,如果路径上有点,那么不需要操作,实现过程使用树链剖分和树状数组

这样做是对的,因为我们每次尽量把选择的点深度尽量提高,这样就能对后续产生更多的贡献啦

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
vector <int> G[maxn];
int n,sz[maxn],son[maxn],tp[maxn];
int fa[maxn],dfn[maxn],dep[maxn];
void dfs(int u)
{
    sz[u]=1;
    // cerr<<u<<endl;
    for(int to:G[u])
    {
        if(to==fa[u]) continue;
        fa[to]=u; dep[to]=dep[u]+1;
        dfs(to);
        sz[u]+=sz[to];
        if(sz[to]>sz[son[u]]) son[u]=to;
    }
}
void dfss(int u,int ff)
{
    // cerr<<u<<" "<<son[u];
    tp[u]=ff; dfn[u]=++dfn[0];
    if(son[u])
        dfss(son[u],ff);
    for(int to:G[u])
    {
        if(to==fa[u] || to==son[u]) continue;
        dfss(to,to);
    }
}   
int lca(int u,int v)
{
    while(tp[u]!=tp[v])
    {
        if(dep[tp[u]]<dep[tp[v]]) swap(u,v);
        u=fa[tp[u]];
    }
    return dep[u]<dep[v]?u:v;
}
int m;
struct node
{
    int u,v,l;
    bool operator < (const node &oth) const{
        return dep[l]>dep[oth.l];
    }
}q[maxn];
int res[maxn],cnt;
int c[maxn];
int lowbit(int x)
{
    return x&-x;
}
void add(int x,int v)
{
    while(x<=n)
    {
        c[x]+=v;
        x+=lowbit(x);
    }
}
int Query(int x)
{
    // cerr<<x<<endl;
    x=min(x,n);
    int ans=0;
    while(x)
    {
        ans+=c[x];
        x-=lowbit(x);
    }
    return ans;
}
int query(int u,int v)
{
    int ans=0;
    while(tp[u]!=tp[v]) {
        if(dep[tp[u]]<dep[tp[v]]) swap(u,v);
        ans+=Query(dfn[u])-Query(dfn[tp[u]]-1);
        u=fa[tp[u]];
    }
    if(dep[u]<dep[v]) swap(u,v);
    ans+=Query(dfn[u])-Query(dfn[v]-1);
    return ans;
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    scanf("%d",&n);
    for(int i=1;i<n;i++)
    {
        int x,y; scanf("%d%d",&x,&y);
        G[x].push_back(y);
        G[y].push_back(x);
    }
    dfs(1); dfss(1,1);
    // printf("%d\n",dfn[0]);
    // return 0;
    // for(int i=1;i<=n;i++) printf("%d %d\n",tp[i],dep[i]);
    // return 0;
    scanf("%d",&m);
    for(int i=1;i<=m;i++)
    {
        scanf("%d%d",&q[i].u,&q[i].v);
        q[i].l=lca(q[i].u,q[i].v);
    }
    // return 0;
    sort(q+1,q+m+1);
    for(int i=1;i<=m;i++)
    {
        if(query(q[i].u,q[i].v)) continue;
        res[++cnt]=q[i].l;
        add(dfn[q[i].l],1);
        // for(int i=1;i<=n;i++) printf("%d ",c[i]);
        // printf("\n");
    }
    printf("%d\n",cnt);
    for(int i=1;i<=cnt;i++) printf("%d ",res[i]);
    return 0;
}

Gym - 101741D

这道题目第一个观察很重要:如果 t i < t j t_i < t_j ti<tj a i < a j a_i<a_j ai<aj 那么 i i i就不会对答案造成影响,这样我们删除这样的点后,得到的就是t递增,a递减的数列

有了这样的性质,我们可以设 f [ i ] f[i] f[i]表示把前 i i i个人都运上去,并回到初始位置的最短时间

发现转移如下
f [ i ] = m i n ( m a x ( t [ i ] , f [ j ] ) + 2 ∗ a [ j + 1 ] ) f[i]=min(max(t[i],f[j])+2*a[j+1]) f[i]=min(max(t[i],f[j])+2a[j+1])

然后我们可以讨论ti和fj的分界点,显然这个分界点是单调的,可以用一个指针完成
后面的min也可以用单调队列实现

时间复杂度 O ( n ) O(n) O(n)

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn=2e5+5;
const ll inf=1e18;
int n,t[maxn],a[maxn];
ll f[maxn];
/*
struct seg
{
    int tr[maxn<<2],tag[maxn<<2];
    void build(int now,int l,int r)
    {
        tr[now]=inf;
        if(l==r) return;
        int mid=l+r>>1;
        build(now<<1,l,mid);
        build(now<<1|1,mid+1,r);
    }
    void pushdown(int now,int l,int r)
    {
        if(!tag[now]) return;
        int mid=l+r>>1;
        int lson=now<<1,rson=now<<1|1;
        tr[lson]=max(tr[lson],tag[now]);
        tr[rson]=max(tr[rson],tag[now]);
        tag[lson]=max(tag[lson],tag[now]);
        tag[rson]=max(tag[rson],tag[now]);
        tag[now]=0;
    }
    void change(int now,int l,int r,int L,int R,int v)
    {
        if(l>=L && r<=R)
        {
            tag[now]=max(tag[now],v);
            tr[now]=max(tr[now],v);
            return;
        }
        int mid=l+r>>1;
        if(L<=mid) change(now<<1,l,mid,L,R,v);
        if(mid<R) change(now<<1|1,mid+1,r,L,R,v);
    }
    int query(int now,int l,int r,int L,int R)
    {
        if(l>=L && r<=R) return tr[now];
        int mid=l+r>>1;
        pushdown(now,l,r);
        if(R<=mid) return query(now<<1,l,mid,L,R);
        if(mid<L) return query(now<<1|1,mid+1,r,L,R);
        return max(query(now<<1,l,mid,L,R),query(now<<1|1,mid+1,r,L,R));
    }
    int ask(int now,int l,int r,int L,int R)
    {
        if(l>=L && r<=R) return tr[now];
        int mid=l+r>>1;
        if(R<=mid) return ask(now<<1,l,mid,L,R);
        if(mid<L) return ask(now<<1|1,mid+1,r,L,R);
        return min(ask(now<<1,l,mid,L,R),ask(now<<1|1,mid+1,r,L,R));
    }
}A,B;
int get_pos(int L,int R,int v)
{
    int ans=0;
    while(L<=R)
    {
        int mid=L+R>>1;
        if(f[mid]<=v) ans=mid,L=mid+1;
        else R=mid-1;
    }
    return ans;
}*/
pair <ll,int> q[maxn];
int l,r,cnt;
int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    while(scanf("%d",&n)!=EOF)
    {
        l=1,r=0;
        for(int i=1;i<=n;i++)
        {
            int x,y; scanf("%d%d",&x,&y);
            while(l<=r && q[r].second<y)
                r--;
            q[++r]=make_pair(x,y);
            // scanf("%d%d",&t[i],&a[i]);
        }
        for(int i=1;i<=n;i++) f[i]=inf;
        cnt=0;
        for(int i=1;i<=r;i++)
        {
            t[++cnt]=q[i].first;
            a[cnt]=q[i].second;
        }
        l=1,r=0;
        int pos=0;
        for(int i=1;i<=cnt;i++)
        {
            // cerr<<f[i];
            while(pos<=cnt && f[pos]<=t[i]) ++pos;
            while(l<=r && q[l].second<pos) l++;
            if(pos<=cnt && pos<=i)
                f[i]=min(f[i],t[i]+2ll*a[pos]);
                // cerr<<"["<<f[i]<<"]";
            if(l<=r) f[i]=min(f[i],q[l].first);
            while(l<=r && q[r].first>=f[i]+2*a[i+1] && i!=cnt)   
                r--;
            q[++r]=make_pair(f[i]+2ll*a[i+1],i);
            // cerr<<" "<<f[i]<<endl;
        }
        printf("%lld\n",f[cnt]);
        /*B.build(1,0,n);
        for(int i=1;i<=n;i++)
        {
            int pos=get_pos(1,i-1,t[i]);
            A.change(1,0,n,0,i,a[i]);
            f[i]=min(f[i],t[i]+2*A.query(1,0,n,0,pos));
            
            f[i]=min(f[i],B.ask(1,0,n,pos+1,i-1));
            B.upd(1,0,n,)
        }*/
    }
    return 0;
}

Gym - 101741J

我们考虑使用类似线段树的方式去维护,每个点维护从mid开始向左和向右的一段的连续的背包的值,然后两段之间可以dp合并,查询的时候看中点是哪个,然后合并左右两段,否则递归到左右处理就好

时空复杂度 O ( n m l o g n ) O(nmlogn) O(nmlogn)

#include<bits/stdc++.h>
using namespace std;
const int maxn=4e5+5;
const int mod=1e9+7;
int n,m,a[maxn],q;
struct seg
{
    int v[20];
    void clear()
    {
        v[0]=1;
        for(int i=1;i<20;i++)
            v[i]=0;
    }
}tr[maxn<<5],tmp;
int cnt,st[maxn<<2];
int add(int x,int y)
{
    if(x+y>=mod) return x+y-mod;
    return x+y;
}
int Add(int x,int y)
{
    if(x+y>=m) return x+y-m;
    return x+y;
}
seg add(seg x,int val)
{
    seg res=x;
    // printf("[%d]\n",x.v[0]);
    for(int i=0;i<m;i++)
        if(x.v[i]) res.v[Add(i,val)]=add(res.v[Add(i,val)],x.v[i]);
    return res;
}
void build(int now,int l,int r)
{
    // cerr<<now<<" "<<l<<" "<<r<<endl;
    cnt++; st[now]=cnt;
    tmp.clear();
    int mid=l+r>>1;
    tmp.v[a[mid]]++;
    tr[cnt]=tmp; 
    if(l==r) return;
    for(int i=mid-1;i>=l;i--)
    {
        cnt++;
        tmp=add(tmp,a[i]);
        tr[cnt]=tmp;
    }
    tmp.clear();
    for(int i=mid+1;i<=r;i++)
    {
        cnt++;
        tmp=add(tmp,a[i]);
        // if(cnt==6) printf("[%d %d ]\n",tmp.v[1],a[i]);
        tr[cnt]=tmp;
    }
    build(now<<1,l,mid);
    build(now<<1|1,mid+1,r); 
}
int merge(seg x,seg y)
{
    seg res;
    res.clear(); res.v[0]--;
    // cerr<<x.v[5]<<" "<<y.v[1]<<endl;
    for(int i=0;i<m;i++)
        if(x.v[i])
        for(int j=0;j<m;j++)
            if(y.v[j]) res.v[Add(i,j)]=add(res.v[Add(i,j)],1ll*x.v[i]*y.v[j]%mod);
    return res.v[0];
}
int query(int now,int l,int r,int L,int R)
{
    int mid=l+r>>1;
    if(L<=mid && mid<R)
    {
        // cerr<<now<<" "<<l<<" "<<r<<endl;
        int lenl=mid-L+1,lenr=R-mid;
        int idl=st[now]+lenl-1,idr=st[now]+mid-l+lenr;
        // cerr<<st[now]<<" "<<idr<<" "<<lenr<<endl;
        // cerr<<tr[idr].v[0]<<" "<<tr[idr].v[1]<<endl;
        return merge(tr[idl],tr[idr]);
    }
    if(R<=mid) return query(now<<1,l,mid,L,R);
    return query(now<<1|1,mid+1,r,L,R);
}
int main()
{
    freopen("a.in","r",stdin);
    freopen("a.out","w",stdout);
    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d",&a[i]),a[i]%=m;
    build(1,1,n);
    // return 0;
    scanf("%d",&q);
    while(q--)
    {
        int l,r; scanf("%d%d",&l,&r);
        if(l==r)
        {
            if(a[l]) printf("1\n");
            else printf("2\n");
        }
        else printf("%d\n",query(1,1,n,l,r));
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值