zoukankan      html  css  js  c++  java
  • 10月14日考试 题解(模拟+哈希+异或+01BFS+状压+期望DP)

    多测不清空,爆零两行泪QAQ

    T1 麻将

    题目大意:给定三种花色的牌,每个牌的点数为$1-9$。规定一组面子为:1.三张牌2.颜色相同3.点数相同或依次递增(如555或678)。现给定$n$为$13$或$14$,规定$14$张牌胡牌的要求为:3组面子和两张相同的牌;如果现有$13$张牌且差一张牌可以胡牌,则称这一张牌为听牌。若$n=13$,则要输出所有可能的听牌,若无则直接换行;若$n=14$输出$0/1$,表示能否胡牌。

    先考虑$n=14$的情况:拿出相同的两张牌,看剩下$12$张牌能否组成三组面子;若$n=13$则直接枚举听牌,然后判断一下是否能胡牌即可。

    代码:

    #include<cstdio>
    #include<iostream>
    #include<map>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    int T,n,tp,vis[15];
    int hh[1505],top;
    string s;
    struct node
    {
        int num,color,h;
    }a[15],tmp[15],ans[200];
    inline bool cmp(node x,node y){
        if (x.color==y.color) return x.num<y.num;
        return x.color<y.color;
    }
    inline bool judge(int x,int y,int z)
    {
        if (tmp[x].h==tmp[y].h&&tmp[y].h==tmp[z].h) return 1;
        if (tmp[x].color==tmp[y].color&&tmp[y].color==tmp[z].color){
            if (tmp[x].num+1==tmp[y].num&&tmp[y].num+1==tmp[z].num) return 1;
        }
        return 0;
    }
    inline bool solve()
    {
        for (int i=1;i<=14;i++)
        {
            tp=0;memset(vis,0,sizeof(vis));
            if (hh[a[i].h]<2) continue;
            int num=hh[a[i].h]-2;
            for (int j=1;j<=14;j++)
                if (a[j].h==a[i].h){
                    if (num>0) num--,tmp[++tp]=a[j];
                }else tmp[++tp]=a[j];
            sort(tmp+1,tmp+tp+1,cmp);
            int j;
            for (j=1;j<=tp;j++)
            {
                if (vis[j]) continue;
                int flag=0;
                for (int k=j+1;k<=tp;k++)
                {
                    if (!vis[k])
                    for (int l=k+1;l<=tp;l++)
                        if (!vis[l]){
                            if (judge(j,k,l)){
                                vis[j]=vis[k]=vis[l]=1;
                                flag=1;break;
                            }
                        }
                    if (flag) break;
                }
                if (!flag) break;
            }
            if (j>tp) return 1;
        }
        return 0;
    }
    int main()
    {
        scanf("%d%d",&n,&T);
        while(T--)
        {
            top=0;
            for (int i=1;i<=n;i++)
            {
                cin>>s;
                a[i].num=s[0]-'0';a[i].color=(int)s[1];
                a[i].h=a[i].num*100+a[i].color;hh[a[i].h]++;
            }
            if (n==14) {
                printf("%d
    ",solve());
                memset(hh,0,sizeof(hh));
                continue;
            }
            for (int i=1;i<=13;i++)
            {
                if (a[i].num<9)
                {
                    a[14]=a[i];a[14].num++;a[14].h+=100;
                    hh[a[14].h]++;
                    int flag=solve();
                    if (flag&&hh[a[14].h]<=4) ans[++top]=a[14];
                    hh[a[14].h]--;
                }
                if (a[i].num>1)
                {
                    a[14]=a[i];a[14].num--;a[14].h-=100;
                    hh[a[14].h]++;
                    int flag=solve();
                    if(flag&&hh[a[14].h]<=4) ans[++top]=a[14];
                    hh[a[14].h]--;
                }
                a[14].num=a[i].num;a[14].color=a[i].color;
                a[14].h=a[i].h;hh[a[14].h]++;
                if (solve()&&hh[a[14].h]<=4) ans[++top]=a[14];hh[a[14].h]--;
                a[14].num=a[14].h=a[14].color=0;
            }
            sort(ans+1,ans+top+1,cmp);
            memset(hh,0,sizeof(hh));
            for (int i=1;i<=top;i++)
                if (!hh[ans[i].h]){
                    hh[ans[i].h]=1;
                    printf("%d%c ",ans[i].num,(char)ans[i].color);
                }
            memset(hh,0,sizeof(hh));
            cout<<endl;
        }
    }


    T2 树

    题目大意:给定一棵含有$n$个结点的树,边带权,$m$组询问。每次询问$(x,y)$路径上边权组成的所有排列是否有一个是回文序列。$nleq 10^6,mleq 10^7$。

    最直观的想法肯定是维护从根到子节点的前缀异或和,但是异或可能有冲突,所以需要哈希来维护。然而中途写挂了好几次……最好开上ull,然后模一个大质数。

    %%%jzp教我怎么手写哈希表

    代码:

    #include<bits/stdc++.h>
    typedef unsigned long long U;
    using namespace std;const U B=793999;
    const int N=1e6+5,K=1e6+7,M=N<<1;
    int Rd(){
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    struct Hash{
        int hd[K],t,V[N],nx[N];U W[N];
        void add(U x){
            int y=x%K;
            nx[++t]=hd[y];W[hd[y]=t]=x;
        }
        bool find(U x){
            int y=x%K;
            for (int i=hd[y];i;i=nx[i])
                if (W[i]==x) return 1;
            return 0;
        }
    }hs;
    int a,b,m,n,t,hd[N],V[M],W[M],nx[M],c,e[N],ans;
    U g[N],h[M];
    void add(int u,int v,int w){
        nx[++t]=hd[u];V[hd[u]=t]=v;W[t]=w;
    }
    void dfs(int u,int fa,U lst){
        h[e[u]=++c]=lst;
        for (int i=hd[u];i;i=nx[i])
            if (V[i]!=fa)
                dfs(V[i],u,g[W[i]]),
                h[++c]=g[W[i]];
    }
    int main(){
        n=Rd(),m=Rd();g[0]=1;hs.add(0);
        for (int i=1;i<=n;i++)
            g[i]=g[i-1]*B,hs.add(g[i]);
        for (int u,v,w,i=1;i<n;i++)
            u=Rd(),v=Rd(),w=Rd(),
            add(u,v,w),add(v,u,w);dfs(1,0,(U)0);
        for (int i=2;i<=c;i++) h[i]^=h[i-1];
        a=Rd();b=Rd();
        for (int x,y;m--;){
            x=a%n+1;y=b%n+1;
            a=666073ll*a%1000000007;
            b=233ll*b%998244353;
            ans+=hs.find(h[e[x]]^h[e[y]]);
        }
        return printf("%d
    ",ans),0;
    }

    T3 换乘
    题目大意:给定$n$个点$m$条边的无向图,每条边有颜色。当从一条边走到另一条颜色不相同的边时花费$+1$。问从$1$走到$n$的最小花费。

    对于每种颜色维护一个连通块,内部连$0$边;同时联通块内的点向原来的点连$1$边,表示换乘。由于边权只有$01$,所以直接$01$BFS即可。

    代码:

    #include<cstdio>
    #include<iostream>
    #include<vector>
    #include<queue>
    #include<cstring>
    using namespace std;
    const int N=2000005;
    int n,m,num,id[N],vis[N],dis[N],q[N],top;
    int head[N],cnt;
    vector< pair<int,int> >v[N];
    struct node{
        int next,to,dis;
    }edge[N*2];
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    inline void add(int from,int to,int dis)
    {
        edge[++cnt]=(node){head[from],to,dis};
        head[from]=cnt;
    }
    inline void update(int x)
    {
        for (int i=head[x];i;i=edge[i].next)
        {
            int to=edge[i].to;
            if (dis[x]+edge[i].dis<dis[to])
            {
                dis[to]=dis[x]+edge[i].dis;
                if (edge[i].dis) q[++top]=to;
                else update(to);
            }
        }
    }
    int main()
    {
        n=num=read();m=read();
        for (int i=1;i<=m;i++)
        {
            int x=read(),y=read(),c=read();
            v[c].push_back(make_pair(x,y));
        }
        for (int i=1;i<=1000000;i++)
            for (int j=0;j<v[i].size();j++)
            {
                int x=v[i][j].first,y=v[i][j].second;
                if (vis[x]!=i) vis[x]=i,id[x]=++num,add(num,x,1),add(x,num,0);
                if (vis[y]!=i) vis[y]=i,id[y]=++num,add(num,y,1),add(y,num,0);
                add(id[x],id[y],0);add(id[y],id[x],0);
            }
        memset(dis,0x3f,sizeof(dis));
        dis[q[top=1]=1]=0;
        for (int i=1;i<=top;i++) update(q[i]);
        printf("%d",dis[n]>=0x3f3f3f3f?-1:dis[n]);
        return 0;
    }

    T4 游戏

    题目大意:给定$n$个长度为$m$的互不相同的字符串,随机选择一个作为答案。一个人每次随机选择$1-m$中的数(选过的不会再选),此时他会知道答案串对应位置的字符。问期望询问次数。$nleq 50,mleq 20$。

    zzz 永远滴神!!!

    设$bit[i]$表示$i$这一位与本身相等的串的集合,$sta[i]$表示选择状态为$i$时这些地方与自己相同的串的集合,$siz[i]$表示选择状态为$i$之后还有多少串不能被确定。

    $bit$可以$O(nm)$枚举得到;

    $sta$有转移:$sta[i]=sta[i-lowbit(i)] and bit[lowbit(i)+1]$;

    对于$siz[i]$,如果当前$sta[i]!=1<<(k-1))$(k为外层枚举的串,那么$siz[i]++$。

    上述预处理详见代码;有了这些预处理我们可以考虑转移。设$f[i]$表示已经询问了$i$中的位置,期望再询问多少次。有转移:

    $f[s]=frac{1}{num_t} imes sumlimits_{t} frac{siz[t]}{siz[j]} imes f[t]+1$

    这里说一下$frac{siz[t]}{siz[j]}$的含义:现在我已经知道答案在$siz[j]$里面,如果因为答案是随机的,所以它有$frac{siz[t]}{siz[j]}$的概率在$siz[t]$里面,这是我新加入一个$1$可以确定新的范围;有$1-frac{siz[t]}{siz[j]}$的概率不在$t$里面,而$siz[j]-siz[t]$是我新的确定的串,所以这时答案直接确定了,可以不用再转移。

    代码:

    #include<cstdio>
    #include<iostream>
    #include<cstring>
    #define int long long
    #define lowbit(x) x&(-x)
    using namespace std;
    const int M=2100005;
    const int N=55;
    double f[M];
    int s[N][N],sta[M],bit[N],siz[M],rm[M],n,m;
    inline int read()
    {
        int x=0,f=1;char ch=getchar();
        while(!isdigit(ch)){if (ch=='-') f=-1;ch=getchar();}
        while(isdigit(ch)){x=x*10+ch-'0';ch=getchar();}
        return x*f;
    }
    signed main()
    {
        n=read();m=read();
        for (int i=1;i<=n;i++)
            for (int j=1;j<=m;j++) s[i][j]=read();
        for (int i=0;i<m;i++) rm[1ll<<i]=i;
        int all=(1ll<<m)-1;
        for (int i=1;i<=n;i++)
        {
            memset(bit,0,sizeof(bit));
            for (int j=1;j<=n;j++)
                for (int k=1;k<=m;k++)
                    if (s[i][k]==s[j][k]) bit[k]|=1ll<<(j-1);
            siz[0]++,sta[0]=(1ll<<n)-1;
            for (int j=1;j<=all;j++)
            {
                int t=lowbit(j);
                sta[j]=sta[j-t]&bit[rm[t]+1];
                if (sta[j]!=(1ll<<(i-1))) siz[j]++;
            }
        }
        for (int j=all;j>=0;j--)
        {
            if (!siz[j]) continue;
            int sz=0;
            for (int k=1;k<=m;k++)
            {
                if ((1ll<<(k-1))&j) continue;
                int t=j|(1ll<<(k-1));
                f[j]+=siz[t]*f[t],sz++;
            }
            f[j]=f[j]/((double)sz*siz[j])+1;
        }
        printf("%.5lf",f[0]);
        return 0;
    }
  • 相关阅读:
    EasyUi中使用自定义图标
    EasyUi初始配置
    struts2文件上传
    转换器
    Oracle 视图
    存储过程的优缺点
    Oracle 序列详解
    plsql使用技巧
    MYSQL和ORACLE的一些区别
    JAVA操作Excle之Poi(二)批量导出Excle数据
  • 原文地址:https://www.cnblogs.com/Invictus-Ocean/p/13821066.html
Copyright © 2011-2022 走看看