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;
}
原文地址:https://www.cnblogs.com/Invictus-Ocean/p/13821066.html