2019.9.29 csp-s模拟测试55 反思总结

不咕咕咕是一种美德【大雾】

头一次体会到爆肝写题解???

这次考试我们没赶上,是后来掐着时间每个人自己考的。我最后的分数能拿到152…熟悉的一题AC两题爆炸。

强烈吐槽出题人起名走心

T1联:

发现每一次加入一个区间的操作,只有区间的l或者r+1有可能成为答案。那么考虑能不能用这两个点代表一整个区间,维护全局最靠左的0在什么地方。

把每个操作的l和r+1都存下来,离散化,建一棵线段树。每一次区间操作都针对线段树上的a[l]-a[r+1]-1这部分(a[x]为x离散化以后的排序,即线段树里的位置)。

维护线段树的区间和,每一次查询就是寻找最左边的区间和不等于区间长度的地方。对于反转操作,打一个可下传的标记,让现有区间和变成区间长度减去原区间和。

要注意一开始答案为1,保存下所有l和r+1还要额外加一个1。

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
int m,cnt=1;
long long b[300010];
struct node{
    int opt;
    long long l,r;
}a[300010];
struct tree{
    int l,r,sum,tag1,tag2;
}t[1000010];
void build(int p,int l,int r){
    t[p].l=l,t[p].r=r;
    t[p].tag1=-1;
    if(l==r)return;
    int mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
}
void pushdown(int p){
    if(t[p].tag1!=-1){
        t[p*2].tag1=t[p].tag1;
        t[p*2].sum=(t[p*2].r-t[p*2].l+1)*t[p].tag1;
        t[p*2].tag2=0;
        t[p*2+1].tag1=t[p].tag1;
        t[p*2+1].sum=(t[p*2+1].r-t[p*2+1].l+1)*t[p].tag1;
        t[p*2+1].tag2=0;
        t[p].tag1=-1;
    }
    if(t[p].tag2){
        t[p*2].tag2^=1;
        t[p*2].sum=t[p*2].r-t[p*2].l+1-t[p*2].sum;
        t[p*2+1].tag2^=1;
        t[p*2+1].sum=t[p*2+1].r-t[p*2+1].l+1-t[p*2+1].sum;
        t[p].tag2=0;
    }
}
void change(int p,int l,int r,int val){
    if(l<=t[p].l&&t[p].r<=r){
        t[p].sum=(t[p].r-t[p].l+1)*val;
        t[p].tag1=val;
        t[p].tag2=0;
        return;
    }
    pushdown(p);
    int mid=(t[p].l+t[p].r)/2;
    if(l<=mid)change(p*2,l,r,val);
    if(r>mid)change(p*2+1,l,r,val);
    t[p].sum=t[p*2].sum+t[p*2+1].sum;
}
void change1(int p,int l,int r){
    if(l<=t[p].l&&t[p].r<=r){
        t[p].tag2^=1;
        t[p].sum=t[p].r-t[p].l+1-t[p].sum;
        return;
    }
    pushdown(p);
    int mid=(t[p].l+t[p].r)/2;
    if(l<=mid)change1(p*2,l,r);
    if(r>mid)change1(p*2+1,l,r);
    t[p].sum=t[p*2].sum+t[p*2+1].sum;
}
long long query(int p){
    if(t[p].l==t[p].r){
        return b[t[p].l];
    }
    pushdown(p);
    int mid=(t[p].l+t[p].r)/2;
    long long val;
    if(t[p*2].sum!=t[p*2].r-t[p*2].l+1)val=query(p*2);
    else if(t[p*2+1].sum!=t[p*2+1].r-t[p*2+1].l+1)val=query(p*2+1);
    t[p].sum=t[p*2].sum+t[p*2+1].sum;
    return val;
}
int main()
{
    scanf("%d",&m);
    b[1]=1;
    for(int i=1;i<=m;i++){
        scanf("%d%lld%lld",&a[i].opt,&a[i].l,&a[i].r);
        a[i].r++;
        b[++cnt]=a[i].l;
        b[++cnt]=a[i].r;
    }
    sort(b+1,b+cnt+1);
    int n=unique(b+1,b+cnt+1)-b-1;
    build(1,1,n);
    for(int i=1;i<=m;i++){
        a[i].l=lower_bound(b+1,b+n+1,a[i].l)-b;
        a[i].r=lower_bound(b+1,b+n+1,a[i].r)-b;
        if(a[i].opt==1)change(1,a[i].l,a[i].r-1,1);
        else if(a[i].opt==2)change(1,a[i].l,a[i].r-1,0);
        else change1(1,a[i].l,a[i].r-1);
        printf("%lld\n",query(1));
    }
    return 0;
}

