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\n",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\n");
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\n",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\n",ans);
67     return 0;
68 }
View Code

 

posted @ 2018-05-01 16:14 Chellyutaha 阅读(...) 评论(...) 编辑 收藏