NOIP2024加赛5

NOIP2024加赛5

题目来源: 2023NOIP A层联测31

\(T1\) HZTG5777. 暴力操作(opt) \(40pts\)

  • 先将 \(\{ a \}\) 升序排序。

  • 因为 \(\left\lfloor \dfrac{\left\lfloor \frac{a}{b} \right\rfloor}{c} \right\rfloor=\left\lfloor \dfrac{a}{bc} \right\rfloor\) ,先钦定 \(\forall i,j \in \mathbb{N}^{*},c_{i,j} \le c_{i}+c_{j}\) ,并让每个数仅被操作一遍。

  • 考虑二分答案,并只对 \([1,\left\lceil \frac{n}{2} \right\rceil]\) 进行操作。

  • 此时需要解形如 \(\left\lfloor \frac{a_{i}}{x} \right\rfloor \le mid\) 的不等式,可以使用二分求解,也可以观察到 \(\frac{a_{i}}{x} < mid+1\) 从而得到 \(x \ge \left\lfloor \frac{a_{i}}{mid+1} \right\rfloor+1\) 。然后取 \(\min\limits_{k \in [\left\lfloor \frac{a_{i}}{mid+1} \right\rfloor+ \infty)}\{ c_{x} \}\) 加入代价。

  • 对于 \(\min\limits_{k \in [\left\lfloor \frac{a_{i}}{mid+1} \right\rfloor+ \infty)}\{ c_{x} \}\) 初始时仅通过调和级数处理到 \(m\) 是不够的。

  • \(\{ suf \}\) 表示 \(\{ c \}\) 的后缀 \(\min\) 。不妨先处理出 \([1,m]\) 的后缀 \(\min\) ,此时有 \(suf_{m+1}=\min\limits_{i=1}^{m}\{ c_{i}+suf_{\left\lceil \frac{m+1}{i} \right\rceil} \}\) ,再次倒着更新 \([1,m+1]\) 的后缀 \(\min\) 即可。

    点击查看代码
    ll a[500010],c[500010],suf[500010];
    bool check(ll n,ll m,ll k,ll mid)
    {
        ll sum=0;
        for(ll i=1;i<=n/2+1;i++)
        {
            if(a[i]>mid)
            {
                sum+=suf[a[i]/(mid+1)+1];
            }
        }
        return sum<=k;
    }	
    int main()
    {
    #define Isaac
    #ifdef Isaac
        freopen("opt.in","r",stdin);
        freopen("opt.out","w",stdout);
    #endif	
        ll n,m,k,l=0,r,mid,ans=-1,i,j;
        scanf("%lld%lld%lld",&n,&m,&k);
        r=m;
        for(i=1;i<=n;i++)
        {
            scanf("%lld",&a[i]);
        }
        for(i=1;i<=m;i++)
        {
            scanf("%lld",&c[i]);
        }
        sort(a+1,a+1+n);
        c[1]=0;
        for(i=1;i<=m;i++)
        {
            for(j=1;i*j<=m;j++)
            {
                c[i*j]=min(c[i*j],c[i]+c[j]);
            }
        }
        suf[m]=c[m];
        for(i=m-1;i>=1;i--)
        {
            suf[i]=min(suf[i+1],c[i]);
        }
        suf[m+1]=0x3f3f3f3f;
        for(i=1;i<=m;i++)
        {
            j=ceil(1.0*(m+1)/i);
            if(j<=m)
            {
                suf[m+1]=min(suf[m+1],c[i]+suf[j]);
            }
        }
        for(i=m;i>=1;i--)
        {
            suf[i]=min(suf[i+1],c[i]);
        }
        while(l<=r)
        {
            mid=(l+r)/2;
            if(check(n,m,k,mid)==true)
            {
                ans=mid;
                r=mid-1;
            }
            else
            {
                l=mid+1;
            }
        }
        printf("%lld\n",ans);
        return 0;
    } 
    

