2017/10/15模拟赛总结

来源:互联网 发布:qt mysql数据库 编辑:程序博客网 时间:2024/06/05 03:16

题目来自计蒜客2017 NOIP 提高组模拟赛(四)Day2

T1 鬼脚图

不难发现一条横线的效果是把aiai+1的元素交换一遍
所以对于第一个问题直接按顺序交换一遍即可
这样第二个问题就转化为使一个排列两两相邻交换变成升序的最少交换次数
这也就是逆序对的个数 用归并排序或BIT求一下即可
O(nlog2n)

#include<bits/stdc++.h>using namespace std;#define N 100010#define M 1000010void rd(int &x){    char c;x=0;    while (c=getchar(),c<48);    do x=(x<<1)+(x<<3)+(c^48);    while (c=getchar(),c>=48);}int n,m,ans[N];struct Binary_Indexed_tree{    int bit[N];    void add(int i){        while (i){            bit[i]++;            i-=i&-i;        }    }    int query(int i){        int res=0;        while (i<=n){            res+=bit[i];            i+=i&-i;        }        return res;    }}BIT;int main(){    int i;    rd(n),rd(m);    for (i=1;i<=n;i++) ans[i]=i;    for (i=1;i<=m;i++){        int x;        rd(x);        swap(ans[x],ans[x+1]);    }    for (i=1;i<=n;i++) printf("%d ",ans[i]);    long long sum=0;    for (i=1;i<=n;i++){        sum+=BIT.query(ans[i]);        BIT.add(ans[i]);    }    printf("\n%lld\n",sum);    return 0;}

T2 跑步爱天天

首先不难发现警卫的路径是互相包含的 祖先包含儿子的路径
那么可以预处理出1节点的路径 然后每个点顺便求出在路径上循环开始和结束的位置
这样可以每次模拟每个警卫走一步 而总共要跳depS次 直接模拟O(ndepS)

考虑链的情况
只需要考虑起点的祖先中距离为偶数的
因为奇数距离会导致穿过之后无法再追上 肯定不会相遇
注意有可能不是以1为端点 (虽然数据都是1为端点)

其实有了前面考虑的几个条件 就离正解不远了
先把所有起点的祖先在路径上第一次出现的位置mark掉
可以枚举相遇的点的在路径上每个对应的位置
然后判断在路径数组里向前走同样步数有没有祖先
一个点的多个对应的位置 可以用一个链表存
O(n)

#include<bits/stdc++.h>using namespace std;#define N 500010void rd(int &x){    char c;x=0;    while (c=getchar(),c<48);    do x=(x<<1)+(x<<3)+(c^48);    while (c=getchar(),c>=48);}struct edge{    int nxt,t;}e[N];int head[N],edge_cnt,n,S,a[N];void add_edge(int x,int y){    e[edge_cnt]=(edge){head[x],y};    head[x]=edge_cnt++;}int fa[N],id[N],dfs_cnt,nxt[N<<1];bool mark[N<<1];void dfs(int x,int f){    id[x]=++dfs_cnt;    fa[x]=f;    int i,las=id[x];    for (i=head[x];~i;i=e[i].nxt){        int to=e[i].t;        dfs(to,x);        nxt[las]=++dfs_cnt;        las=dfs_cnt;    }    nxt[las]=-1;}int main(){    int Case,i,j;    rd(Case);    while (Case--){        memset(head,-1,sizeof(head));        memset(mark,0,sizeof(mark));        dfs_cnt=edge_cnt=0;        rd(n);        for (i=1;i<=n;i++){            int x;            rd(x);            for (j=1;j<=x;j++) rd(a[j]);            for (j=x;j;j--) add_edge(i,a[j]);        }        rd(S);        dfs(1,0);        int x=S,ans=0;        while (x){            mark[id[x]]=1;            x=fa[x];        }        x=S;        int stp=0,i;        while (x){            for (i=id[x];~i;i=nxt[i]){                if (i-stp<=0) continue;                ans+=mark[i-stp];                mark[i-stp]=0;            }            x=fa[x];            stp++;        }        printf("%d\n",ans);    }    return 0;}

T3 神奇的三角形

找规律可以发现i=0n1C(i,j)=C(n,j+1)
然后对于n106就可以暴力用逆元算组合数了
ans=i=1nC(n,i)im
再看m=1m=2的特殊情况
再找规律可以发现 分别是ans=n2n1ans=n(n+1)2n2
但是对于再大一点的m并没有这样的规律

考虑上面那个式子的组合意义
即对于所有的in里选i个颜色 然后用这i个颜色可重复地排出一个长度为m的序列的方案数
但是这样难以直接求解
换一种思路:先枚举一种情况里实际用了i种颜色 然后剩余的颜色可选可不选 就有C(n,i)2ni种方法
再乘上i种颜色的方案数im
这样包含了只取比i少的颜色的方案数 要用容斥原理除去
最后方案数为
C(n,i)2ni(imC(i,i1)(i1)m+C(i,i2)(i2)m+...)
对于正解要用NTT 然而对于我这种蒟蒻来说并不会..
留着以后填坑吧

#include<bits/stdc++.h>using namespace std;#define mo 998244353int Pow(int a,int b){    int res=1;    while (b){        if (b&1) res=1LL*res*a%mo;        b>>=1;        a=1LL*a*a%mo;    }    return res;}int n,m;struct P1{    int sum[1000010];    void solve(){        int i,ans=0;        sum[0]=1;        for (i=1;i<=n/2+1;i++){            sum[i]=1LL*sum[i-1]*(n+1-i)%mo*Pow(i,mo-2)%mo;            ans=(ans+1LL*sum[i]*Pow(i,m))%mo;        }        for (;i<=n;i++) ans=(ans+1LL*sum[n-i]*Pow(i,m))%mo;        printf("%d\n",ans);    }}P40;struct P5{    int A[3010],Inv[3010];    void solve(){        int i,j,ans=0;        for (i=1;i<=3000;i++) A[i]=Pow(i,m),Inv[i]=Pow(i,mo-2);        int C=1,Pow2=Pow(2,n);        for (i=1;i<=m;i++){            C=1LL*C*(n+1-i)%mo*Inv[i]%mo;            Pow2=1LL*Pow2*Inv[2]%mo;            int sum=1LL*C*Pow2%mo,cnt=A[i];            int f=-1,C1=1;            for (j=i-1;j;j--){                C1=1LL*C1*Inv[i-j]%mo*(j+1)%mo;                cnt=(cnt+1LL*f*C1*A[j])%mo;                f=-f;            }            ans=(ans+1LL*sum*cnt)%mo;        }        printf("%d\n",(ans+mo)%mo);    }}P95;int main(){    scanf("%d%d",&n,&m);    if (n<=1000000) P40.solve();    else if (m<=3000) P95.solve();    return 0;}

Date:2017/10/16
By CalvinJin

原创粉丝点击