Codeforces Global Round 19

G.Birthday

题目描述

点此看题

解法

可以写个暴力跑出 \(n=3,4,5,6,7...\) 的情况,猜测最后的结果是最小的 \(x\) 满足它是 \(2\) 的幂次且 \(x\geq n\)

\(n=x\),则可以让 \(n\leftarrow n-1\),这样答案不变,并且可以保证 \(n<x\)

有一个简单的构造想法是把 \(i(i>\frac{x}{2})\)\(x-i\) 配对,就可以得到 \(x\)\(2i-x\),我们把能配对的都配对了,留下得到的若干个 \(x\),会剩下这些东西:

  • \(1,2...n-x-1\)
  • \(2,4...2\cdot (n-\frac{x}{2})\)
  • 单独的 \(\frac{x}{2}\)

设原来的问题为 \((n,x,k)\),其中 \(k\) 表示每个数乘上的倍数,那么我们得到了 \((n-x-1,\frac{x}{2},k)\)\((n-\frac{x}{2},\frac{x}{2},2k)\) 这两个子问题,可以直接递归下去。但是递归回来后还有一个问题,就是要把 \(\frac{x}{2}\) 变成 \(x\) 才能回溯。

发现对于 \(n\leq 7\) 的情况,最后一步都是 \((0,x)\),而 \(0\) 可以完成下面神奇的操作:

  • 操作 \((0,a)\),变成了 \((a,a)\)
  • 操作 \((a,a)\),变成了 \((0,2a)\)

也就是 \(0\) 在保证自身不变的情况下,可以倍增一个数。那么我们的问题都撤销最后一步,这样就可以留下一个 \(0\)

