【19.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不算操作(否则重复一次操作),但我们在计算的时候算上了它。

代码:

#include
#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;
}
View Code

 

T2.

题意:给出一棵树,每个节点有两个值Ai,Di,表示这个节点能给它的0~Di级祖先的F贡献Ai。然后给每条边一个出现概率,每次询问某个节点的联通块F值的和的平方的期望。

题解:

代码:

T3.

题意:

给定两个串ST|S| >= |T|两个人博弈,每一轮操作,两人先后可以删掉S的第一位或最后一位。当操作以后的串的长度等于|T|时,游戏停止。如果停止时S剩下的串=T后手获胜,否则先手获胜。两人均采取最策略下谁赢。

题解:

考虑简化表示状态。

设左边已经删掉了L字符,右边已经删掉了R字符,那么用R-L来表示当前状态。可以用KMP求出有哪些目标状态。

一开始R-L0,每一次操作可以让它+1或者-1,双方轮流操作,总共|S|-|T|次操作,双方都用最优策略看最后是否能到达目标状态。

显然所有的目标状态奇偶性相同。

|S|-|T|为奇数时,最后一次操作是先手做,它肯定往不是目标状态走,那么一个位置在最后一次操作前是目标状态当且仅当它+1-1都是目标状态。

现在就全部转化成|S|-|T|为偶数的情况,假如0是目标状态,那么显然后手会赢,因为无论先手往哪里走后手都可以把他拉回来,如果0不是,并且-22不全是,那么先手一定会朝不是的那一边走,后手无论如何都没有办法将它拉回到是的那一边了。因此后手会赢当且仅当0是目标状态或者-22都是目标状态否则都是先手赢。

代码:

#include
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;
} 
View Code

 

你可能感兴趣的:(【19.10.2】NOIP2018 模拟赛)