\(T2\) HZTG5778. 异或连通(xor) \(50pts\)

  • 部分分

    • 子任务 \(1\)\(DFS\) 找连通块。
    • 子任务 \(2\) :加个记忆化即可。
    • 子任务 \(3\) :使用并查集找连通块。
    点击查看代码
    ll u[200010],v[200010],w[200010];
    unordered_map<ll,ll>f;
    struct DSU
    {
        ll fa[200010],siz[200010];
        void init(ll n)
        {
            for(ll i=1;i<=n;i++)
            {
                fa[i]=i;
                siz[i]=1;
            }
        }
        ll find(ll x)
        {
            return fa[x]==x?x:fa[x]=find(fa[x]);
        }
        void merge(ll x,ll y)
        {
            x=find(x);
            y=find(y);
            if(x!=y)
            {
                fa[y]=x;
                siz[x]+=siz[y];
            }
        }
    }D;
    int main()
    {
    #define Isaac
    #ifdef Isaac
        freopen("xor.in","r",stdin);
        freopen("xor.out","w",stdout);
    #endif	
        ll n,m,q,k,x,ans=0,i,j;
        scanf("%lld%lld%lld%lld",&n,&m,&q,&k);
        for(i=1;i<=m;i++)
        {
            scanf("%lld%lld%lld",&u[i],&v[i],&w[i]);
        }
        for(j=1;j<=q;j++)
        {
            scanf("%lld",&x);
            if(f.find(x)==f.end())
            {
                ans=0;
                D.init(n);
                for(i=1;i<=m;i++)
                {
                    if((w[i]^x)<k)
                    {
                        D.merge(u[i],v[i]);
                    }
                }
                for(i=1;i<=n;i++)
                {
                    if(D.fa[i]==i)
                    {
                        ans+=D.siz[i]*(D.siz[i]-1)/2;
                    }
                }
                f[x]=ans;
            }
            printf("%lld\n",f[x]);
        }
        return 0;
    }
    
  • 正解

    • 观察到每条边存在时对应的 \(k\) 至多形成 \((\log V)\) 段极长连续区间,使用 \(01Trie\) 求出区间并离散化后跑线段树分治即可。
    点击查看代码
    ll u[200010],v[200010],w[200010],a[200010],b[200010],ans[200010];
    struct quality
    {
        ll id,fa,siz;	
    };
    struct DSU
    {
        ll fa[200010],siz[200010];
        void init(ll n)
        {
            for(ll i=1;i<=n;i++)
            {
                fa[i]=i;
                siz[i]=1;
            }
        }
        ll find(ll x)
        {
            return fa[x]==x?x:find(fa[x]);
        }
        void merge(ll x,ll y,stack<quality>&s,ll &sum)
        {
            x=find(x);
            y=find(y);
            if(x!=y)
            {
                s.push((quality){x,fa[x],siz[x]});
                s.push((quality){y,fa[y],siz[y]});
                if(siz[x]<siz[y])
                {
                    swap(x,y);
                }
                fa[y]=x;
                sum-=siz[y]*(siz[y]-1)/2;
                sum-=siz[x]*(siz[x]-1)/2;
                siz[x]+=siz[y];
                sum+=siz[x]*(siz[x]-1)/2;
            }
        }
        void split(stack<quality>&s)
        {
            while(s.empty()==0)
            {
                fa[s.top().id]=s.top().fa;
                siz[s.top().id]=s.top().siz;
                s.pop();
            }
        }
    }D;
    struct SMT
    {
        struct SegmentTree
        {
            vector<ll>info;
        }tree[800010];
        ll lson(ll x)
        {
            return x*2;
        }
        ll rson(ll x)
        {
            return x*2+1;
        }
        void update(ll rt,ll l,ll r,ll x,ll y,ll id)
        {
            if(l>y||r<x)
            {
                return;
            }
            if(x<=l&&r<=y)
            {
                tree[rt].info.push_back(id);
                return;
            }
            ll mid=(l+r)/2;
            update(lson(rt),l,mid,x,y,id);
            update(rson(rt),mid+1,r,x,y,id);
        }
        void solve(ll rt,ll l,ll r,ll sum)
        {	
            stack<quality>s;
            ll mid=(l+r)/2;
            for(ll i=0;i<tree[rt].info.size();i++)
            {
                D.merge(u[tree[rt].info[i]],v[tree[rt].info[i]],s,sum);
            }
            if(l==r)
            {
                ans[l]=sum;
            }
            else
            {
                solve(lson(rt),l,mid,sum);
                solve(rson(rt),mid+1,r,sum);
            }
            D.split(s);
        }
    }S;
    struct Trie
    {
        ll son[6000010][2],st[6000010],ed[6000010],rt_sum=0;
        void insert(ll s,ll id,ll k)
        {
            ll x=0;
            for(ll i=30;i>=0;i--)
            {
                if((k>>i)&1)
                {
                    S.update(1,1,b[0],st[son[x][(s>>i)&1]],ed[son[x][(s>>i)&1]],id);
                    x=son[x][((s>>i)&1)^1];
                }
                else
                {
                    x=son[x][(s>>i)&1];
                }
                if(x==0)
                {
                    break;
                }
            }
        }
        void query(ll s,ll id)
        {
            ll x=0;
            for(ll i=30;i>=0;i--)
            {
                if(son[x][(s>>i)&1]==0)
                {
                    rt_sum++;
                    son[x][(s>>i)&1]=rt_sum;
                    st[son[x][(s>>i)&1]]=id;
                }
                x=son[x][(s>>i)&1];
                ed[x]=id;
            }
        }
    }T;
    int main()
    {
    #define Isaac
    #ifdef Isaac
        freopen("xor.in","r",stdin);
        freopen("xor.out","w",stdout);
    #endif	
        ll n,m,q,k,i;
        scanf("%lld%lld%lld%lld",&n,&m,&q,&k);
        for(i=1;i<=m;i++)
        {
            scanf("%lld%lld%lld",&u[i],&v[i],&w[i]);
        }
        for(i=1;i<=q;i++)
        {
            scanf("%lld",&a[i]);
            b[i]=a[i];
        }
        sort(b+1,b+1+q);
        b[0]=unique(b+1,b+1+q)-(b+1);
        for(i=1;i<=b[0];i++)
        {
            T.query(b[i],i);
        }
        for(i=1;i<=m;i++)
        {
            T.insert(w[i],i,k);
        }
        D.init(n);
        S.solve(1,1,b[0],0);
        for(i=1;i<=q;i++)
        {
            cout<<ans[lower_bound(b+1,b+1+b[0],a[i])-b]<<endl;
        }
        return 0;
    }
    

