Sticey_training#1[MEDIUM]

已补 4 / 5 4/5

CodeForces 1268C

题意

对于 n n 的排列, f ( i ) f(i) 表示求出通过交换相邻元素得到 1 1 ~ i i 的连续段的最小次数。
并输出 f ( i ) , i [ 1 , n ] f(i),i∈[1,n]

题解

如果是对于连续段相互交换得到最终阶段,显然答案是逆序对个数。
而这个实际情况是要把非连续转换成连续。
要求最小化 p o s 1 ( x k ) + p o s 2 ( x k + 1 ) + . . . + p o s k + 1 x + . . . + x + n ( k + 1 ) p o s i |pos_1-(x-k)|+|pos_2-(x-k+1)|+...+|pos_{k+1}-x|+...+|x+n-(k+1)-pos_i|
显然要挑选中位数。
根据上面的式子,可以维护 p o s pos 的和,判断和中位数的大小差。

而逆序对,因为每次插入的都是最大的,所以只用考虑插入位置过后已经插入多少个了。
这些都是可以在一个线段树里维护的。

#include<bits/stdc++.h>
using namespace std;

const int mod = 1e9+7;

const int maxn = 500050;
typedef long long ll;

struct tree2{
    tree2 *lson,*rson;
    ll x,cnt;
}dizhi[maxn<<2],*root=&dizhi[0];

int n,m,t=1,pos[maxn];
ll pre[maxn];

void push_up(tree2 *tree,int l,int r){
    tree->x=tree->lson->x+tree->rson->x;
    tree->cnt=tree->lson->cnt+tree->rson->cnt;
}

void build(tree2 *tree,int l,int r){
    tree->x=tree->cnt=0;
    if(l==r)return ;
    tree->lson=&dizhi[t++];
    tree->rson=&dizhi[t++];
    int mid=(l+r)>>1;
    build(tree->lson,l,mid);
    build(tree->rson,mid+1,r);
    push_up(tree,l,r);
}

void update(tree2 *tree,int l,int r,int pos){
    if(l==r){
      //  cout<<"?"<<l<<endl;
        tree->cnt=1;
        tree->x=pos;
        return ;
    }
    int mid=(l+r)>>1;
    if(pos<=mid)update(tree->lson,l,mid,pos);
    else update(tree->rson,mid+1,r,pos);
    push_up(tree,l,r);
}

ll query_pos(tree2 *tree,int l,int r,int x,int y){
    if(x<=l&&r<=y)return tree->x;
    int mid=(l+r)>>1;
    ll t1=0,t2=0;
    if(x<=mid)t1=query_pos(tree->lson,l,mid,x,y);
    if(y>mid)t2=query_pos(tree->rson,mid+1,r,x,y);
    return t1+t2;
}

int query_num(tree2 *tree,int l,int r,int x,int y){
    if(x<=l&&r<=y)return tree->cnt;
    int mid=(l+r)>>1;
    int t1=0,t2=0;
    if(x<=mid)t1=query_num(tree->lson,l,mid,x,y);
    if(y>mid)t2=query_num(tree->rson,mid+1,r,x,y);
    return t1+t2;
}

int query(tree2 *tree,int l,int r,int k){
    int mid=(l+r)>>1;
    if(l==r)return l;
    if(tree->lson->cnt>k)return query(tree->lson,l,mid,k);
    else return query(tree->rson,mid+1,r,k-(tree->lson->cnt));
}

int main(){
    cin>>n;
    for(int i=1;i<=n;i++){
        int x;scanf("%d",&x);
        pos[x]=i;
    }
    for(int i=1;i<=n;i++)pre[i]=pre[i-1]+1ll*i;
    build(root,1,n);
    ll now=0;
    for(int i=1;i<=n;i++){
        int p=pos[i];
        now+=1ll*query_num(root,1,n,p,n);//逆序对个数
        update(root,1,n,p);//更新
        int mid=query(root,1,n,i/2);;
        ll l=0,r=0;
        if(1<=mid-1)l=query_pos(root,1,n,1,mid-1);
        if(mid<=n)r=query_pos(root,1,n,mid,n);
        ll ans=1ll*mid*(i/2)-l+r-1ll*mid*(i-i/2);
        ans+=now;
        ans+=-pre[i/2]-pre[i-i/2-1];
        printf("%lld ",ans);
    }
    puts("");
}

CodeForces 1271E

题意

对于当 x % 2 = = 1 , x\%2==1, f ( x ) = x 1 f(x)=x-1 ;当 x % 2 = = 0 , f ( x ) = x 2 x\%2==0,f(x)=\frac{x}{2} .
对于一个 x x ,可以得到这样的序列直到等于 1 1 。得到序列为 { x , x 2 , . . . , . . . } \{x,\frac{x}{2},...,...\}
对于每个 i i 都有这样的序列,求出现次数大于等于 k k 的最大数。