T2赛:

考试的时候错误贪心骗到了40分…

正解是枚举满足两个人都喜欢的物品的个数x,然后对于每个人再补上k-x个他喜欢的物品,不足m个就再从剩下的物品里补全。用线段树维护剩下的物品以支持查询前多少个最小值的和。

注意边界问题。

#include<iostream>
#include<cstdio>
#include<queue>
#include<algorithm>
using namespace std;
int n,m,k,A,B,cnt,k1,k2;
int v[200010],v1[200010],va[200010],vb[200010],val[200010];
int a[200010],b[200010],ab[200010],d[200010];
long long sum,ans=1e18,suma[200010],sumb[200010],sumab;
struct node{
    int l,r,cnt;
    long long sum;
}t[1000010];
void build(int p,int l,int r){
    t[p].l=l,t[p].r=r;
    if(l==r)return;
    int mid=(l+r)/2;
    build(p*2,l,mid);
    build(p*2+1,mid+1,r);
}
void change(int p,int l,int r,int y){
    if(l<=t[p].l&&t[p].r<=r){
        t[p].cnt+=y;
        t[p].sum+=y*(val[l]);
        return;
    }
    int mid=(t[p].l+t[p].r)/2;
    if(l<=mid)change(p*2,l,r,y);
    if(r>mid)change(p*2+1,l,r,y);
    t[p].cnt=t[p*2].cnt+t[p*2+1].cnt;
    t[p].sum=t[p*2].sum+t[p*2+1].sum;
}
long long query(int p,int y){
    if(t[p].l==t[p].r){
        return t[p].sum/t[p].cnt*y;
    }
//    int mid=(t[p].l+t[p].r)/2;
    if(t[p*2].cnt>y)return query(p*2,y);
    else if(t[p*2].cnt==y)return t[p*2].sum;
    else{
        long long z=0;
        z+=t[p*2].sum;
        return z+query(p*2+1,y-t[p*2].cnt);
    }
}
int main()
{
    scanf("%d%d%d",&n,&m,&k);
    for(int i=1;i<=n;i++){
        scanf("%d",&v[i]);
        val[++cnt]=v[i];
    }
    sort(val+1,val+cnt+1);
    int num=unique(val+1,val+cnt+1)-val-1;
    scanf("%d",&A);
    for(int i=1,x;i<=A;i++){
        scanf("%d",&x);
        va[x]=1;
    }
    scanf("%d",&B);
    for(int i=1,x;i<=B;i++){
        scanf("%d",&x);
        vb[x]=1;
    }
    if(A<k||B<k||k>m||n<m){
        printf("-1");
        return 0;
    }
    for(int i=1;i<=n;i++){
        v1[i]=lower_bound(val+1,val+num+1,v[i])-val;
        if(va[i]&&vb[i]){
            ab[++ab[0]]=v1[i];
        }
        else if(va[i]){
            a[++a[0]]=v1[i];
        }
        else if(vb[i]){
            b[++b[0]]=v1[i];
        }
        else d[++d[0]]=v1[i];
    }
    if(k*2-ab[0]>m){
        printf("-1");
        return 0;
    }
    build(1,1,num);
    sort(ab+1,ab+ab[0]+1);
    sort(a+1,a+a[0]+1);
    sort(b+1,b+b[0]+1);
    sort(d+1,d+d[0]+1);
    for(int i=1;i<=a[0];i++){
        suma[i]=suma[i-1]+val[a[i]];
        if(i>k)change(1,a[i],a[i],1);
    }
    for(int i=1;i<=b[0];i++){
        sumb[i]=sumb[i-1]+val[b[i]];
        if(i>k)change(1,b[i],b[i],1);
    }
//    for(int i=1;i<=ab[0];i++){
//        change(1,ab[i],ab[i],1);
//    }
    for(int i=1;i<=d[0];i++){
        change(1,d[i],d[i],1);
    }
    for(int i=0;i<=min(ab[0],k);i++){
        if(2*k-m>i||k-i>a[0]||k-i>b[0]){
            if(i>0){
//                change(1,ab[i],ab[i],-1);
                sumab+=val[ab[i]];
                if(a[k-i+1])change(1,a[k-i+1],a[k-i+1],1);
                if(b[k-i+1])change(1,b[k-i+1],b[k-i+1],1);
            }
        }
        else if(i==0){
            sum=suma[k]+sumb[k];
            sum+=query(1,m-2*k);
            ans=min(sum,ans);
        }
        else{
//            change(1,ab[i],ab[i],-1);
            sumab+=val[ab[i]];
            if(a[k-i+1])change(1,a[k-i+1],a[k-i+1],1);
            if(b[k-i+1])change(1,b[k-i+1],b[k-i+1],1);
            sum=sumab+suma[k-i]+sumb[k-i];
            sum+=query(1,m-i-2*(k-i));
            ans=min(ans,sum);
        }
    }
    printf("%lld\n",ans);
    return 0;
}