\(T3\) HZTG5779. 诡异键盘(keyboard) \(20pts\)

  • 部分分

    • 子任务 \(1\) :哈希优化字符串匹配后跑 \(O(n^{3})\)\(DP\)
    点击查看代码
    const ll mod=1000003579,base=13331;
    ll f[5010],hshs[5010],lent[5010],jc[5010];
    vector<ll>hsht[5010];
    char s[1000010];
    void sx_hash(char s[],ll len)
    {
        for(ll i=0;i<=len;i++)
        {
            hshs[i]=(i==0)?0:(hshs[i-1]*base%mod+s[i])%mod;
        }
    }
    void sx_hash_t(char s[],ll len,ll id)
    {
        for(ll i=0;i<=len;i++)
        {
            hsht[id][i]=(i==0)?0:(hsht[id][i-1]*base%mod+s[i])%mod;
        }
    }
    ll ask_hash(ll l,ll r)
    {
        return (hshs[r]-hshs[l-1]*jc[r-l+1]%mod+mod)%mod;
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
        freopen("keyboard.in","r",stdin);
        freopen("keyboard.out","w",stdout);
    #endif	
        int t,n,m,len,i,j,k,h;
        cin>>t;
        for(i=0;i<=5000;i++)
        {
            jc[i]=(i==0)?1:jc[i-1]*base%mod;
        }
        for(h=1;h<=t;h++)
        {
            memset(f,0x3f,sizeof(f));
            cin>>n>>m;
            for(i=1;i<=n;i++)
            {
                cin>>(s+1);
                lent[i]=strlen(s+1);
                hsht[i].clear();
                hsht[i].resize(lent[i]+10);
                sx_hash_t(s,lent[i],i);
            }
            cin>>(s+1);
            len=strlen(s+1);
            sx_hash(s,len);
            f[0]=0;
            for(i=1;i<=len;i++)
            {
                for(j=0;j<=i-1;j++)
                {
                    for(k=1;k<=n;k++)
                    {
                        if(i-j<=lent[k]&&ask_hash(j+1,i)==hsht[k][i-j])
                        {
                            f[i]=min(f[i],f[j]+lent[k]-(i-j)+1);
                        }
                    }
                }
            }
            cout<<((f[len]==0x3f3f3f3f3f3f3f3f)?1:f[len])<<endl;
        }
        return 0;
    }
    
  • 正解