题解

我们可以考虑每个数被多少个包括。
对于奇数:
x 2 x 2 x + 1 4 x 4 x + 1 4 x + 2 4 x + 3 . . . x、2x、2x+1、4x、4x+1、4x+2、4x+3、...
对于偶数:
x x + 1 2 x 2 x + 1 2 x + 2 2 x + 3 . . . x、x+1、2x、2x+1、2x+2、2x+3、...
显然都是满足 1 2 4 8 1、2、4、8 的。
最大值分别是 2 k x + 2 k 1 2^kx+2^k-1 2 k x + 2 k + 1 1 2^kx+2^{k+1}-1
第二个看着不明显,建议在往下推一个就很明了了。

显然这就是出现次比如数了,显然当出现次数刚好大于等于 k k 了,这个值肯定是最大的。
分别考虑奇偶,去计算出答案。
对于上述情况, x = 1 x=1 是不行的,所以需要特判(因为 x + 1 = 2 x x+1=2x )

推出来公式即可(保证最大值 - (前缀和 k - k ) n \leq n )
这样保证刚好最大,但不一定能保证奇偶性,所以需要特判。

#include<bits/stdc++.h>
using namespace std;

const int mod = 1e9+7;

const int maxn = 100+1;
const int up=61;
typedef long long ll;

ll odd[maxn],even[maxn];
ll sumodd[maxn],sumeven[maxn];
ll k,n;

ll sloveodd(){
    int x=1;
    for(x=1;x<=up;x++){
        if(sumodd[x]>=k)break;
    }
    ll ret=n+(sumodd[x]-k)-(odd[x]-1);
    ll t=ret/odd[x];
    if(t%2)return t;
    else{
        if((t+1)*odd[x]<=ret)return t+1;
        else if(t-1>=1)return t-1;
    }
}

ll sloveeven(){
    int x;
    for(x=1;x<=up;x++){
        if(sumeven[x]>=k)break;
    }
    ll ret=n+(sumeven[x]-k)-(even[x]-1);
    ll t=ret/(even[x]/2);
    if(t%2==0)return t;
    else{
        if((t+1)*(even[x]/2)<=ret&&t+1<=n)return t+1;
        else if(t-1>=1)return t-1;
    }
}

int main(){
    odd[1]=1;sumodd[1]=1;
    for(int i=2;i<=up;i++)odd[i]=odd[i-1]*2,sumodd[i]=sumodd[i-1]+odd[i];
    even[1]=2;sumeven[1]=2;
    for(int i=2;i<=up;i++)even[i]=even[i-1]*2,sumeven[i]=sumeven[i-1]+even[i];
    cin>>n>>k;
    if(k==n)puts("1");
    else{
        ll ans1=sloveodd();
        ll ans2=sloveeven();
        ll ans=max(ans1,ans2);
        cout<<ans<<endl;
    }
}

CodeForces 1280C

题意

一棵树,树上分配 2 k 2k 个人,每个人和另一个人有关系,且不会同时和别人有关系。问有关系的一对人之间的距离和最大最小为多少

题解

考虑每条边跑多少次。比如边 x x ,要求最大的话,就是尽量平均分配。
要求最小的话,就是尽量都放一边。
d f s dfs

#include<bits/stdc++.h>
using namespace std;
 
const int maxn = 200050;
 
typedef long long ll;
vector<pair<ll,int> >G[maxn];
int sz[maxn];
int t,k;
ll lans,rans;
 
void dfs(int u,int f){
    sz[u]=1;
    for(auto it:G[u]){
        int v=it.second;
        ll val=it.first;
        if(v==f)continue;
        dfs(v,u);
        sz[u]+=sz[v];
        lans+=1ll*min(2*k-sz[v],sz[v])*val;
        if(sz[v]%2&&(2*k-sz[v])%2)rans+=val;
    }
}
 
int main(){
    cin>>t;
    while(t--){
        scanf("%d",&k);
        for(int i=1;i<=2*k;i++)G[i].clear(),sz[i]=0;
        for(int i=1;i<=2*k-1;i++){
            int u,v;ll val;scanf("%d%d%lld",&u,&v,&val);
            G[u].push_back(make_pair(val,v));
            G[v].push_back(make_pair(val,u));
        }
        lans=0,rans=0;
        dfs(1,-1);
        printf("%lld %lld\n",rans,lans);
    }
 
}

CodeForces 514D

忘了…但是很明显就是五个线段树。卡卡常

发布了203 篇原创文章 · 获赞 17 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/mxYlulu/article/details/103706902