Codeforces Global Round 9 E.Inversion SwapSort(冒泡排序性质)/F.Integer Game(构造)/G.Tree Modification(二分图染色)

思路来源

CF用户disangan233代码、tourist代码、官方题解等

https://www.cnblogs.com/dysyn1314/p/13246526.html

心得

赛中D神奇的mex构造做了1.5h就tm离谱,后续发现CDEFG都是思维(构造)题

被思维题搞死了GG

E.Inversion SwapSort(冒泡排序性质)

给你一个长度n(n<=1e3)的数组a[],找出该序列里所有的逆序对下标对(i,j),

只能交换这些下标(i,j)的数,每对恰交换一次,

首先输出逆序对数x,然后输出x个逆序对的交换顺序,使得交换后的序列单增(非严格)

注意,发生交换了之后,后续也只能用那些在原序列a[]里是逆序对的下标对(i,j)

题解

搞一发tourist的题解,感觉比另外那个做法简洁多了,另外那个就不提了

考虑n个数两两不同,即是一个1-n的排列时怎么做,

如果i+1和i构成逆序对,则将其位置交换,

此时对于<=i-1的数u和>=i+1的数v来说,

(i,u)若之前构成构成逆序对,则(i+1,u)仍然构成逆序对,

(i+1,u)若之前构成构成逆序对,则(i,u)仍然构成逆序对,

(v,i+1)若之前构成逆序对,则(v,i)仍然构成逆序对

(v,i)若之前构成逆序对,则(v,i+1)仍然构成逆序对

由于u和v的位置没变,原先的逆序对(v,u)仍然不变

所以,其他逆序对的位置对没有受到影响,只减少了(i+1,i)这一对

于是可以按冒泡排序的交换次数=逆序对数量的思想,一对一对的交换回去

 

考虑存在有数相同时怎么做,

不妨认为值相同的时候,前面的rank小,这样就形成了不同的rank,

排名i+1的位置,如果在排名i的位置的前面,就交换,否则忽略

值相同时,认为前面的排名小,这样就不会出现交换(v,v)这一非逆序对的情形了

所以,构造数组sa[i]表示排名为i的值的下标,冒泡排序换即可

 

为什么一定会出现形如相邻值(排名)的逆序对,即形如(i+1,i)的逆序对,

不妨设存在(i+x,i)的逆序对,

①若i+1在i+x后面,构成(i+x,i+1)的逆序对,然后递归考虑(i+x,i+1);

②若在i+x前,构成(i+1,i)的逆序对,成立

代码

#include<bits/stdc++.h>
using namespace std;
const int N=1e3+10,M=N*N/2;
typedef pair<int,int> P;
#define fi first
#define se second
int n,a[N],sa[N],c;
P ans[M];
int main(){
    scanf("%d",&n);
    for(int i=1;i<=n;++i){
        scanf("%d",&a[i]);
        sa[i]=i;
    }
    sort(sa+1,sa+n+1,[](int i,int j){return a[i]==a[j]?i<j:a[i]<a[j];});
    for(int j=1;j<=n;++j){//第j轮 只考虑[1,n-j]
        for(int i=1;i<=n-j;++i){
            if(sa[i+1]<sa[i]){
                swap(sa[i],sa[i+1]);
                ans[++c]=P(sa[i],sa[i+1]);
            }
        }
    }
    printf("%d\n",c);
    for(int i=1;i<=c;++i){
        printf("%d %d\n",ans[i].fi,ans[i].se);
    }
    return 0;
}

F. Integer Game(构造)

交互题,给出三个不同整数a,b,c(1<=a,b,c<=1e9),代表初始时第一、二、三堆石子的数量

你可以选择先手还是后手,若先手输出一行First,若后手输出一行Second

 

先手的人,每次给出一个值v(1<=v<=1e12),电脑会随便选一堆,并返回这堆的堆号i(1<=i<=3),

代表向第i堆加上v个石子,特别地,电脑这一轮选择的堆号,不能与上一轮相同

如果你能使得,在1000轮以内的某一轮,存在两堆石子数量相同,则你获胜

 

后手的人,反过来,如果你能在1000轮内不败,则获胜,

 

获胜时,系统会返回一个0,你读入完之后退出程序就代表AC了,

中途如果有非法输入或超过1000轮,系统返回一个-1,代表你WA了

题解

任意时刻加完石子之后都排序,e[0].v<=e[1].v<=e[2].v

第一轮加上2e9个石子,这样无论哪一堆分到,加上后的石子都是最多的一堆,即e[2]

第二轮加上e[2].v*2-e[1].v-e[0].v个石子,这样无论加在e[0]上还是e[1]上,三者都构成等差数列

以e0为例,现在e[0]比e[2]多的,正是e[2]比e[1]多的,且e[0]是最多的,然后再排一次序

第三轮由于是a,a+x,a+2x的局面,且上一轮a+2x被使用过,这一轮直接加上x个石子即可获胜

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define sci(a) scanf("%d",&(a))
typedef long long ll;
int v;
struct node{
    ll v,id;
}e[3];
bool operator<(node a,node b){
    return a.v<b.v;
}
void ask(ll v){
    printf("%lld\n",v);
    fflush(stdout);
    int id;
    scanf("%d",&id);id--;
    for(int i=0;i<3;++i){
        if(e[i].id==id){
            e[i].v+=v;
            break;
        }
    }
    sort(e,e+3);
}
int main(){
    for(int i=0;i<3;++i){
        scanf("%lld",&e[i].v);
        e[i].id=i;
    }
    puts("First");
    fflush(stdout);
    ask(2e9);
    ask(e[2].v*2-e[1].v-e[0].v);
    ask(e[1].v-e[0].v);
    scanf("%d",&v);
    return 0;
}

G. Tree Modification(二分图染色)

n(3<=n<=2e5)个节点的树,

每次操作,你可以选择树上相邻的三个点a,b,c,要求b和a相邻,b和c相邻,

①对于所有与a相邻的节点d(忽略b节点),把d与a之间的边删除,将d与c之间连边

②把a与b之间的边删除,把a连到c上

这个操作可能看原题的图会好理解一点,

原题链接:https://codeforces.com/contest/1375/problem/G

问多少次操作之后,原树可以构成一个菊花图(原文称为星型),

即中心一个点为根,剩下的n-1个点都是叶子

题解

考虑将原树按二分图黑白染色,不妨设b为黑色,则ac均为白色,

a连接的所有d也为黑色,连到c上仍应为黑色,保持不变

而a连接到c上时,由原来的白色转变为黑色,这说明每次操作恰改变一个点的颜色

最后的星型,只要留一个白或留一个黑,

所以答案是min(黑,白)-1,代表把这么多的颜色都转成另一种

考虑满足这个最小值的情况下,如何构造方案:

只要一种颜色还有两个,说明一定可以放到a、c的位置,这样本轮操作后这种颜色就少了一个

代码

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define pb push_back
#define rep(i,a,b) for(int i=(a);i<=(b);++i)
#define sci(a) scanf("%d",&(a))
typedef long long ll;
const int N=2e5+10;
int n,u,v,cnt[2];
vector<int>e[N];
void dfs(int u,int fa,int c){
    cnt[c]++;
    for(int v:e[u]){
        if(v==fa)continue;
        dfs(v,u,1-c);
    }
}
int main(){
    sci(n);
    rep(i,2,n){
        sci(u),sci(v);
        e[u].pb(v);e[v].pb(u);
    }
    dfs(1,-1,1);
    printf("%d\n",min(cnt[0],cnt[1])-1);
    return 0;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Code92007

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值