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\n",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)$路径上边权组成的所有排列是否有一个是回文序列。$n\leq 10^6,m\leq 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\n",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$中的数(选过的不会再选),此时他会知道答案串对应位置的字符。问期望询问次数。$n\leq 50,m\leq 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}\times \sum\limits_{t} \frac{siz[t]}{siz[j]}\times 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; }