再来梳理一遍,对于 \(n\leq 7\) 我们直接取用暴搜的结果;对于 \(n\geq 8\),我们可以直接递归下去,由于一定有一个子问题满足 \(n'\geq 3\),所以回溯时一定能获取一个 \(0\),用这个 \(0\) 倍增所有 \(<x\) 数就可以回溯了。

操作次数 \(O(n\log n)\),讲解没完全看懂的可以参考代码。

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
#define pii pair<int,int> 
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
vector<pii> b[8] = {
	{},
	{},
	{},
	{{1, 3}, {2, 2}},
	{{1, 3}, {2, 2}},
	{{1, 2}, {1, 3}, {2, 4}, {2, 6}, {4, 4}, {3, 5}, {0, 2}, {2, 2}, {0, 4}, {4, 4}},
	{{1, 2}, {1, 3}, {2, 4}, {2, 6}, {4, 4}, {3, 5}, {2, 6}, {0, 4}, {4, 4}},
	{{1, 2}, {3, 5}, {2, 6}, {4, 4}, {1, 0}, {1, 3}, {1, 7}, {2, 6}, {4, 4}}
};
int T,n,m;vector<pii> a;
void add(int x,int y,int k)
{
	a.push_back({x*k,y*k});
}
void get(int x,int to,int k)
{
	while(x<to)
	{
		add(0,x,k);
		add(x,x,k);
		x<<=1;
	}
}
void dfs(int n,int x,int k)
{
	if(n==x) {dfs(n-1,x,k);return ;}
	if(n<=x/2)
	{
		dfs(n,x/2,k);
		for(int i=1;i<n;i++) get(x/2,x,k);
		return ;
	}
	if(n<=7)
	{
		for(auto [x,y]:b[n]) add(x,y,k);
		return ;
	}
	for(int i=x/2+1;i<=n;i++) add(x-i,i,k);
	int A=x-n-1,B=n-x/2;
	if(A>=3)
	{
		dfs(A,x/2,k);
		for(int i=1;i<A;i++) get(x/2,x,k);
	}
	if(B>=3) dfs(B,x/2,k*2);
	get(x/2,x,k);
	if(A<=2) for(int i=1;i<=A;i++) get(i,x,k);
	if(B<=2) for(int i=1;i<=B;i++) get(i,x/2,k*2);
	if(A>=3 && B>=3) add(0,x,k);
}
void work()
{
	n=read();m=1;a.clear();
	if(n==2) {puts("-1");return ;}
	while(m<n) m<<=1;
	if(n<=7) a=b[n];
	else dfs(n,m,1);
	add(0,m,1);
	printf("%d\n",a.size());
	for(auto [x,y]:a) printf("%d %d\n",x,y);
}
signed main()
{
	T=read();
	while(T--) work();
}

H.Minimize Inversions Number

题目描述

点此看题

解法

首先考虑 \(k=1\) 的情况,设 \(d_i=\sum_{j=1}^{i-1} [p_j>p_i]-[p_j<p_i]\),表示如果前移 \(i\) 逆序对的减少量。

对于 \(k>1\) 的情况,直接用选取点的 \(\sum d_i\) 计算代价是不行的,需要减去一个「选取子序列的顺序对数 \(-\) 逆序对数」的修正量。设 \(s_1,s_2...s_k\) 表示选取的子序列,\(q_1,q_2...q_k\) 表示对应的值,那么减去的总量是:

\[\sum_{i=1}^k d_{s_i}+{k\choose 2}-2\sum_{i=1}^k\sum _{j=1}^{i-1} [q_j>q_i] \]

所以我们要最大化 \(\sum_{i=1}^k d_{s_i}-2\sum_{i=1}^k\sum_{j=1}^{i-1}[q_j>q_i]\)

关键的结论是:对于原序列的逆序对 \((i,j)\),如果 \(i\) 在子序列中,那么 \(j\) 一定要在子序列中。

证明使用反证 + 调整法,即证明存在方案不劣于 \(i\) 在子序列中,但是 \(j\) 不在子序列中。

我们从 存在逆序对 \((i,j)\),满足 \(i\) 在子序列中,但是 \(j\) 不在子序列中 开始调整。考虑收紧限制,找到 \(j-i\) 最小并且满足上述条件的逆序对 \((i,j)\),因为 \(j-i\) 最小,所以在 \((i,j)\) 之间不存在这样的数:

  • 值介于 \(p_i\)\(p_j\) 之间的数。
  • 大于 \(p_i\) 且在子序列中的数。
  • 小于 \(p_j\) 且不在子序列中的数。

考虑取消 \(i\) 的选择,转而选择 \(j\),考虑代价的变化,对于某个位置 \(k\) 的贡献是:

  • 如果 \(k>j\) 且不在子序列中,由于相对位置不变,所以无贡献。
  • 如果 \(k>j\) 且在子序列中,当且仅当 \(p_k\) 介于 \(p_i,p_j\) 之间会有 \(-2\) 的贡献,否则无贡献。
  • 如果 \(k<i\) 且在子序列中,由于相对位置不变,所以无贡献。
  • 如果 \(k<i\) 且不在子序列中,当且仅当 \(p_k\) 介于 \(p_i,p_j\) 之间会有 \(-2\) 的贡献,否则无贡献。
  • 如果 \(i<k<j\)\(p_k>p_i\) 且不在子序列中,贡献为 \(-1\)
  • 如果 \(i<k<j\)\(p_k<p_j\) 且在子序列中,贡献为 \(-1\)

所以总贡献非正,并且由于调整可以在有限步内结束,所以 \(i\) 在子序列中,但是 \(j\) 不在子序列中 不会成为最优解。

如果感性理解上面的结论,就是决定逆序对的只有:位置和值。那么位置大的并且值小的放前面更优,所以原序列的逆序对会导致一些选择的偏序关系,可以指向上面的结论。

知道了这个结论后,我们只需要最大化 \(\sum_{i=1}^k d_{s_i}-2\sum_{i=1}^k\sum_{j=s_i+1}^n [p_i>p_j]\)

所以预处理 \(c_i=d_i-2\sum_{j=i+1}^n [p_i>p_j]\),然后按照 \(c\) 从大到小选择即可,时间复杂度 \(O(n\log n)\)

#include <cstdio>
#include <iostream>
#include <algorithm>
using namespace std;
const int M = 500005;
#define int long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,ans,a[M],b[M],c[M];
void add(int x)
{
	for(int i=x;i<=n;i+=i&(-i)) b[i]++;
}
int ask(int x)
{
	int r=0;
	for(int i=x;i>0;i-=i&(-i)) r+=b[i];
	return r;
}
void work()
{
	n=read();ans=0;
	for(int i=1;i<=n;i++) a[i]=read(),b[i]=0;
	for(int i=1;i<=n;i++)
	{
		int d=ask(a[i]);
		ans+=i-1-d;c[i]=i-1-2*d;
		add(a[i]);
	}
	for(int i=1;i<=n;i++) b[i]=0;
	for(int i=n;i>=1;i--)
		c[i]-=2*ask(a[i]),add(a[i]);
	sort(c+1,c+1+n,greater<int>());
	printf("%lld ",ans);
	for(int i=1;i<=n;i++)
	{
		ans-=c[i]+(i-1);
		printf("%lld ",ans);
	}
	puts("");
}
signed main()
{
	T=read();
	while(T--) work();
}
posted @ 2022-07-03 19:48  C202044zxy  阅读(93)  评论(2编辑  收藏  举报