T3题:

对于每个苹果,考虑它如果要存活下来需要满足哪些条件,求出它如果存活需要ban掉的苹果的集合。最后统计答案,枚举两个苹果,如果它们都有机会存活且存活需要ban掉的集合没有交集,那么答案++。

于是一开始假设一个苹果最后活着,从后往前逆推出需要满足的集合。逆推的意义为,若要让现有集合存活到这个操作之后,需要让前面的集合满足什么样子。

如果一个操作的两个苹果都属于现有集合,那么作为目标存活到最后的苹果显然必死。如果一个操作中有一个苹果属于现有集合,那么为了让这个苹果存活下来以满足后面的操作,另一个苹果肯定属于这个操作之前的集合。

考试的时候想到了集合也想到了逆推,愣是没搞出来怎么写,大约是含义还有不明确的地方。

#include<iostream>
#include<cstdio>
#include<bitset>
#include<cstring>
using namespace std;
int n,m,vis[410],flag,ans,x[50010],y[50010],num;
bitset<410>a[410];
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1;i<=m;i++){
        scanf("%d%d",&x[i],&y[i]);
    }
    for(int i=1;i<=n;i++){
        a[i][i]=1;
        for(int j=m;j>=1;j--){
            int u=x[j],v=y[j];
            if(a[i][u]&&a[i][v]){
                vis[i]=1;
                break;
            }
            else if(a[i][u]||a[i][v]){
                a[i][u]=a[i][v]=1;
            }
        }
    }
    for(int i=1;i<=n;i++){
        for(int j=i+1;j<=n;j++){
            if(!vis[i]&&!vis[j]&&(a[i]&a[j]).count()==0)ans++;
        }
    }
    printf("%d",ans);
    return 0;
}

几次考试暴露出来的问题越来越多,有点应付不过来。

趁着国庆集训赶紧追进度…不然怕是真的要在一个多月以后彻底结束了。

原文地址:https://www.cnblogs.com/chloris/p/11614792.html

时间: 2024-10-07 19:05:04

2019.9.29 csp-s模拟测试55 反思总结的相关文章

[考试反思]0929csp-s模拟测试55:沦陷

菜得过分. 面对T1的大板子不知所措,然后T2的贪心不小心把排序语句删了... T1这种大模板啊...其实我是觉得我能打出来的,然后先用一个小时码了一个2k. 然后做T2想贪心就出来了.十分钟码完T3暴力之后回T1打对拍瞬间爆炸. 于是又重新打了一个2k,WA0.对拍发现. 然后考试就没几分钟了交暴力走了. 不要打完就跑,记得早点对拍改进思路. T1: 的确是挺裸的线段树.离散化或者权值线段树都可以. 但是考场上两个都打出来都死了. 最后用离散化A的. 1 #include<cstdio> 2

2019.9.28 csp-s模拟测试54 反思总结

咕咕咕的冲动如此强烈x T1x: 看完题目想了想,感觉把gcd不为1的强行放在一组,看作一个连通块,最后考虑连通块之间的组合方式就可以了. 然后维护这个连通块可以写并查集可以连边跑dfs怎么着都行… 然而我在处理数字分解质因数这里T掉了,原因是一个很显然的优化写法我基本没怎么写过.线性筛的时候记录每个数是被哪个质数标记过的,分解一个数的时候直接处理记录下来的质数就可以. #include<iostream> #include<cstdio> #include<cmath>

