北师校赛,三号题目Squared Permutation, 映射+线段树

https://www.bnuoj.com/v3/problem_show.php?pid=51636

如果我们线段树,每次更新都只维护位置i所代表的数字的话,会有一个显然的错误:

1
4
4 3 2 1
3
1 1 3
1 2 4
2 3 4
这组数据答案为5,为什么呢:
初识的f[i] 为4,3,2,1, 位置i 的值为f[f[i]]
所以树的节点 1,2,3,4, 节点值为1,2,3,4
……………1-4
………1-2………3-4
……1……2……3……4

如果我们更新 只更新l,r的节点值v
那么第一次交换,f[]= 2 3 4 1, 节点值v更新为:3 2 1 4
第二次交换以后, f[]=2 1 4 3,节点值更新为:3 2 1 4
显然二次交换是错的,交换后的v应该为 分别为:3 4 1 2 和1 2 3 4

那么我们如果把其他所有的点全部更新一边,也就是我们在比赛中的想法,是必然超时的, 其实这个想都不要想的超时,
那我们需要更新所有的点吗???
在纸上写写看:
5
5 4 3 2 1
3
1 1 3
1 2 5

上面这组数据有两次交换:
初始f []=5 4 3 2 1
初始 v =1 2 3 4 5

一次交换后 f[]=3 4 5 2 1
只更新l,r错误的v= 5 2 1 4 5
而正确的v= 5 2 1 4 3

第二次交换2,5 f[]= 3 1 5 2 4
只更新l,r错误的v = 5 3 1 4 2
正确的 v = 5 3 4 1 2 (1.4不对)
我们可以发现其实错误的v 和正确的v 之间其实并不需要一个On 来更新一次;
除了l,r 更新
我们只需要更新l,r的位置 这个节点

比如一次交换1,3 ,我们更新1,3 这两个点之外 f[]=3 4 5 2 1
还需要更新 g[1]和g[3],即5 和 1
然后重点是需要更新正确的g值,现在的g[3]=1 ,g[5]=3

第二次更新 则应先更新 g[2] 和2 再更新 g[r]和r
f[]=3 1 5 2 4 时
就更新v:5 2 1 4 3 -> v:5 2 1 1 3 -> 5 3 1 1 3
再: v:5 3 1 1 3 -> v: 5 3 4 1 5 -> 5 3 4 1 2 ,最后得到的结果是一毛一样的,完美

后记:但是这尼玛我还是不知道为毛怎么证明更新这两个g,就是正确的,只是知其然,然而不知其所以然。TUT
今天q神来我校,教了我这个证明。
映射单个成环,如果替换一个i,所影响的是 i自己本身和 i环里面前面的一个值。

两个环

#include<cstdio>
#include<cstring>
#include<cstdlib>
#include<cmath>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int MAXN=100005;
int f[MAXN],g[MAXN];
struct node
{
    int l,r,m;
    ll v;
}s[MAXN<<2];
void push_up(int n)
{
    s[n].v=s[n<<1].v+s[n<<1|1].v;
}
void build(int l,int r,int n)
{
    int m=(l+r)>>1;
    s[n].l=l;
    s[n].r=r;
    s[n].m=m;
    if(r==l){
        s[n].v=f[f[l]];
        return;
    }
    build(l,m,n<<1);
    build(m+1,r,n<<1|1);
    push_up(n);
}
void update(int p,int v,int n) //把p位置 的值更新为v
{
    if(s[n].l==p && s[n].r==p)
    {
        s[n].v=v;
        return;
    }
    if(p<=s[n].m)update(p,v,n<<1);
    else update(p,v,n<<1|1);
    push_up(n);
}
ll query(int l,int r,int n)
{
    if(s[n].l==l && s[n].r==r)
        return s[n].v;
    if(r<=s[n].m)
        return query(l,r,n<<1);
    if(l>s[n].m)
        return query(l,r,n<<1|1);
    return  query(l,s[n].m,n<<1)+query(s[n].m+1,r,n<<1|1);
}
int main()
{
    //freopen("1.txt","r",stdin);
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,q;
        scanf("%d",&n);
        for(int i=1;i<=n;i++)
        {
            scanf("%d",&f[i]);  //f[i]  第i个数字为f[i];
            g[f[i]]=i;        // g[i]   数字i 的位置
        }
        build(1,n,1);
        scanf("%d",&q);
        while(q--)
        {
            int op,l,r;
            scanf("%d%d%d",&op,&l,&r);
            if(op==1)
            {
                swap(f[l],f[r]);    //先交换,f[l]=f[r] ,f[r]=f[l]
                //l位置的值为f[l], 所以代表的数字就是: f[f[l]] ;
                update(g[l],f[f[g[l]]],1);
                update(l,f[f[l]],1);//
                update(g[r],f[f[g[r]]],1);
                update(r,f[f[r]],1);
                swap(g[f[l]],g[f[r]]);
            }
            else
                printf("%lld\n",query(l,r,1));

        }
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值