zoukankan      html  css  js  c++  java
  • HGOI 20191108 题解

    Problem A 新婚快乐

    一条路,被$n$个红绿灯划分成$n+1$段,从前到后一次给出每一段的长度$l_i$,每走$1$的长度需要$1$分钟。

    一开始所有红绿灯都是绿色的,$g$分钟后所有红绿灯变成红色,再过$r$分钟,所有红绿灯又重新变为绿色。

    以$r+g$分钟为一个周期,如此反复。

    有$Q$组询问,如果第$t_i$分钟从第一条线段的首端出发走到最后一条线段末端需要的时刻。

    对于$100\%$的数据满足$1 leq nleq 2 imes 10^5$ , 其他所有数字都在$10^9$以下。

    Solution : 

      设$f[i]$表示第$i$个红绿灯恰好绿灯开始,走到最后的时间长度。

      转移的话就是从最近的一个$j$(即编号最小),且满足$|s_j - s_i| \% (r+g) geq g$。

      这个时候,我们需要解这样一个带有取模运算的不等式,$(a+b)\% c geq d$

      我们打一个表发现,若$a=0$,答案显然是$[d,c-1]$,如果对于任意$1 leq a < c$只要把答案区间往左平移$a$即可。

      这个时候,可能出现段首一部分被覆盖,段尾一部分被覆盖。

      直接用权值线段树维护位置信息即可。

      最后计算答案的时候,也需要解这样一个不等式,类似的,计算出当前出发第一个遇到红灯的位置即可。

      使用动态开点的权值线段树,可以实现时间复杂度$O(n log_2 S)$,其中$S$是值域。

    # include <bits/stdc++.h>
    # define int long long 
    # define root rt
    # define inf (1e18)
    using namespace std;
    const int N=2e5+10;
    int n,g,r;
    int s[N],f[N];
    struct Seg {
        int ls,rs,val;
        Seg() { val = inf; ls=rs=0; }
    };
    struct QwQ {
        int root,tot;
        QwQ () { root=0;tot=0;}
        Seg tr[N*32];
        void insert(int &x,int l,int r,int pos,int val) {
            if (!x) x=++tot;
            if (l==r) { tr[x].val=val; return;}
            int  mid=(l+r)>>1;
            if (pos<=mid) insert(tr[x].ls,l,mid,pos,val);
            else insert(tr[x].rs,mid+1,r,pos,val);
            tr[x].val=min(tr[tr[x].ls].val,tr[tr[x].rs].val);
        }
        int query(int x,int l,int r,int opl,int opr) {
            if (!x) return inf;
            if (opl<=l && r<=opr) return tr[x].val;
            int ret=inf,mid=(l+r)>>1;
            if (opl<=mid) ret=min(ret,query(tr[x].ls,l,mid,opl,opr));
            if (opr>mid) ret=min(ret,query(tr[x].rs,mid+1,r,opl,opr));
            return ret;
        }
        int query(int x) {
            x=(x%(g+r)+(g+r))%(g+r);
            if (g-x>=0) return query(root,0,g+r-1,g-x,g+r-1-x);
            else return min(query(root,0,g+r-1,0,g+r-1-x),query(root,0,g+r-1,(g-x+g+r)%(g+r),g+r-1));
        }
    }tr1,tr2;
    signed main()
    {
        scanf("%lld%lld%lld",&n,&g,&r);
        for (int i=1;i<=n+1;i++) {
            int t; scanf("%lld",&t); s[i]=s[i-1]+t;
        }
        f[n]=s[n+1]-s[n]; tr1.insert(tr1.root,0,g+r-1,s[n]%(g+r),n);
        for (int i=n-1;i>=1;i--) {
            int j = tr1.query(g+r-s[i]%(g+r));
            if (j == inf) {
                f[i] = s[n+1]-s[i];
            } else {
                f[i] = f[j] + (s[j]-s[i]) + g+r-(s[j]-s[i])%(g+r);
            }
            tr1.insert(tr1.root,0,g+r-1,s[i]%(g+r),i);
        }
        for (int i=1;i<=n;i++) tr2.insert(tr2.root,0,g+r-1,s[i]%(g+r),i);
        int q; scanf("%lld",&q);
        while (q--) {
            int t; scanf("%lld",&t);
            int p = tr2.query(t%(g+r));
            if (p == inf) { printf("%lld
    ",t+s[n+1]); continue;}
            else {
                printf("%lld
    ",s[p]+t+g+r-(s[p]+t)%(g+r)+f[p]);
            }
        }
        return 0;
    }
    wedding.cpp

    Problem B 能量传输  

      有$n$个人排成一圈,每恰好相隔$k$个人可以花费$1$的代价传输$1$的能量。

      给出每个人的初始能量,为了使得每个人最终的能量都相等,输出最小代价。

      对于$100\%$的数据满足$1 leq kleq n leq 5 imes 10^5 $

    Solution : 

       首先,本题每相邻$k$个人可进行交换,于是我们就可以做若干次环形均分纸牌即可。

       环形均分纸牌问题,需要使用两个模型,第一个是线性的均分纸牌,另外一个是中位数。

       设$s[i]$为数组的前缀和,这些数最终都变成$r = frac{sumlimits_{i=1}^{n}}{n}$

          线性状态下 , 第$1$个人显然需要和第$2$进行交换,使得自己纸牌数成为$r$.

       依次类推,在最优状态下,到第$i$个人和第$i+1$个人交换的时候,$|i imes r - s[i]|$是需要交换的总排数。

         所有总交换次数就是$sumlimits_{i=1}^{n} |i imes r - s[i]|$

       为了计算方便,一开始,我们让所有数减去一个平均数$r$,并最终让所有人手中有0张牌,答案显然不变,就是$sum |s_i|$了。

        为了让线性变成环形,我们考虑一个较为朴素的做法,将$n$种断环方法,然后做一次均分纸牌即可。

        继续推导,当前断环所成的东西是这样的。  

    // 设S[i]数组为减去avg的前缀和数组,显然有S[M]=0
    A[k+1] S[k+1]-S[k]
    A[k+2] S[k+2]-S[k+1]
    ....
    A[M] S[M] - S[k]
    A[1] S[1]+S[M]-S[k]
    ...
    A[k] S[k]+S[M]-S[k]

      这个时候所需要的步数就是$sumlimits_{i=1}^{n}|s[i] - s[k]|$,这个时候取$s[k]$为中位数的时候最小。

      时间复杂度为$O(n log_2 n)$

    # include <bits/stdc++.h>
    # define int long long
    using namespace std;
    const int N=2e6+10;
    int a[N],b[N],g[N];
    bool vis[N];
    int cnt,n,k,sum;
    int solve(int u) {
        int cnt=0;
        for (int i=u;;i+=k) {
            if (i>n) i-=n;
            if (vis[i]) break;
            vis[i]=1; b[++cnt]=a[i]-sum;
        }
        for (int i=1;i<=cnt;i++)
            g[i]=g[i-1]+b[i];
        sort(g+1,g+1+cnt);
        int mid = g[(cnt+1)/2]; int ans=0;
        for (int i=1;i<=cnt;i++) ans+=abs(g[i]-mid);
        return ans; 
    }
    signed main()
    {
        scanf("%lld%lld",&n,&k); k++;
        for (int i=1;i<=n;i++) {
            scanf("%lld",&a[i]); sum+=a[i];
        }
        sum/=n; int ans=0;
        for (int i=1;i<=n;i++) if (!vis[i]) {
            ans+=solve(i);
        }
        printf("%lld
    ",ans);
        return 0;
     }
    energy.cpp

    Problem C 矿物运输 

      给出一棵有$n$个点的树,每一个点上面有一个权值$val_i$

      先手和后手轮流操作,使得一个节点上$1$的权值移到它的父亲节点上、

      无法移动的人判负,先手胜利输出"win"否则输出"lose"

      对于$100\%$的数据满足$1 leq n leq 2 imes 10^5$

     Solution : 

     这是是多个有向图游戏的和,而多个有向图(G)游戏的和SG函数值等于它包含的各个子游戏SG函数值的异或和,即SG(G) = SG(G1) xor SG(G2) xor SG(Gm)。

     所以我们不妨将深度为奇数的所有点矿石数求异或,结果大于零(根据NIM游戏规则)则输出win(先手必胜),否则输出lose

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

    #include<bits/stdc++.h>
    using namespace std;
    
    const int maxn=200005;
    
    struct Edge{
        int next,to;
    }edge[maxn];
    
    int n,nedge,head[maxn],ans;
    int a[maxn];
    
    void addedge(int a,int b){
        edge[nedge].next=head[a];
        edge[nedge].to=b;
        head[a]=nedge++; 
    }
    
    void dfs(int u,int d){
        if (d%2==1) ans^=a[u];
        for (int i=head[u];i!=-1;i=edge[i].next){
            int v=edge[i].to;
            dfs(v,d+1);
        }
    }
    
    int main(){
    
        int cas;
        scanf("%d",&cas);
        while(cas--){
            scanf("%d",&n); 
            nedge=0,ans=0;
            memset(head,-1,sizeof(head));
            for (int i=1;i<n;i++){
                int k;
                scanf("%d",&k);
                addedge(k,i);
            }
            for (int i=0;i<n;i++) scanf("%d",&a[i]);
            dfs(0,0);
            if (!ans) puts("lose");
            else puts("win");
        }
        return 0;
    }
    ore.cpp
  • 相关阅读:
    python笔记-列表和元组
    T-sql for xml path使用(转)
    除非另外还指定了 TOP 或 FOR XML,否则,ORDER BY 子句在视图、内联函数、派生表、子查询和公用表表达式中无效。
    VS2015 经常不出现智能提示,代码颜色也没有了
    虚拟机重新决定网卡地址时,老是报错
    模块的命令
    关闭NetworkManager的作用
    centos6上yum安装drbd(内核:2.6.32.696)
    检查硬件变化的命令kudzu
    parted分区和挂载及非交互式操作
  • 原文地址:https://www.cnblogs.com/ljc20020730/p/11819967.html
Copyright © 2011-2022 走看看