2019.10.30 csp-s模拟测试94 反思总结

头一次做图巨的模拟题OWO 自从上一次听图巨讲课然后骗了小礼物以后一直对图巨印象挺好的233 T1: 对于XY取对数=Y*log(x) 对于Y!取对数=log(1*2*3*...*Y)=log1+log2+log3+...+logY 因为数字大小不超过1e5,直接累加最后比较就可以了 #include<iostream> #include<cstdio> #include<cmath> using namespace std; int t,x,y; double a,b

2019.9.19 csp-s模拟测试47 反思总结

思路接近正解?都想到了?这都是借口呀. 没有用的,往前走吧. T1:Emotional Flutter 我的做法和题解不太一样,我把s放在最后考虑了. 因为出发以后步幅是一样的,所以每一个黑条可以ban掉一段出发点.把黑条的左右边界%k存成区间,每个黑条可以存一个或者两个区间[跨越k这个边界].然后像以前写区间覆盖的贪心一样按左端点排序,看看有没有长至少为s的空余. 代码: #include<iostream> #include<cstdio> #include<cstrin

2019.9.20 csp-s模拟测试48 反思总结

头疼,不说废话了,祝大家rp++. T1: 暴力枚举,n3. 枚举两个串开始匹配的位置,每一次尽量修改. #include<iostream> #include<cstdio> using namespace std; int n,k,cnt,num,ans; char a[310],b[310]; int main() { scanf("%d%d",&n,&k); scanf("%s",a+1); scanf("%

2019.9.26 csp-s模拟测试52 反思总结

刚刚写了一个小时的博客没了,浏览器自动刷新. 一!个!小!时! 鼠标键盘电脑哪个都不能摔,气死我了. 垃圾选手T1T2没思路,T3倒是想出来得比较早,靠T3撑着分数. 数据结构学傻选手,属实垃圾. T1平均数: 一个序列的所有数如果减去x,那么平均数也会减去x.可以二分这个x,统计序列里平均数小于0的序列的个数,含义为原序列平均数小于x的序列的个数.最后统计值小于k且最接近k的x就是所求答案. 序列的平均数小于0,那么序列的和也一定小于0.表现在前缀和上即为一个区间的sumr<suml-1,转化

2019.9.27 csp-s模拟测试53 反思总结

这个起名方式居然还有后续?! 为什么起名不是连续的?! T1想了半天,搞出来了,结果数组开小[其实是没注意范围].T2概率期望直接跳,后来翻回来写发现自己整个理解错了期望的含义[何].T3错误想到赛道修建结果来了个错误贪心. 关于T2破罐子破摔输出k居然骗了二十分这件事…… T1u: 一开始各种想偏,维护哪种值是奇数或偶数个,考虑每次操作影响哪些值变化…这些全都跑出来了. 大概过了一个世纪那么长,突然想着能不能直接优化操作的过程啊,然后对暴力进行钻研,终于开始想到差分. 然后觉着nq的复杂度过不

模拟测试55

T1: 区间很大,但是最优位置可能出现的地方不超过$2n$个. 可以将区间端点及最优值可能出现的位置离散化,即每个区间的$l$.$r$和$r+1$,连同$1$一起离散化. 然后线段树维护就即可. 要同时维护最左侧的$0$和$1$,当前区间是否全为$1$或全为$0$. 修改时如果是区间赋值,直接覆盖答案及懒标记. 如果是区间反转,交换最左侧的$0$和$1$的位置,同时反转区间状况和懒标记. 查询最左侧的$0$即可. 时间复杂度$O(nlogn)$. T2: 将物品分成4类:甲喜欢,乙喜欢,都喜欢和

2019.10.21 csp-s模拟测试81 反思总结

T1: 把每一行状压,按行DP.设fi,j,k,i表示第几行,j是当前行的1覆盖状态,k是当前行选择按钮的状态.转移的时候枚举j和k,再枚举下一层的按钮选择情况l.如果l和j可以全覆盖当前层则转移合法,根据下一层选择l状态的代价进行转移.预处理一行每一种选法i可以覆盖到的状态di,各行选择按钮状态i对应的代价dpi,以及每一行的初始状态bi.转移时下一层的覆盖情况就是k|dl|bi+1.初始化第一层是所有选法i对应的代价,即f1,d[i]|b[1],i=dp1,i. 整个DP过程的复杂度是O(3