\(T4\) HZTG5780. 民主投票(election) \(5pts\)

  • 部分分

    • 子任务 \(1\) :模拟。
    点击查看代码
    vector<int>e[1000010];
    int siz[1000010],dfn[1000010],out[1000010],pos[1000010],fa[1000010],sum[1000010],tot;
    void add(int u,int v)
    {
        e[u].push_back(v);
    }
    void dfs(int x)
    {
        siz[x]=1;
        tot++;
        dfn[x]=tot;
        pos[tot]=x;
        for(int i=0;i<e[x].size();i++)
        {
            dfs(e[x][i]);
            siz[x]+=siz[e[x][i]];
        }
        out[x]=tot;
    }
    bool work(int x,int maxx)
    {
        if(x==1)
        {
            return true;
        }
        x=fa[x];
        while(x!=0)
        {
            if(sum[x]+1<maxx)
            {
                sum[x]++;
                return true;
            }
            x=fa[x];
        }
        return false;
    }
    int check(int x,int n)
    {
        memset(sum,0,sizeof(sum));
        int maxx=siz[x]-1;
        for(int i=1;i<=n;i++)
        {
            if((i<=dfn[x]||i>out[x])&&work(pos[i],maxx)==false)
            {
                return 0;
            }
        }
        return 1;
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
        freopen("election.in","r",stdin);
        freopen("election.out","w",stdout);
    #endif	
        int t,n,i,j,k;
        scanf("%d",&t);
        for(k=1;k<=t;k++)
        {
            scanf("%d",&n);
            tot=0;
            for(i=1;i<=n;i++)
            {
                e[i].clear();
            }
            for(i=2;i<=n;i++)
            {
                scanf("%d",&fa[i]);
                add(fa[i],i);
            }
            dfs(1);
            for(i=1;i<=n;i++)
            {
                printf("%d",check(i,n));
            }
            printf("\n");
        }
        return 0;
    }
    
  • 正解

    • 考虑先二分出树上最少的最多次数 \(k\) 。具体地,设当前二分出的答案为 \(mid\)\(check\) 时设 \(f_{x,mid}\) 表示以 \(x\) 为根的子树内除去 \(x\) 在满足每个人最多被投 \(mid\) 票时,需要向上投的最少票数,状态转移方程为 \(f_{x,mid}=\max(0,\sum\limits_{y \in Son(x)}(f_{y,mid}+1)-mid)\) 。最后答案合法当且仅当 \(f_{1,mid}=0\)
    • 难点在于最后询问满足 \(siz_{x}-1=k\)\(x\) 时其他节点是否能满足最多 \(siz_{x}-2=k-1\) 张票的限制条件。不难发现 \(f_{x,k-1} \le 1\) ,若 \(x_{x,k-1}=0\) 因为 \(f_{1,mid}>0\)\(x\) 不会获胜,否则若 \(\forall y \in (1 \to x),f_{y,k-1}=1\)\(x\) 获得 \(k\) 张票时有 \(\forall y \in (1 \to fa_{x}),f_{y,k-1}=0\)\(x\) 会获胜。
    • 略带卡常。
    点击查看代码
    vector<int>e[1000010];
    int fa[1000010],siz[1000010],ans[1000010],f[1000010],dfn[1000010],pos[1000010],tot;
    void add(int u,int v)
    {
        e[u].push_back(v);
    }
    void dfs1(int x)
    {
        siz[x]=1;
        tot++;
        dfn[x]=tot;
        pos[tot]=x;
        for(int i=0;i<e[x].size();i++)
        {
            dfs1(e[x][i]);
            siz[x]+=siz[e[x][i]];
        }
    }
    void dfs2(int n,int mid)
    {
        for(int i=1;i<=n;i++)
        {
            f[i]=0;
        }
        for(int i=n;i>=1;i--)
        {
            f[pos[i]]=max(0,f[pos[i]]-mid);
            f[fa[pos[i]]]+=f[pos[i]]+1;
        }
    }
    void dfs3(int x)
    {
        if((f[x]==0)||(x==1&&f[x]!=1))
        {
            return;
        }
        ans[x]=1;
        for(int i=0;i<e[x].size();i++)
        {
            dfs3(e[x][i]);
        }
    }
    int main()
    {
    #define Isaac
    #ifdef Isaac
        freopen("election.in","r",stdin);
        freopen("election.out","w",stdout);
    #endif	
        int t,n,l,r,k,mid,i,j;
        scanf("%d",&t);
        for(j=1;j<=t;j++)
        {
            scanf("%d",&n);
            tot=0;
            for(i=1;i<=n;i++)
            {
                e[i].clear();
                ans[i]=0;
            }
            for(i=2;i<=n;i++)
            {
                scanf("%d",&fa[i]);
                add(fa[i],i);
            }
            dfs1(1);
            l=1;
            r=n;
            k=-1;
            while(l<=r)
            {
                mid=(l+r)/2;
                dfs2(n,mid);
                if(f[1]==0)
                {
                    k=mid;
                    r=mid-1;
                }
                else
                {
                    l=mid+1;
                }
            }
            dfs2(n,k-1);
            dfs3(1);
            for(i=1;i<=n;i++)
            {
                printf("%d",((siz[i]-1==k)?ans[i]:(siz[i]-1>k)));
            }
            printf("\n");
        }
        return 0;
    }
    

总结

  • \(T1\) 调和级数仅处理到了 \(m\) ,挂了 \(60pts\)
  • \(T4\) 链的部分分写假了,挂了 \(20pts\)

后记

  • \(T1\) 题面从 \(tgHZOJ\) 搬过来的时候忘把更改后的题面(将原 \(x \in [1,m]\) 改成了 \(c_{x \in [1,m]}\))搬过来了,赛时 \(feifei\) 用画图更改了题面重新下发了。
  • \(T3\) 未注明 \(\sum|S_{i}|\) 为每个数据点的总数据规模,赛时 \(feifei\) 还称是单组数据的数据规模。
posted @ 2024-11-15 17:57  hzoi_Shadow  阅读(107)  评论(7编辑  收藏  举报
扩大
缩小