标签:add 自动机 模拟 其它 int eof 线段 字符 时间
思路接近正解?都想到了?这都是借口呀。
没有用的,往前走吧。
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; }
在没放弃前都不算输家,记住这一点并继续加油吧。嗯,我这种垃圾不谈,希望的大家一定可以做到的。
标签:add 自动机 模拟 其它 int eof 线段 字符 时间
原文地址:https://www.cnblogs.com/chloris/p/11558772.html