zoukankan      html  css  js  c++  java
  • Atcoder Grand Contest 023

    A

    B

    C(计数)

    题意:

      有n个白球排成一行,故有n-1个空隙,我可以给一个空隙对应的两个白球都涂黑。n-1个空隙的一个排列就对应着一个涂黑顺序,定义这个涂黑顺序的价值是“将所有n个球都涂黑的最少步数”。对于n-1的所有排列,我们要求对应价值的和。

      n<=1e6

    分析:

      首先易得最少步数一定在ceil(n/2)到n-1之间,我们去枚举最少步数,然后算对应的排列有多少个

      设f(i)表示最少步数为i的排列有多少个

      我们去观察n-1个空隙,假设我们取的空隙从小到大编号依次是$x_1,x_2,x_3,......,x_i$

      那么x1一定是1,xi一定是n-1,并且满足$x_{i+1}-x_i=1 or 2$

      那取哪些位置可以通过组合数算出来,然后它们在排列一下,剩下的(n-1-i)个无关空隙也丢在后面排列一下,就能计算了

    D(贪心)

    题意:

      在一个数轴上有n个点,表示n个公寓,第i个公寓的坐标是xi,住了pi个人。这些人都是公司的员工,公司的坐标在S。

      现在下班了,所有人坐上了大巴车,大巴车从S出发,大巴的速度是固定的,每秒1个单位长度。每次车上的人都进行投票,选择大巴是向左开还是向右开(平票就向左)。每个人的投票原则就是要让自己尽量早到家,并且每个人都是极其聪明的。大巴在到达了一个坐标之后,家住在这的人都会立刻下车。

      问大巴会运行多长时间。

      n<=1e5,pi<=1e9,坐标<=1e9

    分析:

      如果S在所有点的一侧,那么很显然就是直接一路开过去。

      如果S在这些点的中间位置,那么显然下车顺序一定是从中间到两边不断吞噬。

      我们来考察一下最外面的两个点1和n,不妨设p[1]>=p[n]

      这样的话,在到达第n个点之前,一定到达了第1个点(假设现在在第n-1个点且第1个点还没到达,那么第一个点人多,所以他们会投票往左开)。在到达了第1个点后,再一路向右开。也就是说time[n]=time[1]+x[n]-x[1]

      换句话说,住在第n个点人的到家时间是第1个点的人到家时间加上一个常数,第n个点的人希望自己到家时间尽量短,也就是希望第1个点的人到家时间尽量短,也就是说第n个点的人的投票一定是和第一个点的人投票是一样的

      那这样我们就可以看成只有1~n-1个点,将p[n]加到[1]上,缩小了问题规模

      p[1]<p[n]也是同理

      这样不断从两边向中间缩小规模,最后得到一个点,直接从S开到这个点就行了,在贪心过程中记录下时间的计算关系,计算下时间就行了。

      注意一下细节,在缩小规模的过程中,如果出现了S在目前所有点的一侧,要特殊处理后面的所有过程。

      时间复杂度O(n)

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const int maxn=1e5;
     4 typedef long long ll;
     5 #define mp make_pair
     6 ll x[maxn+5],p[maxn+5];
     7 vector<pair<int,ll> > g[maxn+5];
     8 ll ans[maxn+5];
     9 int n,s;
    10 void addedge(int u,int v,ll w)
    11 {
    12     g[u].push_back(mp(v,w));
    13 }
    14 void dfs(int k)
    15 {
    16     for(auto x:g[k])
    17     {
    18         ans[x.first]=ans[k]+x.second;
    19         dfs(x.first);
    20     }
    21 }
    22 int main()
    23 {
    24  
    25     scanf("%d%d",&n,&s);
    26     for(int i=1;i<=n;++i) scanf("%lld%lld",&x[i],&p[i]);
    27     int l=1,r=n;
    28     while(l<r)
    29     {
    30         if(x[l]>=s) addedge(l,l+1,x[l+1]-x[l]),++l;
    31         else
    32             if(x[r]<=s) addedge(r,r-1,x[r]-x[r-1]),--r;
    33         else
    34         if(p[l]>=p[r]) p[l]+=p[r],addedge(l,r,x[r]-x[l]),--r;
    35         else p[r]+=p[l],addedge(r,l,x[r]-x[l]),++l;
    36     }
    37     ans[l]=abs(x[l]-s);
    38     dfs(l);
    39     printf("%lld
    ",max(ans[1],ans[n]));
    40     return 0;
    41 }
    View Code

    E(计数)

    题意:

      给出a1~an,表示每一个位置的上限,在满足这个上限要求的所有n的排列中,求逆序对个数的和。

      n<=2e5

    分析:

      首先需要会两个子问题:

      1、给定上限要求a1~an,一共有多少个排列满足这个上限限制呢?

        记cnt[k]=(满足ai>=k的个数)-(n-k),那么排列个数就是cnt[1]*cnt[2]*...*cnt[n]。这是因为我们从大到小去安排,对于最大的数字n,我们看看它有多少个位置可以放;对于次大的数字n-1,我们看看它有多少个位置可以放(注意之前在某个位置已经放过了一个n)……。这样总排列数就是所有cnt的乘积。

      2、如何求n的所有排列的逆序对的个数和?

        算任意两个位置的贡献。考虑枚举两个位置i和j(i<j),我们去计算有多少个排列满足pi>pj。那么怎么计算有多少个排列满足这个条件呢?考虑对称性,pi>pj的排列个数=pi<pj的排列个数,故pi<pj的排列个数就是总排列个数的一半,也就是n!/2

        这个思想就是解决这个问题的关键。当然如果计算逆序对个数和的话,再化化简就能得出一个通式,这里就不推导了。

      现在回到这个问题,我们假设满足限制的排列个数是S(如果S是0,就直接返回0了)。

      我们去枚举任意两个位置i,j(i<j),我们去计算有多少个排列满足pi>pj,且满足a[i]的限制

      ①对于ai<=aj的情况,相当于把aj变成ai,然后求有多少个排列满足这个限制,再除以2。我们考虑把aj变成ai会带来什么影响,其实就是cnt[a[i]+1]~cnt[a[j]]这一段都减去了1,我们可以用一个D[]来表示(cnt[i]-1)/cnt[i]的前缀积,那么答案就是1/2*S*D[a[j]]/D[a[i]]。这样时间复杂度是O(n^2)的,但是很显然这个是可以用bit来维护的,O(nlogn)

      ②对于ai>aj的情况,处理思路也是差不多的,但这个时候就要计算反面,即满足i<j,ai>aj,pi>pj的排列数等于总排列数S-"满足i<j,ai>aj,pi<pj的排列数",后面这个东西的处理和第一种情况是类似的。

      但还有一个麻烦的问题,就是D[x]/D[y]可能会出现分母为0的情况,我们来考虑它的实际意义

      0 0 1 2 3 0 2

      比如这里D[5]/D[3]虽然是0/0,但是是合法的,它表示将[4,5]这一段的cnt用cnt-1替换,我们可以将每一个D[i]看做是$D[i]*0^{x[i]}$,若作除法的两个D的0上指标相同,那就可以得到正确结果,否则就得到0。

      那么怎么具体实现呢?注意到指标相同的两个数一定是这个数组里连续的无0的一段,所以我们可以预处理出每个位置的pre[]和suf[],表示0的上指标相同的左右位置极限,然后在树状数组求解的时候强化一下询问范围就行了。

      时间复杂度O(nlogn)

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 typedef long long ll;
     4 const int maxn=2e5;
     5 const ll mod=1e9+7;
     6 ll a[maxn+5],num[maxn+5],cnt[maxn+5],S,D[maxn+5];
     7 int pre[maxn+5],suf[maxn+5];
     8 int n;
     9 ll c[2][maxn+5];
    10 ll ans=0;
    11 ll Pow(ll a,ll b,ll mod)
    12 {
    13     ll ans=1;
    14     while(b)
    15     {
    16         if(b&1) ans=ans*a%mod;
    17         a=a*a%mod;
    18         b>>=1;
    19     }
    20     return ans;
    21 }
    22 ll inv(ll a)
    23 {
    24     return Pow(a,mod-2,mod);
    25 }
    26 int lowbit(int x)
    27 {
    28     return x&(-x);
    29 }
    30 ll add(ll *c,int k,ll data)
    31 {
    32     for(;k<=n+1;k+=lowbit(k)) c[k]=(c[k]+data)%mod;
    33 }
    34 ll query(ll *c,int k)
    35 {
    36     ll ans=0;
    37     for(;k;k-=lowbit(k)) ans=(ans+c[k])%mod;
    38     return ans;
    39 }
    40 int main()
    41 {
    42  
    43     scanf("%d",&n);
    44     for(int i=1;i<=n;++i) scanf("%lld",&a[i]),++num[a[i]];
    45     for(int i=n-1;i>=1;--i) num[i]+=num[i+1];
    46     S=1;
    47     D[0]=1;
    48     for(int i=1;i<=n;++i)
    49     {
    50         cnt[i]=num[i]-(n-i),S=S*cnt[i]%mod;
    51         if(cnt[i]<=0) return 0*printf("0
    ");
    52         D[i]=D[i-1];
    53         if(cnt[i]>1) D[i]=D[i]*(cnt[i]-1)%mod*inv(cnt[i])%mod,pre[i]=pre[i-1];else pre[i]=i;
    54     }
    55     suf[n]=n;
    56     for(int i=n-1;i>=1;--i)
    57         if(cnt[i]>1)
    58         {
    59             if(cnt[i+1]>1) suf[i]=suf[i+1];else suf[i]=i;
    60         }
    61         else suf[i]=i;
    62     for(int i=1;i<=n;++i)
    63     {
    64         ans=(ans+(query(c[0],a[i])-query(c[0],pre[a[i]]-1))%mod*S%mod*D[a[i]]%mod*inv(2LL)%mod)%mod;
    65         if(ans<0) ans+=mod;
    66         add(c[0],a[i],inv(D[a[i]]));
    67     }
    68     for(int i=1;i<=n;++i) c[0][i]=0;
    69     for(int i=1;i<=n;++i)
    70     {
    71         ans=(ans+S*(query(c[1],n)-query(c[1],a[i]))%mod-inv(2LL)*S%mod*inv(D[a[i]])%mod*(query(c[0],suf[a[i]])-query(c[0],a[i]))%mod)%mod;
    72         if(ans<0) ans+=mod;
    73         add(c[0],a[i],D[a[i]]);
    74         add(c[1],a[i],1LL);
    75     }
    76     printf("%lld
    ",ans);
    77     return 0;
    78 }
    View Code

    F(贪心)

    题意:

      给一个n个点的树,每个点表示一个数字0/1。你需要找出一个拓扑序,使得这个拓扑序对应的01序列逆序对个数尽量少。

      n<=2e5

    分析:

      0应该尽量放前面,于是我们在树中找一个是0的点,让它和其父亲绑定。(即我们希望它的父亲出现后,紧跟在后出现的就是这个点)

      假设这样的限制都弄出来了,会有一些互相无限制的点集,那么我们怎么决定先后取的顺序呢?

      假设有这样两个点集a和b,a里有a0个0,a1个1,b中有b0个0,b1个1

      那么如果ab比ba优秀,那么一定有a1*b0<a0*b1,即a1/a0<b1/b0

      于是我们的贪心方法出来了,刚开始,每个点自己作为一个点集,我们记录每个点集的cnt0和cnt1。

      然后我们选出cnt1/cnt0最小的点集,把这个点集和其父亲所在的点集合并就行了,一直合并只剩最后一个根节点

      用set实现就行了

      时间复杂度O(nlogn)

     1 #include<bits/stdc++.h>
     2 using namespace std;
     3 const int maxn=2e5;
     4 struct wjmzbmr
     5 {
     6     int c0,c1,id;
     7     wjmzbmr(){}
     8     wjmzbmr(int a,int b,int c):c0(a),c1(b),id(c) {}
     9     bool operator < (const wjmzbmr& x) const
    10     {
    11         if(1LL*c0*x.c1!=1LL*c1*x.c0) return 1LL*c0*x.c1>1LL*c1*x.c0;
    12         return id<x.id;
    13     }
    14 }a[maxn+5];
    15 int p[maxn+5],f[maxn+5],tmp[maxn+5],tail[maxn+5],nx[maxn+5],c0[maxn+5],c1[maxn+5],d[maxn+5];
    16 int n,root;
    17 int b[maxn+5];
    18 int c[maxn+5];
    19 long long ans=0;
    20 multiset<wjmzbmr> s;
    21 int find(int x)
    22 {
    23     if(f[x]==x) return f[x];else return f[x]=find(f[x]);
    24 }
    25 int main()
    26 {
    27 
    28     scanf("%d",&n);
    29     for(int i=2;i<=n;++i) scanf("%d",&p[i]);
    30     for(int i=1;i<=n;++i)
    31     {
    32         scanf("%d",&tmp[i]);
    33         f[i]=i,tail[i]=i;
    34         if(tmp[i]==0) a[i]={1,0,i},c0[i]=1;else a[i]={0,1,i},c1[i]=1;
    35         if(i>=2)
    36         s.insert(a[i]);
    37     }
    38     while(s.size()>0)
    39     {
    40         wjmzbmr x=*s.begin();
    41         s.erase(s.begin());
    42         int u=x.id;
    43  
    44         int v=find(p[x.id]);
    45         nx[tail[v]]=u;
    46         ++d[u];
    47         f[u]=v,tail[v]=tail[u];
    48         if(v!=1) s.erase(s.find(wjmzbmr(c0[v],c1[v],v)));
    49         c0[v]+=c0[u],c1[v]+=c1[u];
    50         if(v!=1) s.insert(wjmzbmr(c0[v],c1[v],v));
    51     }
    52     for(int i=1;i<=n;++i)
    53         if(d[i]==0)
    54         {
    55             root=i;
    56             break;
    57         }
    58     for(int i=1;i<=n;++i,root=nx[root])
    59     {
    60         b[i]=tmp[root];
    61     }
    62  
    63     int now=0;
    64     for(int i=n;i>=1;--i)
    65         if(b[i]==1) ans+=now;else now+=1;
    66     printf("%lld
    ",ans);
    67     return 0;
    68 }
    View Code
  • 相关阅读:
    Linux
    Linux -- 文件统计常用命令
    再论最小二乘
    再论EM算法的收敛性和K-Means的收敛性
    【MySQL】优化—工欲善其事,必先利其器之EXPLAIN
    【Linux】浅谈段页式内存管理
    【Linux】之系统工具top
    【Linux】之shell特殊变量整理
    【Linux】系统 之 RAID
    【MySQL】binlog缓存的问题和性能
  • 原文地址:https://www.cnblogs.com/wmrv587/p/8976535.html
Copyright © 2011-2022 走看看