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

思路接近正解?都想到了?这都是借口呀。

没有用的,往前走吧。

T1:Emotional Flutter

我的做法和题解不太一样,我把s放在最后考虑了。

因为出发以后步幅是一样的,所以每一个黑条可以ban掉一段出发点。把黑条的左右边界%k存成区间,每个黑条可以存一个或者两个区间【跨越k这个边界】。然后像以前写区间覆盖的贪心一样按左端点排序,看看有没有长至少为s的空余。

代码:

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int t;
int s,k,n,flag,flag1;
int a[500010],cnt;
long long sum[500010];
struct node{
    int l,r;
}b[1000010];
bool cmp(node x,node y){
    if(x.l<y.l)return true;
    else if(x.l==y.l){
        if(x.r<y.r)return true;
        else return false;
    }
    else return false;
}
int main()
{
    scanf("%d",&t);
    while(t--){
        scanf("%d%d%d",&s,&k,&n);
        flag=0;
        cnt=0;
        if(k<s)flag=1;
        for(int i=1;i<=n;i++){
            scanf("%d",&a[i]);
            if(a[i]>=k&&i&1)flag=1;
            sum[i]=sum[i-1]+a[i];
        }
        if(flag){
            printf("NIE\n");
            continue;
        }
        for(int i=1;i<=n;i+=2){
            long long x=sum[i-1]+1,y=sum[i];
            if(x/k<y/k){
                b[++cnt].l=0,b[cnt].r=y%k;
                b[++cnt].l=x%k;b[cnt].r=k-1;
            }
            else{
                b[++cnt].l=x%k,b[cnt].r=y%k;
            }
        }
        sort(b+1,b+cnt+1,cmp);
        int r=-1;
        for(int i=1;i<=cnt;i++){
            if(b[i].l-r-1>=s){
                flag=1;
                break;
            }
            r=max(b[i].r,r);
        }
        if(k-r-1>=s)flag=1;
        if(flag)printf("TAK\n");
        else printf("NIE\n");
    }
    return 0;
}

至于考场上对s的处理混乱,没有考虑黑条的时候才判断是否大于k……这些,都是不应该犯的错误才对。

T2:Endless Fantasy

题意很明确,统计一个子树内人数最多的A以及这个人数B,需要考虑的就是怎么去除因为dfs序的限制而产生的其它不相关子树的影响。

……其实是道板子题,但是我当时在树上启发式合并那个地方偷懒了,没学……

咳,欠下的总是要还的,我好像之前也说过这句话…

于是一边跟自己生气一边头铁,大半夜开始学树上启发式合并【这个时候刚刚改完T1】。先是几乎默写了一遍板子,在1:24的时候通过T2。然后再自己理解着重来一次,三点钟又交了一次。大约是晚上状态很差花的时间不短,神奇的是两次的代码居然一模一样,可能我还是背会的……?

嗯,那么再来记一遍树上启发式合并。

对于每个节点,统计它的答案。首先solve所有轻儿子的答案,然后再遍历一次去除它们对统计数组的影响。然后solve重儿子,这一次不用清除,直接先让当前节点继承它处理过的统计数组以及答案。最后再次遍历轻儿子及其子树,把它们的信息累加到统计数组里。这样可以避免其它不相关节点的信息留在统计数组里造成影响,也可以避免每次暴力清除和合并【虽然树上启发式合并就是个优化的暴力】。树上启发式合并可以少跑重儿子的清除,虽然轻儿子会多跑两次。处理轻儿子的总时间复杂度是O(nlogn)级别,处理重儿子是O(n)级别,总复杂度O(nlogn)。

另外再提一下,线段树合并也是O(nlogn)级别的,而平衡树合并是O(nlog2n)。到刚刚为止我都算错了线段树合并的复杂度,不然哪来这么多事【捶桌】。

代码:

