zoukankan      html  css  js  c++  java
  • UNR#3 Day1——[ 堆+ST表+复杂度分析 ][ 结论 ][ 线段树合并 ]

    地址:http://uoj.ac/contest/45

    第一题是鸽子固定器。

      只会10分。按 s 从小到大排序,然后 dp[ i ][ j ][ k ] 表示前 i 个元素、已经选了 j 个、最小值所在位置是 k 的最大代价。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    using namespace std;
    int rdn()
    {
      int ret=0;bool fx=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
      while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
      return fx?ret:-ret;
    }
    ll Mx(ll a,ll b){return a>b?a:b;}
    ll Mn(ll a,ll b){return a<b?a:b;}
    const int N=1005,M=55;
    int n,m,ds,dv,f[M][N]; ll ans=-1e18;
    struct Node{
      int s,v;
      bool operator< (const Node &b)const
      {return s<b.s;}
    }a[N];
    ll calv(int x)
    { if(dv==1)return x;return (ll)x*x;}
    ll cals(int x)
    { if(ds==1)return x;return (ll)x*x;}
    int main()
    {
      n=rdn();m=rdn();ds=rdn();dv=rdn();
      for(int i=1;i<=n;i++)
        a[i].s=rdn(),a[i].v=rdn();
      sort(a+1,a+n+1);
      for(int i=1;i<=n;i++)
        {
          for(int j=Mn(i,m);j>1;j--)
        for(int k=1;k<i;k++)
          {
            ans=Mx(ans,calv(f[j-1][k]+a[i].v)-cals(a[i].s-a[k].s));
            f[j][k]=Mx(f[j][k],f[j-1][k]+a[i].v);
          }
          f[1][i]=a[i].v;
          ans=Mx(ans,calv(a[i].v));
        }
      printf("%lld
    ",ans);
      return 0;
    }

      当 ds=dv=1 的时候,不用考虑最小值是多少,只要考虑新增一个元素的代价。也就是原来的最后一个元素不是最大值、自己才是最大值。

      那么 ( dp[i][j] ) 表示前 i 个选 j 个、一定选了第 i 个;这样就知道第 i 个是当前的最后一个。

      转移只需 ( dp[i][j]=v[i]+s[i]+maxlimits_{1<=k<i} dp[k][j-1]-s[k] ) ;前缀最大值优化 DP 。没有实现。

      应该考虑不仅用 DP 来做。

      注意到一个暴力做法:确定 r ,枚举 l ;那么中间选择的一定是 v 最大的一些元素。那么维护对于 v 的小根堆,看看 l 能否取代堆顶即可。

      1.一旦 r 被弹出堆,就直接枚举下一个 r ; 2.用 ST 表来二分寻找下一个 l (下一个 v 大于堆顶的元素)。

      根据题解的证明,这样复杂度是 O(nmlogn) ,可过。虽然还可以做到 O(nm) ,但没有实现。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    #define ll long long
    using namespace std;
    int rdn()
    {
      int ret=0;bool fx=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
      while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
      return fx?ret:-ret;
    }
    ll Mx(ll a,ll b){return a>b?a:b;}
    ll Mn(ll a,ll b){return a<b?a:b;}
    const int N=2e5+5,K=20;
    int n,m,ds,dv,lg[N],bin[K],mx[N][K]; ll ans;
    struct Node{
      int s,v;
      bool operator< (const Node &b)const
      {return s<b.s;}
    }a[N];
    priority_queue<int,vector<int>,greater<int> > q;
    ll cal(int v,int s){return (dv==1?v:(ll)v*v)-(ds==1?s:(ll)s*s);}
    int main()
    {
      n=rdn();m=rdn();ds=rdn();dv=rdn();
      for(int i=1;i<=n;i++)
        a[i].s=rdn(), a[i].v=rdn();
      for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
      bin[0]=1;for(int i=1;i<=lg[n]+1;i++)bin[i]=bin[i-1]<<1;//+1
      sort(a+1,a+n+1);
      for(int i=1;i<=n;i++)
        {
          mx[i][0]=a[i].v;
          for(int t=1;i>=bin[t];t++)
        mx[i][t]=Mx(mx[i][t-1],mx[i-bin[t-1]][t-1]);
        }
      for(int i=1,j;i<=n;i++)
        {
          while(q.size())q.pop(); int sm=0;
          for(j=i;j&&j>i-m;j--)
        {
          q.push(a[j].v);sm+=a[j].v;
          ans=Mx(ans,cal(sm,a[i].s-a[j].s));
        }
          while(1)
        {
          int d=q.top(); if(d==a[i].v)break;
          for(int t=lg[j];t>=0;t--)
            if(bin[t]<=j&&mx[j][t]<=d)j-=bin[t];//bin[t]<=j
          if(!j)break;
          q.pop(); q.push(a[j].v); sm+=a[j].v-d;
          ans=Mx(ans,cal(sm,a[i].s-a[j].s));
          j--;/////
        }
        }
      printf("%lld
    ",ans);
      return 0;
    }
    View Code

    第二题是 To Do Tree 。

      每次把新解锁的点放进堆里,每次操作取出向下的 dep 前 m 大的元素即可。不太会证明……

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<queue>
    using namespace std;
    int rdn()
    {
      int ret=0;bool fx=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
      while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
      return fx?ret:-ret;
    }
    int Mx(int a,int b){return a>b?a:b;}
    int Mn(int a,int b){return a<b?a:b;}
    const int N=1e5+5;
    int n,m,hd[N],xnt,to[N<<1],nxt[N<<1];
    int dep[N],p[N],tot,ct[N],cnt;
    struct cmp{
      bool operator() (const int &a,const int &b)const
      {return dep[a]<dep[b];}
    };
    priority_queue<int,vector<int>,cmp> q;
    void add(int x,int y)
    {
      to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;
    }
    void dfs(int cr)
    {
      for(int i=hd[cr],v;i;i=nxt[i])
        {
          dfs(v=to[i]); dep[cr]=Mx(dep[cr],dep[v]+1);
        }
    }
    int main()
    {
      n=rdn();m=rdn();
      for(int i=2,d;i<=n;i++)
        { d=rdn();add(d,i);}
      dfs(1); q.push(1);
      while(q.size())
        {
          cnt++; int lst=tot;
          while(q.size())
        {
          p[++tot]=q.top(); q.pop();
          if(tot-lst==m)break;
        }
          ct[cnt]=tot-lst;
          for(int i=lst+1;i<=tot;i++)
        for(int j=hd[p[i]];j;j=nxt[j])
          q.push(to[j]);
        }
      printf("%d
    ",cnt);
      for(int i=1,nw=1;i<=cnt;i++)
        {
          printf("%d ",ct[i]);
          for(int j=1;j<=ct[i];j++,nw++)
        printf("%d ",p[nw]); puts("");
        }
      return 0;
    }
    View Code

     第三题是配对树。

      不知道怎么不枚举序列上的区间。

      考虑 m2 枚举区间。把区间里的元素“加入”表示树上该点到根的链上点的状态都取反。树点的状态为 0/1 表示其子树里有 偶数/奇数 个区间里的点;如果状态是 1 的话,该点连向父亲的边需要加入答案。

      用 LCT 维护这个过程。区间 (L-2,R) 可以继承 (L,R) 的状态。应该是 O( m2logn ) 的,但是在链的数据上实测很慢。不 makeroot 而每次 access 的 LCT 复杂度?总之得了20分。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define ll long long
    #define ls c[x][0]
    #define rs c[x][1]
    using namespace std;
    int rdn()
    {
      int ret=0;bool fx=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
      while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
      return fx?ret:-ret;
    }
    const int N=1e5+5,mod=998244353;
    int upt(int x){while(x>=mod)x-=mod;while(x<0)x+=mod;return x;}
    
    int n,m,a[N],hd[N],xnt,to[N<<1],nxt[N<<1],w[N<<1],ans,prn;
    int fa[N],c[N][2],vl[N],sm[N],s2[N]; bool tg[N],b[N];
    bool rv[N];
    int sta[N],top;
    void add(int x,int y,int z)
    { to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;w[xnt]=z;}
    void dfs(int cr,int f)
    {
      fa[cr]=f;
      for(int i=hd[cr],v;i;i=nxt[i])
        if((v=to[i])!=f)
          {
        vl[v]=sm[v]=w[i]; dfs(v,cr);
          }
    }
    bool isrt(int x){return c[fa[x]][0]!=x&&c[fa[x]][1]!=x;}
    void pshd(int x)
    {
      if(rv[x])
        {
          rv[x]=0; if(ls)rv[ls]^=1; if(rs)rv[rs]^=1; swap(ls,rs);
        }
      if(!tg[x])return; tg[x]=0;
      if(ls) s2[ls]=upt(sm[ls]-s2[ls]), tg[ls]^=1, b[ls]^=1;
      if(rs) s2[rs]=upt(sm[rs]-s2[rs]), tg[rs]^=1, b[rs]^=1;
    }
    void pshp(int x)
    {
      sm[x]=upt(sm[ls]+sm[rs]); sm[x]=upt(sm[x]+vl[x]);
      s2[x]=upt(s2[ls]+s2[rs]); if(b[x])s2[x]=upt(s2[x]+vl[x]);
    }
    void rotate(int x)
    {
      int y=fa[x],z=fa[y]; bool d=(x==c[y][1]);
      if(!isrt(y))c[z][y==c[z][1]]=x;
      fa[x]=z; fa[y]=x; fa[c[x][!d]]=y;
      c[y][d]=c[x][!d]; c[x][!d]=y;
      pshp(y); pshp(x);
    }
    void splay(int x)
    {
      sta[top=1]=x;
      for(int k=x;fa[k];k=fa[k])sta[++top]=fa[k];
      for(int i=top;i;i--)pshd(sta[i]);
      for(int y,z;!isrt(x);rotate(x))
        {
          y=fa[x];z=fa[y];
          if(!isrt(y))
        ((x==c[y][0])^(y==c[z][0]))?rotate(x):rotate(y);
        }
    }
    void access(int x)
    {
      for(int t=0;x;t=x,x=fa[x])
        {
          splay(x);//tg[x]=0
          c[x][1]=t; pshp(x);
        }
    }
    void mkrt(int x)
    {
      access(x); splay(x); rv[x]^=1; swap(ls,rs);
    }
    void cz(int x)
    {
      if(rand()&1)
        {
          mkrt(1); access(x); splay(x);
          int y=s2[x];
          s2[x]=upt(sm[x]-s2[x]); tg[x]^=1; b[x]^=1;
          ans=upt(ans+s2[x]-y);
        }
      else
        {
          mkrt(x); access(1); splay(1);
          int y=s2[1];
          s2[1]=upt(sm[1]-s2[1]); tg[1]^=1; b[1]^=1;
          ans=upt(ans+s2[1]-y);
        }
    }
    int main()
    {
      srand(1039121);
      n=rdn();m=rdn();
      for(int i=1,u,v,w;i<n;i++)
        {
          u=rdn();v=rdn();w=rdn()%mod;
          add(u,v,w); add(v,u,w);
        }
      for(int i=1;i<=m;i++)a[i]=rdn();
      dfs(1,0);
      for(int i=2;i<=m;i++)
        {
          for(int j=i;j>1;j-=2)
        {
          cz(a[j]); cz(a[j-1]); prn=upt(prn+ans);
        }
          ans=0; for(int j=1;j<=n;j++)s2[j]=0, tg[j]=b[j]=0;
        }
      printf("%d
    ",prn);
      return 0;
    }

      不枚举序列上的区间的方法就是直接考虑一条树边被算了几次。

      把一个点的子树里的节点对应的序列上的位置都赋值为1。有多少个 “长度为偶数且包含了奇数个1” 的区间,该点连向父亲的边就被算了多少次。

      因为是把子树里的点的位置都赋值为1,所以考虑线段树合并。

      又注意到长度为偶数的区间 ( L, R ) ,R 和 L-1 的奇偶性相同;所以分开考虑序列的奇数位置和偶数位置,维护“前缀和为奇数/偶数”的个数即可。区间合并的时候看左区间若有奇数个1,就需要把右区间的奇/偶翻转再加到自己身上。

      注意动态开点的话,pshp 的时候小心没有左/右孩子的情况。

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #include<vector>
    #define ll long long
    #define ls Ls[cr]
    #define rs Rs[cr]
    using namespace std;
    int rdn()
    {
      int ret=0;bool fx=1;char ch=getchar();
      while(ch>'9'||ch<'0'){if(ch=='-')fx=0;ch=getchar();}
      while(ch>='0'&&ch<='9')ret=ret*10+ch-'0',ch=getchar();
      return fx?ret:-ret;
    }
    const int N=1e5+5,M=N*20,mod=998244353;
    int upt(int x){while(x>=mod)x-=mod;while(x<0)x+=mod;return x;}
    void Add(int &x,int y){x=upt(x+y);}
    
    int n,m,hd[N],xnt,to[N<<1],nxt[N<<1],w[N<<1],ans;
    int rt[N],tot,Ls[M],Rs[M],sm[M],ct[M][2];
    vector<int> ps[N];
    void add(int x,int y,int z)
    { to[++xnt]=y;nxt[xnt]=hd[x];hd[x]=xnt;w[xnt]=z;}
    int cal(bool fx,int l,int r)
    {
      if(!fx)return (r>>1)-((l-1)>>1);//if l==0 then +1
      return ((r+1)>>1)-(l>>1);
    }
    void pshp(int cr,int l,int mid,int r)
    {
      sm[cr]=sm[ls]+sm[rs];
      for(int i=0;i<=1;i++)ct[cr][i]=ct[ls][i];
      if(sm[ls]&1)
        for(int i=0;i<=1;i++)
          Add(ct[cr][i],cal(i,mid+1,r)-ct[rs][i]);//-ct...
      else
        for(int i=0;i<=1;i++)
          Add(ct[cr][i],ct[rs][i]);
    }
    void mrg(int l,int r,int &cr,int pr)
    {
      if(!cr){cr=pr;return;} if(!pr)return;
      int mid=l+r>>1;
      mrg(l,mid,ls,Ls[pr]); mrg(mid+1,r,rs,Rs[pr]);
      pshp(cr,l,mid,r);
    }
    void mdfy(int l,int r,int &cr,int p)
    {
      if(!cr) cr=++tot;
      if(l==r)
        {
          if(l&1)ct[cr][1]++; else ct[cr][0]++;
          sm[cr]=1; return;
        }
      int mid=l+r>>1;
      if(p<=mid)mdfy(l,mid,ls,p); else mdfy(mid+1,r,rs,p);
      pshp(cr,l,mid,r);
    }
    void dfs(int cr,int fa,int tw)
    {
      for(int i=hd[cr],v;i;i=nxt[i])
        if((v=to[i])!=fa)
          {
        dfs(v,cr,w[i]); mrg(0,m,rt[cr],rt[v]);
          }
      for(int i=0,lm=ps[cr].size();i<lm;i++)
        mdfy(0,m,rt[cr],ps[cr][i]);
      int r=rt[cr];
      ans=(ans+(ll)ct[r][0]*(cal(0,0,m)-ct[r][0])%mod*tw)%mod;
      ans=(ans+(ll)ct[r][1]*(cal(1,0,m)-ct[r][1])%mod*tw)%mod;
    }
    int main()
    {
      n=rdn();m=rdn();
      for(int i=1,u,v,z;i<n;i++)
        {
          u=rdn();v=rdn();z=rdn();
          add(u,v,z);add(v,u,z);
        }
      for(int i=1,d;i<=m;i++)
        { d=rdn();ps[d].push_back(i);}
      dfs(1,0,0); printf("%d
    ",ans);
      return 0;
    }
    View Code
  • 相关阅读:
    centos7 安装高版本svn
    idea 常用快捷键
    IDEA 打可执行jar包(maven项目)
    服务器安装JDK
    阿里云服务器连接AWS-S3
    mysql5.7 修改密码,修改权限
    win10 手动安装mysql-8.0.11-winx64.zip
    centos7 关闭防火墙
    centos7 配置阿里云yum源
    centos7 源码安装nginx
  • 原文地址:https://www.cnblogs.com/Narh/p/11177057.html
Copyright © 2011-2022 走看看