省选模拟测试11

期望得分:(???+0+??? = ???)

实际得分:(80+0+20=100)

(T1) 想了两个多小时才想出来了假的贪心的做法,没想到拿了 (80pts)

(T2) 考试的时候打了个区间修改的主席树,到考试结束也没调出来。

(T3) 神仙题,打了爆搜的分,剩下的随机大法(随机化没有前途)。

T1 游戏

题目描述

CF798D Mike and distribution

给两个长度为 (n) 的数列 (A,B),要求至多选择 (n/2+1) 个下标,使得 (A) 数组中选出的数的和的两倍大于 (sumA)(B) 数组中选出的数的和的两倍大于 (sumB)

数据范围:(nleq 10^5,a_i,b_ileq 10^9)

solution

构造加贪心。

我们考虑怎么构造出一种方案出来。

首先先把所有的下标,按 (a_i) 从大到小排序,先把第 (1) 个数选上,然后在剩下的 (n-1) 个下标中选 ({nover 2}) 个。

之后把剩下的 (n-1) 个数两两分为一组,每一组里面选 (b_i) 较大的即可。

为什么这么构造是对的呢?证明如下:

首先有: (|a_2 - a_3| + |a_4-a_5| + |a_6-a_7| + ... + |a_{n-1}-a_{n}| < a_1)

可以把 (a_1) 抽象为长度为 (a_1) 的一个线段,(|a_i-a_{i+1}|) 相当于每次取出线段中的一小部分,可以发现无论怎么取,都会剩下一部分。

根据这个我们可以发现,最坏的情况下,每一组都取 (a_i) 较小的那一个也是符合 (A) 数组中选出来的数的两倍大于 (sumA) 这个条件的。

另外一个条件 (B) 数组中选出的数的和的两倍大于 (sumB) ,我们可以转化为选了的 (b_i) 之和大于没选的 (b_i) 之和。

又因为我们每一组都是选 (b_i) 较大的那一个,所以第二个条件也被满足了。

update:(T1) 好像随机化贪心就能过,自己的那种写法多贪几遍就过了。

代码实现起来很简单,注意开 (long long)

#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
#define int long long
const int N = 1e5+10;
int n,m,tota,totb,suma,sumb,cnt;
struct node
{
    int a,b,w,id;
}e[N],b[N];
inline int read()
{
    int s = 0,w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    return s * w;
}
bool comp(node a,node b)
{
	if(a.a == b.a) return a.id < b.id;
    return a.a > b.a;
}
signed main()
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
    n = read(); m = (n/2)+1;
    for(int i = 1; i <= n; i++) e[i].a = read();
    for(int i = 1; i <= n; i++) e[i].b = read();
    for(int i = 1; i <= n; i++)
    {
        e[i].id = i;
        suma += e[i].a;
        sumb += e[i].b;
    }
    sort(e+1,e+n+1,comp);
    printf("%lld
",(n/2)+1);
    printf("%lld ",e[1].id);
    for(int i = 2; i <= n; i += 2)
    {
    	if(e[i].b > e[i+1].b) printf("%lld ",e[i].id);
    	else printf("%lld ",e[i+1].id);
	}
    fclose(stdin); fclose(stdout);
    return 0;
}

T2 现实

题目描述

CF707D Persistent Bookcase

维护一个二维零一矩阵,支持四种操作 :

  • ((i,j)) 置一
  • ((i,j)) 置零
  • 将第 (i) 行零一反转
  • 回到第 (K) 次操作前的状态
  • 每次操作后输出全局一共有多少个一

数据范围:(n,mleq 1000), 操作数 (qleq 10^5)

solution

可持久化主席树好像可以做的样子,但是毒瘤出题人空间只给了 (256MB) ,那我们只能去想另外的办法了。

首先我们可以建一棵操作树,具体来说就是对于第 (i) 个操作,如果操作类型为 (1,2,3) 则由 (i-1)(i) 连一条边。

对于操作 (4), 则由 (k)(i) 连边。

每个点都有唯一一个的父亲,这显然会构成一棵树。

不难发现,把根节点到当前节点 (x) 的路径上所有的操作做完之后,就相当于得到了第 (x) 次操作后的矩阵。

因此我们可以从根节点进行 (DFS), 拿 ( bitset) 维护这个矩阵,搜完当前节点的子树就把 (bitset) 回溯一下即可。

代码写起来比较流畅,反正好写就是了。

复杂度:(O(能过))

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<bitset>
using namespace std;
const int N = 1e5+10;
int n,m,tot,cntq,num,ans[N],head[N];
bitset<1010>s[1010],b;
struct bian
{
    int to,net;
}e[N<<1];
struct node
{
    int opt,x,y;
}q[N];
inline int read()
{
    int s = 0,w = 1; char ch = getchar();
    while(ch < '0' || ch > '9'){if(ch == '-') w = -1; ch = getchar();}
    while(ch >= '0' && ch <= '9'){s = s * 10 + ch - '0'; ch = getchar();}
    return s * w;
}
void add(int x,int y)
{
    e[++tot].to = y;
    e[tot].net = head[x];
    head[x] = tot;
}
void dfs(int now,int fa)
{
    int last;
    if(q[now].opt == 1)
    {
        last = s[q[now].x][q[now].y];
        if(last == 0) num++;
        s[q[now].x][q[now].y] = 1;
    }
    if(q[now].opt == 2)
    {
        last = s[q[now].x][q[now].y];
        if(last == 1) num--;
        s[q[now].x][q[now].y] = 0;
    }
    if(q[now].opt == 3)
    {
        num -= s[q[now].x].count();
        s[q[now].x] = s[q[now].x] ^ b;
        num += s[q[now].x].count();
    }
    ans[now] = num;
    for(int i = head[now]; i; i = e[i].net)
    {
        int to = e[i].to;
        if(to == fa) continue;
        dfs(to,now);
    }
    if(q[now].opt == 1)
    {
        if(last == 0) num--;
        s[q[now].x][q[now].y] = last;
    }
    if(q[now].opt == 2)
    {
        if(last == 1) num++;
        s[q[now].x][q[now].y] = last;
    }
    if(q[now].opt == 3)
    {
        num -= s[q[now].x].count();
        s[q[now].x] = s[q[now].x] ^ b;
        num += s[q[now].x].count();
    }
}
int main()
{
	freopen("now.in","r",stdin);
	freopen("now.out","w",stdout);
    n = read(); m = read(); cntq = read(); 
    for(int i = 1; i <= m; i++) b[i] = 1;
    for(int i = 1; i <= cntq; i++)
    {
        q[i].opt = read();
        q[i].x = read();
        if(q[i].opt == 1 || q[i].opt == 2) q[i].y = read();
        if(q[i].opt == 4) add(q[i].x,i);
        else add(i-1,i);
    }
    dfs(0,0);
    for(int i = 1; i <= cntq; i++) printf("%d
",ans[i]);
    fclose(stdin); fclose(stdout);
    return 0;
}

T3 聚会

题目描述

CF429E Points and Segments

给定 (n) 条线段 ([l_i,r_i]) ,然后给这些线段红蓝染色,求最后直线上上任意一个点被蓝色及红色线段覆盖次数之差的绝对值不大于1。

数据范围:(nleq 10^5,l_i,r_ile 10^9)

solution

神仙构造题,不会溜了溜了。

原文地址:https://www.cnblogs.com/genshy/p/14520844.html