#include<iostream>
#include<cstdio>
using namespace std;
int n,m,ans[400010][2],a[400010],b[400010],f[400010];
int ver[800010],Next[800010],head[400010],tot;
int siz[400010];
void add(int x,int y){
    ver[++tot]=y;
    Next[tot]=head[x];
    head[x]=tot;
}
void dfs(int x,int fa){
    siz[x]=1;
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        if(y==fa)continue;
        dfs(y,x);
        siz[x]+=siz[y];
    }
}
void ins(int x,int y,int v,int opt){
    if(opt==1){
        f[x]+=y;
        if(f[x]>ans[v][1]||(f[x]==ans[v][1]&&x<ans[v][0])){
            ans[v][0]=x;
            ans[v][1]=f[x];
        }
    }
    else f[x]-=y;
}
void count(int x,int fa,int v,int opt){
    ins(a[x],b[x],v,opt);
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        if(y==fa)continue;
        count(y,x,v,opt);
    }
}
void solve(int x,int fa){
    int v=0;
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        if(y==fa)continue;
        if(siz[y]>siz[v])v=y;
    }
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        if(y==fa||y==v)continue;
        solve(y,x);
        count(y,x,x,0);
    }
    if(v)solve(v,x),ans[x][0]=ans[v][0],ans[x][1]=ans[v][1];
    ins(a[x],b[x],x,1);
    for(int i=head[x];i;i=Next[i]){
        int y=ver[i];
        if(y==fa||y==v)continue;
        count(y,x,x,1);
    }
}
int main()
{
    scanf("%d%d",&n,&m);
    for(int i=1,x,y;i<n;i++){
        scanf("%d%d",&x,&y);
        add(x,y),add(y,x);
    }
    for(int i=1;i<=n;i++)scanf("%d%d",&a[i],&b[i]);
    dfs(1,0);
    solve(1,0);
    for(int i=1;i<=n;i++)printf("%d %d\n",ans[i][0],ans[i][1]);
    return 0;
}

T3:字符消除2

【考试的时候我疑惑了一下为什么题目名称画风变了主人公也变了,仔细一看题面,这个语死早的感觉,一定不是同一个出题人,你看看T2的题面多良心……【大雾】】

你好,题面理解先gank掉半小时。【并没有这么夸张】

我想到了kmp,因为我只会kmp,不要提什么自动机自动机我全忘了还没复习】】】

虽然改错的时候发现kmp也快记错了…

考场上没什么思路,打了个暴力dfs。题解给出的正解是跑kmp,从n开始往回跳的next数组就是t集合。即t集合包含n,next[n],next[next[n]]……

可能是我对题目理解还是有偏差。我觉得对于原串,所求出的这个并非t集合,而是与每一个t对应的,原串折叠回去的长度。

求出这个t集合以后开始生成01串,t作为现在已经生成的串长。考虑从前一个t到下一个t,因为t是结尾折叠回去的长度,每个t对应的串结尾部分一定相同。于是如果上一个t长*2大于等于当前t的串长,直接把当前t减去上一个t的长度复制成上一个t的结尾。如果上一个t长*2小于当前串长,那么除了复制的一段,中间还要填0或者1。最优的情况肯定是全部填成0,但是这样可能会使得不该与t对应的长度满足了t的条件。发现中间填的这一段的最后一个数字就可以决定这一段是否与原串不相符,所以可以只考虑这个位置填什么。

题解的做法是往回跳next,并看当前串长与是非被减去重复部分的串长整除。但我手模了很多组数据,感觉只有后面新的一部分串和前面完全相同的时候才可能出现冲突。等一下试着写写。

