为班内准备机考出的一套模拟题
#题目链接
#题意简述
若干个数中,有且仅有一个数字出现了一次,其余均出现两次。找到那个出现了一次的数。
#简要做法
观察到每个数均小于$1500$,我们可以用数组统计每个数字出现的次数。统计完后$O(n)$复杂度从小到大扫一遍,找到次数为一的数字,输出答案,退出循环。
#参考代码
 | 
int n; | 
int a[1505]; | 
int main() { | 
    scanf("%d" , &n); | 
    int t , mx = 0; | 
    for(int i = 1; i <= n; ++i) { | 
        scanf("%d" , &t); | 
        if(t > mx) mx = t;//找到最大的数字就够了 | 
        a[t]++; | 
    } | 
    for(int i = 1; i <= mx; ++i) | 
        if(a[i] == 1) { | 
            printf("%d\n" , i); | 
            break; | 
        } | 
    return 0; | 
} | 
想要更简单的做法?那我们首先需要学习异或的几条性质:
*异或是一种基于二进制的位运算,用符号XOR或者 ^ 表示,其运算法则是对运算符两侧数的每一个二进制位,同值取0,异值取1。它与布尔运算的区别在于,当运算符两侧均为1时,布尔运算的结果为1,异或运算的结果为0。*按位异或便是将数字转为二进制后右对齐,每一位进行异或运算。位数不同时则位数少的前面补零。
由按位异或的性质,我们不难发现:
- 0 ^ x = x , x ^ x = 0
 - a ^ b = b ^ a
 - a ^ b ^ b = a ^ 0 = a
 
由此性质,本题出现两次的数字均可由异或运算化为0,从而直接得解。
#参考代码1
 | 
