NOIP2017 赛前模拟(2017.10.6)

本次考试三道题的难度适中,T1没有认真分析,把情况考虑全面,就炸了。T2的话主要是二分的check()写错了;T3是异或的套路题然而我并不知道这个套路

T1 :
题目描述:
给出 n 个数。请找出一个排列使得相邻两个数的差的绝对值的和最大。请求出这个最大值。
题解:
通过样例数据我们可以发现:
n为偶数时,只存在一种情况 ,对于一个数列 1 4 2 3 ,ans=-1+4+4-2-2+3
n为奇数时,存在两种情况,分别是将最大的数放在中间,将最小的数放在中间,例:
对于数列 1 5 2 4 3 当前的 ans=-1+5+5-2-2+4+4-3
对于数列 4 2 5 1 3 当前的 ans=+4-2-2+5+5-1-1+3
我们不难发现,对于两种情况我们分别把小的一半中的最大的两个放两边为最优解,和把大的一般的最小的两个数放两边为最优解,最后在取max就行了

#include<cstdio>
#include<algorithm>
#include<iomanip>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<ctime>
#include<cctype>
using namespace std;
int n,sum,T,a[60],sum0,sum1;
//---------------------
inline int Readint(){
    int i=0,f=1;char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) i=(i<<1)+(i<<3)+ch-'0';
    return i*f;
}
//---------------------
int main(){
    //freopen("sequence.in","r",stdin);
    //freopen("sequence.out","w",stdout);

    T=Readint();
    for(int i=1;i<=T;i++){
        cout<<"Case "<<i<<": ";
        memset(a,0,sizeof(a));
        sum=0;
        n=Readint();
        for(int j=1;j<=n;j++) a[j]=Readint();
        sort(a+1,a+1+n);
        if(n==1) cout<<sum<<endl;
        else {
            if(n%2==0){
                sum0=a[n/2],sum1=a[n/2+1];
                for(int i=1;i<n/2;i++) sum0+=2*a[i];
                for(int i=n/2+2;i<=n;i++) sum1+=2*a[i];
                sum=sum1-sum0;
            }
            else{
                sum0=0,sum1=a[n/2+1]+a[n/2+2];
                for(int i=1;i<=n/2;i++) sum0+=2*a[i];
                for(int i=n/2+3;i<=n;i++) sum1+=2*a[i];
                sum=sum1-sum0;

                sum1=0,sum0=a[n/2]+a[n/2+1];
                for(int i=1;i<n/2;i++) sum0+=2*a[i];
                for(int i=n/2+2;i<=n;i++) sum1+=2*a[i];
                sum=max(sum,sum1-sum0);
            }
            cout<<sum<<endl;
        }
    }
    return 0;
}

T2:
题目描述:
今天是ABC的生日,他制作了一个巧克力蛋糕!你可以把它理解成一个有 R×C 个小格子组成的矩形。每个格子上都有一些巧克力 chips ,第 i 行,第 j 列的格子上有 A[i][j] 个巧克力 chips 。

有 A×B 个人要出席 ABC 的生日晚会(包括ABC自己),每个人都想得到一块蛋糕。于是他想要把他的蛋糕切成 A×B 个小块。首先,他先横着切(A-1)刀,蛋糕就变成了 A 条,然后,对于每一条,他都纵着切(B-1)刀,就的到了(A×B)个小块。为了体现自己的大度, ABC 决定最后选蛋糕,而 ABC 的朋友就不会这么想了,他们总是拿走巧克力最多的蛋糕,所以,ABC 得到的蛋糕永远是巧克力最少的那一块。

聪明的 ABC 很喜欢吃巧克力,他想得到更多的巧克力,但是他正忙着准备自己的生日晚会,于是他来寻求你的帮助,希望你不要让他失望。

题解:
题目看完,正常人都能看出是二分,而且都知道直接贪心check即可,然而考试的时候我并没有想出这个贪心的方法..

回到正题,由题意可知,每次横着切是必定会把蛋糕彻底切开,所以我们贪心的时候只需要对于每一横排,看它能不能被切成B块,如果不能的话,再加上下一排,最后再看看排数有没有A 即可