诶 不是 不对,刚刚只改了一个地方,照这样尝试只有六十分……不过可能是别的问题 我再看看【空格病开始了】

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
int n,j,k,len,nxt[200010],nxt1[200010],a[200010],b[200010],cnt;
char s[200010];
int main()
{
    scanf("%d",&n);
    while(n--){
        scanf("%s",s+1);
        len=strlen(s+1);
        j=0;
        cnt=0;
        memset(nxt,0,sizeof(nxt));
        memset(nxt1,0,sizeof(nxt1));
        memset(b,0,sizeof(b));
        for(int i=2;i<=len;i++){
            while(j&&s[i]!=s[j+1])j=nxt[j];
            if(s[i]==s[j+1])j++;
            nxt[i]=j;
        }
        for(int i=len;i;i=nxt[i])a[++cnt]=i;
        sort(a+1,a+cnt+1);
        for(int i=1;i<a[1];i++)b[i]=0;
        b[a[1]]=1;
        b[1]=0;
        k=0;
        for(int i=2;i<=a[1];i++){
            while(k&&b[i]!=b[k+1])k=nxt1[k];
            if(b[i]==b[k+1])k++;
            nxt1[i]=k;
        }
        for(int i=2;i<=cnt;i++){
            if(a[i-1]*2>=a[i]){
                for(int l=a[i-1]+1;l<=a[i];l++){
                    b[l]=b[l-(a[i]-a[i-1])];
                    while(k&&b[l]!=b[k+1])k=nxt1[k];
                    if(b[l]==b[k+1])k++;
                    nxt1[l]=k;
                }
            }
            else{
                for(int l=a[i-1]+1;l<a[i]-a[i-1];l++){
                    b[l]=0;
                    while(k&&b[l]!=b[k+1])k=nxt1[k];
                    if(b[l]==b[k+1])k++;
                    nxt1[l]=k;
                }
                int flag=0,pos=a[i]-a[i-1],p=k;
                while(p){
                    if(!b[p+1]){
                        if(pos%(pos-p-1)==0)flag=1;
                    }
                    p=nxt1[p];
                }
                if(!b[p+1]){
                    if(pos%(pos-p-1)==0)flag=1;
                }
                if(flag)b[pos]=1;
                else b[pos]=0;
                while(k&&b[pos]!=b[k+1])k=nxt1[k];
                if(b[pos]==b[k+1])k++;
                nxt1[pos]=k;
                for(int l=pos+1;l<=a[i];l++){
                    b[l]=b[a[i-1]+l-a[i]];
                    while(k&&b[l]!=b[k+1])k=nxt1[k];
                    if(b[l]==b[k+1])k++;
                    nxt1[l]=k;
                }
            }
        }
        for(int i=1;i<=len;i++)printf("%d",b[i]);
        printf("\n");
    }
    return 0;
 } 

在没放弃前都不算输家,记住这一点并继续加油吧。嗯,我这种垃圾不谈,希望的大家一定可以做到的。

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

时间: 2024-07-29 23:39:46

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

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

模拟测试47

T1: 题意: 有交替的N个黑白段,长度给定,一个人脚长S,步长K,可以从任意一点出发,问是否有一种方案,能够从第一个块之前走到最后一个块以后,并且脚不碰到任何一个黑色段. 题解: 我们可以将该问题在模意义下解决. 将每个黑色段的区间求出,左侧缩一的长度,右侧延伸脚长减一,这样将脚变成了一个点. 如果某个黑色段的长度大于K,那么一定无解.将所有的黑色段左右端点取模,特判黑色段被劈成两段的情况. 然后得到了一些区间,问题转化为0-K-1是否被全部覆盖,排序后单调指针扫一遍即可. 时间复杂度$O(N

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的复杂度过不

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离散化以后的排序,即线段树里的位置)

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

2019.10.22 csp-s模拟测试82 反思总结

算了 我在干什么orz T2: #include<iostream> #include<cstdio> #include<cstring> using namespace std; const int N=1e5+10,p=1500007,mod=998244353; int n,len,cnt1=1,cnt2=1,lens; char s[2*N],c[2*N]; int tree1[2*N][27],tree2[2*N][27]; unsigned long lon