int n; | 
int main() { | 
    scanf("%d",&n); | 
    int ans = 0 , t; | 
    for(int i = 1; i <= n; ++i) { | 
        scanf("%d" , &t); | 
        ans ^= t; //异或运算 | 
    } | 
    printf("%d\n" , ans); | 
    return 0; | 
} | 
#题目链接
B: [T113497 wxl的午餐
#简要做法
将吃饭时间(即$b_{i}$)按从大到小排序,求出最长的吃完时间。
该做法基于贪心的思想,下面给出证明:
 我们不难发现,所有人吃完饭的最早时刻即为max{$b_i + \sum_{k=1}^ia_k$}。现在考虑相邻的两个人p、q。显然,是否调换p、q的顺序对于他们前面和后面的人的吃完饭时间均无影响。不妨设$b_p > b_q$,则有:
p在q前:
 $time_p = () + a_p + b_p$
 $time_q = () + a_p + a_q + b_q$
q在p前:
 $time_{q1} = () + a_q + b_q$
 $time_{p1} = () + a_q + a_p + b_p$
四项中的最大值显然出现在“q在p前”,因此要尽可能将b值大的往前放。
#参考代码
//std provided by yyn | 
 | 
 | 
int a[1005],b[1005],n; | 
int main() | 
{ | 
    int n; | 
    scanf("%d",&n); | 
    int i,ii; | 
    for(i=1;i<=n;i++) | 
    { | 
        scanf("%d%d",&a[i],&b[i]); | 
    } | 
    int ans=0,tmp=0; | 
    for(i=1;i<=n;i++) | 
    { | 
        int t=1; | 
        for(ii=2;ii<=n;ii++) | 
        { | 
            if(b[ii]>b[t])t=ii; | 
        } | 
        tmp+=a[t]; | 
        if(tmp+b[t]>ans)ans=tmp+b[t]; | 
        b[t]=-1; | 
    } | 
    printf("%d\n",ans); | 
    return 0; | 
} | 
UPD: 今天脑袋抽了,抄了个错误方法,大家忘掉就好。。。这个错误方法正确主要是我数据出水了(
求解正确答案是离不开排序的
#题目链接
C:[T112898 wxl的括号编辑器
#简要做法
基本就是模拟,题目说什么我们就做什么。记录好当前光标的位置,然后对相应位置进行修改即可。修改答案字符串的同时,在相同位置打好标记,以便于后续判断括号序列是否合法。
#参考代码
 | 
 | 
char ans[1005] , s[1005]; | 
int a[1005] , n; | 
int main() { | 
    scanf("%d%s" , &n , s); | 
    int pos = 0; | 
    for(int i = 0; i < n; ++i) { | 
        if(s[i] == 'L' && pos - 1 >= 0) pos--; | 
        else if(s[i] == 'R') pos++;  | 
        else if(s[i] == '(') { | 
            ans[pos] = '('; | 
            a[pos] = 1; | 
        } | 
        else if(s[i] == ')') { | 
            ans[pos] = ')'; | 
            a[pos] = -1; | 
        } | 
    } | 
    printf("%s\n" , ans); | 
    int l = strlen(ans); | 
    int sum = 0; | 
    for(int i = 0; i < l; ++i) { | 
        sum += a[i]; | 
        if(sum < 0) { | 
            printf("No\n"); | 
            return 0; | 
        } | 
    } | 
    if(sum == 0)	 | 
        printf("Yes\n"); | 
    else printf("No\n"); | 
    return 0; | 
} | 
#题目链接
#题意简述
找到最小的整数x,使得$x^2/a + \sqrt{x} >= n$。
#简要做法
首先注意到$n,a$均很大,因此答案可能会很大,需要用long long进行运算,否则会爆int(涉及平方运算时)。
50PTS做法:
 从小到大枚举,找到第一个满足条件的值,将其输出。时间复杂度$O(tn)$
#参考代码
 | 
 | 
 | 
ll t , n , a; | 
int main() { | 
    scanf("%lld" , &t); | 
    while(t--) { | 
        scanf("%lld%lld" , &n , &a); | 
        ll v; | 
        for(ll i = 0; i <= n; ++i) { | 
            v = i * i / a + sqrt(i); | 
            if(v >= n) { | 
                printf("%lld\n" , i); | 
                break; | 
            } | 
        } | 
    } | 
    return 0; | 
} | 
100PTS做法:
 线性复杂度太慢,观察到趋势是单调的,我们可以用二分查找这个答案。时间复杂度$O(t\log_2n)$。
#参考代码
 | 
 | 
int t; | 
long long n , a; | 
int check(long long x) { | 
    long long tmp = sqrt(x) + x * x / a; | 
    return tmp >= n; | 
} | 
int main() { | 
    scanf("%d" , &t);  | 
    while(t--) { | 
        scanf("%lld%lld" , &n , &a); | 
        long long l = 0 , r = n , ans; | 
        while(l <= r) { | 
            //long long mid = l + r >> 1; | 
            long long mid = (l + r) / 2; | 
            if(check(mid)) { | 
                ans = mid; | 
                r = mid - 1; | 
            } | 
            else l = mid + 1; | 
        } | 
        printf("%lld\n" , ans); | 
    } | 
    return 0; | 
} | 
#题目链接
#简要做法
首先注意到计算得到的数字可能会超出int范围,因此用long long储存变量数值。
70PTS:
 对于每一次询问,用循环计算出答案,时间复杂度$O(mn)$。
#参考代码
 | 
 | 
const ll mod = 1e9 + 7; | 
ll a[30005]; | 
int n , m; | 
int main() { | 
    scanf("%d%d" , &n , &m); | 
    for(int i = 1; i <= n; ++i) | 
        scanf("%lld" , &a[i]); | 
    ll l , r; | 
    for(int i = 1; i <= m; ++i) { | 
        scanf("%lld%lld" , &l , &r); | 
        ll sum = 0; | 
        for(int j = l; j <= r; ++j) { | 
            sum = (sum + (j - l + 1) * a[j]) % mod; | 
        } | 
        printf("%lld\n" , sum); | 
    } | 
    return 0; | 
} | 
100PTS做法:
 我们想到对于每一次询问,我们都要对左右区间内的元素进行一次运算,但是这样想必是很浪费时间的。我们能否想到一个方法,使得将这个过程优化到$O(1)$的复杂度呢?
 要实现这样的目的,我们首先介绍前缀和的概念:
 我们定义前缀和$s_i=\sum_{k=1}^ia_k$,这样,想求$\sum_{i=l}^ra_i$,便可直接用$s_r-s_{l-1}$得到,时间复杂度为$O(1)$。
//前缀和实现 | 
s[0] = 0; | 
for(int i = 1; i <= n; ++i) | 
    s[i] = s[i - 1] + a[i]; | 
介绍完前缀和,我们如何达成本题的目的呢?
展开
$\sum_{i=l}^ra_i \cdot (i - l + 1)$为
$a[l]\cdot1 + a[l+1]\cdot2+……+a[r-1]\cdot(r-l)+a[r]\cdot(r-l+1)$
$=(s[l]-s[l-1])+..+(s[r-1]-s[r-2])\cdot(r-l)+(s[r]-s[r-1])\cdot(r-l+1)$
$=-(s[l-1]+s[l]+s[l+1]+…..+s[r-1])+(r-l+1) \cdot s[r]$
到这一步我们发现我们可以对s数组进行求和,于是再计算s的前缀和e
所求$=(r-l+1) \cdot s[r]-(e[r-1]-e[l-2])$
#参考代码
 | 
 | 
const int mod = 1e9 + 7; | 
int n , m; | 
ll a[30005] , s[30005] , e[30005]; | 
int main() { | 
    scanf("%d%d" , &n , &m); | 
    for(int i = 1; i <= n; ++i) { | 
        scanf("%lld" , &a[i]); | 
        s[i] = s[i - 1] + a[i];//a的前缀和 | 
        e[i] = e[i - 1] + s[i];//s的前缀和 | 
    } | 
    ll l , r; | 
    for(int i = 1; i <= m; ++i) { | 
        scanf("%lld%lld" , &l , &r); | 
        ll ans = (((r - l + 1) * s[r]) % mod - (e[r - 1] - e[l - 2]) % mod + mod) % mod;//由于都进行了取模,减法可能会减出负数,因此要加上1个mod再取模 | 
        printf("%lld\n" , ans); | 
    } | 
    return 0; | 
} |