P13794 [SWERC 2023] Metro quiz 题解
好耶我是这题目的第八个 AC!好前!qp!
额扯远了,来讲题吧。
题目就是说,给定 not possible。
首先考虑在哪种情况下会输出 not possible。显然,如果每个地铁站都问过的话,其实等同于是确定了的,但如果存在两条地铁线路经过的站点编号一模一样的话就可能猜不出来了,所以这个时候输出 not possible 就没毛病了。可以在输入后 return 0; 防止进入之后的操作。
那么接下来考虑定义 DP 状态。设
首先计算一下在这种情况下是不是已经可以确定了。具体的,定义
但是如果
得出这个转移式之后咱就可以痛痛快快 DP 啦!
但是这题结束了吗?没有哦,因为会爆炸。先不管时间复杂度,单空间复杂度就已经
必须优化。由于
由于真正涉及转移的,即,
那么
最终答案就是
#include<bits/stdc++.h>
#define LL long long
#define UInt unsigned int
#define ULL unsigned long long
#define LD long double
#define pii pair<int,int>
#define pLL pair<LL,LL>
#define fr first
#define se second
#define pb push_back
using namespace std;
const int N = 20 , M = 55;
const int K = (1<<20) + 5;
int n,m,a[M],dp[K][M],flag[N];LD Ans;
int read(){
int su=0,pp=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')pp=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){su=su*10+ch-'0';ch=getchar();}
return su*pp;
}
int main(){
n=read(),m=read();
for(int i=1;i<=m;i++){
int t=read();
while(t--)a[i]|=(1<<read());
}for(int i=1;i<m;i++)for(int j=i+1;j<=m;j++)
if(a[i]==a[j]){cout<<"not possible\n";return 0;}
memset(dp,0x3f,sizeof(dp));
for(int s=(1<<n+1)-1;s>=0;s--)for(int i=1;i<=m;i++){
int cnt=0;for(int j=0;j<n;j++)flag[j]=0;
for(int j=1;j<=m;j++)if((s&a[i])==(s&a[j])){cnt++;
for(int k=0;k<n;k++)if((a[i]&(1<<k))!=(a[j]&(1<<k)))flag[k]=j;
}if(cnt<=1){dp[s][i]=0;continue;}
for(int j=0;j<n;j++)if(flag[j])
dp[s][i]=min(dp[s][i],cnt+dp[s|(1<<j)][i]+dp[s|(1<<j)][flag[j]]);
}
Ans=1.00*dp[0][1]/m;printf("%.10Lf\n",Ans);
return 0;
}
花絮,思路小诗。
如果自信能看懂我猎奇的表达方式可以选择先看下面这个神秘的东西。
dp[s][t] 表示问 s 答 t
t 可以用 1~m 中的数代表每个线路
毕竟真正有效的是正确线路的值
嗯对然后 "not possible" 的情况就
当且仅当存在两条线路经过的站点完全一样
这样就猜不出来了所以没可能
这个可以输入的时候特判一下
然后初始化 dp 值为 inf
接下来先枚举 s 和 i
但是记着 s 得倒着枚举才行
s 不用说就是所谓问
i 表示当前猜测到线路是 i
即问 s 后回复的是 i 线路对应的集合
弄一个临时的 flag 标记最开始先清空
然后再用 j 枚举每个线路
看看是否满足与 s 与 i 完美贴合
具体的判断是否满足 s&a[i]=s&a[j]
满足的话 cnt++
然后枚举每一位看是否重合
不重合就用 flag 记录下 j 等会更新要用
出来后先判断 cnt 是否 <=1
是的的话不用转移直接 dp[s][i]=0
否则枚举每一位 j
然后判断 flag[j] 有没有值有就转移
具体的话转移式长这样
dp[s][i]=cnt+dp[s|(1<<j)][i]+dp[s|(1<<j)][flag[j]]
有点长但是记得取 min 哦不然白费功夫
最后直接输出 dp[0][1] 就好啦
显然还要换成小数并除以 m 哟
然后本题就结束啦是不是很简单哒