#include<cstdio>
#include<algorithm>
#include<iomanip>
#include<iostream>
#include<cstring>
#include<string>
#include<cmath>
#include<ctime>
#include<cctype>
using namespace std;
int n,m,a,b,val[520][520];
long long L,R,tmp[520];
//--------------------- 
inline int Readint(){
    int i=0,f=1;char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) i=(i<<1)+(i<<3)+ch-'0';
    return i*f;
}
//---------------------
inline bool check(long long x){
    memset(tmp,0,sizeof(tmp));
    int cnt0=0,cnt1,w;
    for(int i=1;i<=n;i++){
        cnt1=w=0;
        for(int j=1;j<=m;j++) tmp[j]+=val[i][j];
        for(int j=1;j<=m;j++){
            w+=tmp[j];
            if(w>=x) cnt1++,w=0;
        }
        if(cnt1>=b){
            memset(tmp,0,sizeof(tmp));
            cnt0++;
        }
        if(cnt0>=a) return true;
    }
    return cnt0>=a;
}
//---------------------
int main(){
    //freopen("cut.in","r",stdin);
    //freopen("cut.out","w",stdout);

    n=Readint(),m=Readint(),a=Readint(),b=Readint();
    for(int i=1;i<=n;i++){
        for(int j=1;j<=m;j++){
            val[i][j]=Readint();
            R+=val[i][j];
        }
    }
    while(L<=R){
        long long mid=(L+R)>>1;
        if(check(mid)) L=mid+1;
        else R=mid-1;
    }
    cout<<R<<endl;
    return 0;
}

T3:
题目描述:
求一棵带边权的树的一条最大 Xor 路径的值。这里的“路径”不一定从根到叶子结点,中间一段路径只要满足条件也可以。

其实这道题就是一个在一个二进制tri树上的贪心,前提是我考试的时候要先知道Xor路径是什么意思。
首先, Xor路径就是路径上的每条边的长度都与前面的值异或一下的结果。
然后,你要知道 a^b^a=b 得到,a^b^b=a;
所以对于树上的任意两点间的路径的异或值,等于他们到根节点的异或值再异或一下,因为lca以上的部分被抵消了;
当这些都已经知道了,就只剩下了tri树上的套路了:
将每个点到根节点的异或路径的值都用二进制加入到tri树中,由异或的性质我们可以知道,要想两条路径的异或值最大,对于一条已知的路径,我们只需要在tri树上从根节点开始出发,每次尽量找以当前位不同的数继续走(比如当前是1,就找0,反之亦然)

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<string>
#include<cmath>
#include<ctime>
#include<iomanip>
#include<iostream>
#include<cctype>
using namespace std;
//---------------------
const int N = 100005,L=35;
struct node{
    int son[2];
}tri[N*L];
int n,len,mx;
int tot,first[N],to[N<<1],next[N<<1],val[N<<1];
int dis[N];
//---------------------
inline int Readint(){
    int i=0,f=1;char ch;
    for(ch=getchar();(ch<'0'||ch>'9')&&ch!='-';ch=getchar());
    if(ch=='-') f=-1,ch=getchar();
    for(;ch>='0'&&ch<='9';ch=getchar()) i=(i<<1)+(i<<3)+ch-'0';
    return i*f;
}
//---------------------
inline void add(int x,int y,int z){
    next[++tot]=first[x];first[x]=tot;
    to[tot]=y;val[tot]=z;
}
//---------------------
inline void dfs(int now,int fa){
    for(int e=first[now];e;e=next[e]){
        int v=to[e];
        if(v!=fa){
            dis[v]=dis[now]^val[e];
            dfs(v,now);
        }
    }
}
//---------------------
inline void Insert(int val){
    int po=0;
    for(int i=len-1;i>=0;i--){
        int t=(val>>i)&1;
        if(!tri[po].son[t]) tri[po].son[t]=++tot;
        po=tri[po].son[t];
    }
}
//---------------------
inline int find(int val){
    int po=0;
    for(int i=len-1;i>=0;i--){
        int t=(val>>i)&1;
        if(tri[po].son[t^1]) po=tri[po].son[t^1],val|=(1<<i);
        else po=tri[po].son[t],val^=(t<<i);
    }
    return val;
}
//---------------------
int main(){
//  freopen("xor.in","r",stdin);

    int x,y,z;
    n=Readint();
    for(int i=1;i<n;i++){
        x=Readint(),y=Readint(),z=Readint();
        mx=max(mx,z);
        add(x,y,z); add(y,x,z);
    }

    dfs(1,0);

    while(mx) mx>>=1,len++;
    tot=0;
    for(int i=1;i<=n;i++) Insert(dis[i]);
    for(int i=1;i<=n;i++){
        dis[i]=find(dis[i]);
        mx=max(mx,dis[i]);
    }
    cout<<mx<<endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值