CF Manthan, Codefest 19 (open for everyone, rated, Div. 1 + Div. 2)(C D E F题解)

CodeForces,Manthan,Codefest19(部分题解CDEF)

题目链接

(比赛的时候,先是解了A,B,然后看C,中途一度想放弃,后来突然乱写一波过了,就去写D,感觉有思路,但是还是时间不够3题结束。没想到B题FST了)

C Magic Grid (瞎搞)

题意:

要求构造一个 n ∗ n n*n nn 的矩阵, n n n 4 4 4 的倍数,使得每一行,每一列的异或和都相等。

思路:

就是把样例的 4 ∗ 4 4*4 44 矩阵当作一个单元,一直去填充矩阵,每次只要在原矩阵单元加上 16 , 32 , 48... 16,32,48... 16,32,48... 就行了。

代码:

#include
using namespace std;
const int N=2000;
int n;
int arr[N][N];
int cl(int x,int y){
    return (x/4)*(n/4)+(y/4);
}
int a[4][4]={
    {8,9,1,13},
    {3,12,7,5},
    {0,2,4,11},
    {6,10,15,14}
};
int main()
{
    cin>>n;
    for(int i=0;i

D Restore Permutation (线段树)

题意:

给定一个数组 d [ i ] d[i] d[i] ,要求构造一个 1 − n 1-n 1n 的排列 S S S ,使得对于每一个 i i i 满足 d [ i ] = ∑ j S [ j ] ( 1 < = j < i & & S [ j ] < S [ i ] ) d[i]=\sum_{j}{S[j](1<=j<i\&\&S[j]<S[i])} d[i]=jS[j](1<=j<i&&S[j]<S[i]) .

思路:

一开始想用树状数组但是思路有点不清晰,后来队友说用线段树做,循环查找 1 − n 1-n 1n ,对于数字 i i i 查找 d [ i ] d[i] d[i] 数组中最右边的0的位置 p i p_i pi ,那么 S p i S_{p_i} Spi 就是为 i i i ,再将 d [ i ] d[i] d[i] 中的 [ p i − n ] [p_i-n] [pin] 减去 i i i 即可。

代码:

#include
#define ls x<<1
#define rs x<<1|1
#define ll long long
using namespace std;
const int N=2e5+10;
ll arr[N];
int n;
struct node{
    int l,r;
    ll sum,mi,f,siz;//区间维护最小值,用来判断0的位置,其实不用维护sum
}e[N*4];
void up(int x){
    e[x].mi=min(e[ls].mi,e[rs].mi);
    e[x].sum=e[ls].sum+e[rs].sum;
}
void down(int x){
    if(e[x].f==0)return ;
    ll f=e[x].f;
    e[ls].f+=f;e[rs].f+=f;
    e[ls].mi+=f;e[rs].mi+=f;
    e[ls].sum+=e[ls].siz*f;e[rs].sum+=e[rs].siz*f;
    e[x].f=0;
}
void built(int x,int l,int r){
    e[x].l=l;e[x].r=r;e[x].siz=(r-l+1);e[x].f=0;
    if(l==r){
        e[x].mi=e[x].sum=arr[l];
        return ;
    }
    int mid=(l+r)/2;
    built(ls,l,mid);built(rs,mid+1,r);
    up(x);
}
void adds(int x,int pos,ll v){//对于已经确定的位置,由于维护最小值,所以加上一个大数
    if(e[x].l==e[x].r){
        e[x].mi=e[x].sum=v;
        return ;
    }
    down(x);
    int mid=(e[x].l+e[x].r)/2;
    if(pos<=mid)adds(ls,pos,v);
    else adds(rs,pos,v);
    up(x);
}
void addr(int x,int LL,int RR,ll v){//区间加
    if(LL>RR)return ;
    if(e[x].l>=LL&&e[x].r<=RR){
        e[x].sum+=e[x].siz*v;e[x].mi+=v;
        e[x].f+=v;
        return ;
    }
    down(x);
    int mid=(e[x].l+e[x].r)/2;
    if(LL<=mid)addr(ls,LL,RR,v);
    if(RR>mid)addr(rs,LL,RR,v);
    up(x);
}
int query(int x){
    if(e[x].l==e[x].r)return e[x].l;
    down(x);
    if(e[rs].mi==0)return query(rs);
    return query(ls);
}
int debug(int x,int pos){
    if(e[x].l==e[x].r)return e[x].mi;
    down(x);
    int mid=(e[x].l+e[x].r)/2;
    if(pos<=mid)return debug(ls,pos);
    else return debug(rs,pos);
}
int t[N];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++)scanf("%lld",&arr[i]);
    built(1,1,n);
    for(int i=1;i<=n;i++){
        int p=query(1);//cout<

E Let Them Slide (ST表 + 差分)

题意:

给定一个 n ∗ m n*m nm 的矩阵,每一行有一个长度为 l e n i len_i leni 的数组 a i a_i ai ,可以左右移动,问通过数组的移动可以使得每一列的数字之和是多少(每一列单独计算),可以移出矩阵,此时贡献为 0 0 0 ( − 1 e 9 < = a i < = 1 e 9 ) (-1e9<=a_i<=1e9) (1e9<=ai<=1e9)

思路:

首先在纸上模拟一个数组在矩阵中左右移动,可以发现对于矩阵的某一列 j j j ,对于第 i i i 个数组中可以移动到 j j j 列的范围 [ L , R ] [L,R] [L,R] 是确定的,那么这个数组对这一列的贡献就是 m a x ( a [ i ] , L < = i < = R ) max(a[i],L<=i<=R) max(a[i],L<=i<=R) ,但是如果对于每一个数组都扫一遍要 n ∗ m n*m nm 的复杂度必然超时,注意到 ∑ ( l e n i ) < = 1 e 6 \sum(len_i)<=1e6 (leni)<=1e6 。那我们对于 l e n i ∗ 2 > = m len_i*2>=m leni2>=m 的数组,可以直接扫一遍,但是对于 l e n i ∗ 2 < m len_i*2<m leni2<m 的情况,我们其实可以在纸上发现规律,从前往后,对于第 j   ( j < = l e n i ) j\ (j<=len_i) j (j<=leni) 列,能产生贡献的必然是 [ 1 , j ] [1,j] [1,j] 从后往前也是类似,对于中间的部分发现他们的贡献区间都相同故使用差分数组维护区间加即可避免遍历所有列。

代码:

#include
#define ll long long
using namespace std;
const int N=2e6+10;
int n,m;
int st[N][21];
ll ans[N];
void cl(int x){//处理ST表
    for(int i=1;i<=20;i++)
    for(int j=1;j<=x;j++)st[j][i]=max(st[j][i-1],st[j+(1<<(i-1))][i-1]);
}
int mi(int l,int r){//查询区间最大值
    int k=log2(r-l+1);
    return max(st[l][k],st[r-(1<=m){//第一种情况
            for(int j=1;j<=m;j++){
                int l=len-m+j,r=j;int v=mi(max(l,1),min(len,r));//确定可用的区间
                if(l<1||r>len)v=max(v,0);//防止负数的情况
                ans[j]+=v;//差分
                ans[j+1]-=v;
            }
        }else{
            int v1=0,v2=0;
            for(int j=1;j<=len;j++){
                v1=max(v1,st[j][0]);//前k个
                v2=max(v2,st[len-j+1][0]);//后k个
                ans[j]+=v1;ans[j+1]-=v1;
                ans[m-j+1]+=v2;ans[m-j+2]-=v2;
            }
            ans[len+1]+=v1;//中间的部分都是同样的贡献
            ans[m-len+1]-=v1;
        }
    }
    for(int i=1;i<=m;i++){
        ans[i]+=ans[i-1];//前缀和
        printf("%lld ",ans[i]);
    }
    puts("");
}

F (位运算 + 贪心)

题意:

给定一个数组,要求 m a x   ( a i   ∣   ( a j & a k )   )   (   i < j < k   ) max\ (a_i\ |\ (a_j \&a_k)\ )\ (\ i<j<k\ ) max (ai  (aj&ak) ) ( i<j<k )

思路:

从后往前对于每一个 a i a_i ai 考虑其最大的贡献。因为是与操作,我们就是要贪心的使 a i a_i ai 中的 0 0 0 变成 1 1 1 ,我们就记录 a i + 1 − a n a_{i+1}-a_n ai+1an 所能有的 1 1 1 的集合,如果一个位置上 1 1 1 出现两次,那么我们就可以将这两个数当作 a j , a k a_j,a_k aj,ak 从而使得 a i a_i ai 这一位为1,就这样不断更新答案即可。

代码:

#include
using namespace std;
const int N=(1<<21);
int cnt[N],vis[N];
int n;
int a[N];
void ins(int x,int time){//更新当前的可用数,time为时间戳
    if(cnt[x]==2||vis[x]==time)return ;
    cnt[x]++;vis[x]=time;
    for(int i=20;i>=0;i--){
        if((x>>i)&1){
            ins(x^(1<=1;i--){//从后往前
        int s=0;
        for(int j=20;j>=0;j--){//从大到小贪心
            if(((a[i]>>j)&1)==0){
                if(cnt[s|(1<

by YaoYaoLe

你可能感兴趣的:(Codeforces,贪心,数据结构)