Codeforces Global Round 19
G.Birthday
题目描述
解法
可以写个暴力跑出 的情况,猜测最后的结果是最小的 满足它是 的幂次且
若 ,则可以让 ,这样答案不变,并且可以保证
有一个简单的构造想法是把 和 配对,就可以得到 和 ,我们把能配对的都配对了,留下得到的若干个 ,会剩下这些东西:
- 单独的
设原来的问题为 ,其中 表示每个数乘上的倍数,那么我们得到了 和 这两个子问题,可以直接递归下去。但是递归回来后还有一个问题,就是要把 变成 才能回溯。
发现对于 的情况,最后一步都是 ,而 可以完成下面神奇的操作:
- 操作 ,变成了
- 操作 ,变成了
也就是 在保证自身不变的情况下,可以倍增一个数。那么我们的问题都撤销最后一步,这样就可以留下一个
再来梳理一遍,对于 我们直接取用暴搜的结果;对于 ,我们可以直接递归下去,由于一定有一个子问题满足 ,所以回溯时一定能获取一个 ,用这个 倍增所有 数就可以回溯了。
操作次数 ,讲解没完全看懂的可以参考代码。
#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
题目描述
解法
首先考虑 的情况,设 ,表示如果前移 逆序对的减少量。
对于 的情况,直接用选取点的 计算代价是不行的,需要减去一个「选取子序列的顺序对数 逆序对数」的修正量。设 表示选取的子序列, 表示对应的值,那么减去的总量是:
所以我们要最大化
关键的结论是:对于原序列的逆序对 ,如果 在子序列中,那么 一定要在子序列中。
证明使用反证 + 调整法,即证明存在方案不劣于 在子序列中,但是 不在子序列中。
我们从 存在逆序对 ,满足 在子序列中,但是 不在子序列中 开始调整。考虑收紧限制,找到 最小并且满足上述条件的逆序对 ,因为 最小,所以在 之间不存在这样的数:
- 值介于 和 之间的数。
- 大于 且在子序列中的数。
- 小于 且不在子序列中的数。
考虑取消 的选择,转而选择 ,考虑代价的变化,对于某个位置 的贡献是:
- 如果 且不在子序列中,由于相对位置不变,所以无贡献。
- 如果 且在子序列中,当且仅当 介于 之间会有 的贡献,否则无贡献。
- 如果 且在子序列中,由于相对位置不变,所以无贡献。
- 如果 且不在子序列中,当且仅当 介于 之间会有 的贡献,否则无贡献。
- 如果 , 且不在子序列中,贡献为
- 如果 , 且在子序列中,贡献为
所以总贡献非正,并且由于调整可以在有限步内结束,所以 在子序列中,但是 不在子序列中 不会成为最优解。
如果感性理解上面的结论,就是决定逆序对的只有:位置和值。那么位置大的并且值小的放前面更优,所以原序列的逆序对会导致一些选择的偏序关系,可以指向上面的结论。
知道了这个结论后,我们只需要最大化
所以预处理 ,然后按照 从大到小选择即可,时间复杂度
#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();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
2021-07-03 CF1458D Flip and Reverse