【2019.10.2】NOIP2018 模拟赛
总结:
实话除了T2之外T1、T3都还蛮可做的,操作比较常规并不难想,T2的暴力虽然拿分少但也可以做。所以还是要抓紧时间多做点题多敲点代码,不然就会知道大概做法但推不出正解。
多做题,多考试,多总结。
位运算 + 联通块 + KMP + 博弈论。
T1.
题意:
给出一个n个数的序列a,每次操作可以将a中一个数变成整个序列的值的异或。求最少需要多少次才能将a变成目标序列b,无法完成输出-1。
题解:
异或题(位运算题)都不会是纯模拟,所以考完多做点这种题练练手感熟悉操作。
一个性质:一个数异或同一个数两次后还是原来的数,证明枚举一下就行。
根据这个性质我们可以看出题其实目就是一个n+1的序列交换顺序,达到期望序列,不然输出-1。
交换过程把不相等的a[i]和b[i]连成边,交换次数即联通块边数。
跳到不同联通块次数要加1,n+1不同的话单独算一个连通块。
不难发现最后答案就是:连通块数+边数-1,因为n+1不算操作(否则重复一次操作),但我们在计算的时候算上了它。
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> #define For(i,l,r) for(int i=l;i<=r;i++) #define ll long long using namespace std; const int M=1e5+5; int n,a[M],b[M],x[M],y[M],vis[M],cnt,ans; map<int,int>d; vector<int>Q[M]; inline int read(){ int f=1,sum=0; char ch=getchar(); while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();} while(isdigit(ch)){sum=(sum<<1)+(sum<<3)+(ch^48);ch=getchar();} return f*sum; } inline void dfs(int x){ vis[x]=1; For(i,0,Q[x].size()-1){ if(!vis[Q[x][i]]){ dfs(Q[x][i]); } } } int main(){ n=read(); For(i,1,n){ a[i]=read();a[n+1]^=a[i]; if(!d[a[i]]) d[a[i]]=++cnt; } if(!d[a[n+1]]) d[a[n+1]]=++cnt; For(i,1,n){ b[i]=read();b[n+1]^=b[i]; if(!d[b[i]]) d[b[i]]=++cnt; } if(!d[b[n+1]]) d[b[n+1]]=++cnt; memcpy(x,a,sizeof(x));memcpy(y,b,sizeof(y)); sort(x+1,x+n+2);sort(y+1,y+n+2); For(i,1,n) if(x[i]!=y[i]){printf("-1");return 0;} For(i,1,n+1) a[i]=d[a[i]],b[i]=d[b[i]]; For(i,1,n) if(a[i]!=b[i]) ans++,Q[a[i]].push_back(b[i]); if(a[n+1]!=b[n+1]) Q[a[n+1]].push_back(b[n+1]); For(i,1,cnt) if(Q[i].size()&&!vis[i]) dfs(i),ans++; printf("%d",ans-1); return 0; }
T2.
题意:给出一棵树,每个节点有两个值Ai,Di,表示这个节点能给它的0~Di级祖先的F值贡献Ai。然后给每条边一个出现概率,每次询问某个节点的联通块的F值的和的平方的期望。
题解:
代码:
T3.
题意:
给定两个串S和T,|S| >= |T|。两个人博弈,每一轮操作,两人先后可以删掉S的第一位或最后一位。当操作以后的串的长度等于|T|时,游戏停止。如果停止时S剩下的串=T,则后手获胜,否则先手获胜。问两人均采取最优策略下谁赢。
题解:
考虑简化表示状态。
设左边已经删掉了L个字符,右边已经删掉了R个字符,那么用R-L来表示当前状态。可以用KMP求出有哪些目标状态。
一开始R-L为0,每一次操作可以让它+1或者-1,双方轮流操作,总共|S|-|T|次操作,双方都用最优策略看最后是否能到达目标状态。
显然所有的目标状态奇偶性相同。
当|S|-|T|为奇数时,最后一次操作是先手做,它肯定往不是目标状态走,那么一个位置在最后一次操作前是目标状态当且仅当它+1或-1都是目标状态。
现在就全部转化成|S|-|T|为偶数的情况,假如0是目标状态,那么显然后手会赢,因为无论先手往哪里走后手都可以把他拉回来,如果0不是,并且-2和2不全是,那么先手一定会朝不是的那一边走,后手无论如何都没有办法将它拉回到是的那一边了。因此后手会赢当且仅当0是目标状态或者-2和2都是目标状态否则都是先手赢。
代码:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include<bits/stdc++.h> using namespace std; const int M=1e5+5; int t,la,lb,nxt[M],goal[M<<1],pos[M]; char a[M],b[M]; int main(){ scanf("%d",&t); while(t--){ scanf("%s",a+1),scanf("%s",b+1); la=strlen(a+1),lb=strlen(b+1); memset(nxt,0,sizeof(nxt)); memset(goal,0,sizeof(goal)); memset(pos,0,sizeof(pos)); for(int i=2,j=0;i<=lb;i++){ while(j&&b[j+1]!=b[i]) j=nxt[j]; if(b[j+1]==b[i]) j++; nxt[i]=j; } for(int i=1,j=0;i<=la;i++){ while(j&&(b[j+1]!=a[i]||j==lb)) j=nxt[j]; if(b[j+1]==a[i]) j++; if(j==lb) pos[i]=1; } if((la-lb)&1) for(int i=1;i<=la;i++) goal[la+lb-2*i+1+la]=pos[i-1]&pos[i]; else for(int i=1;i<=la;i++) goal[la+lb-2*i+la]=pos[i]; if(goal[la]||goal[la-2]&&goal[la+2]) printf("pty\n"); else printf("cqf\n"); } return 0; }