CSP-S模拟XX && 2022赛前模测 提高组验题-14 T3
C 修水管
由于是我造的新数据,所以讲的详细些
作为本场考试 \(Lyin\) 大佬唯一没有一眼秒切的题,这题还是有点意思的
这边顺便讲一下新设置的子任务
我觉得我很良心了,比原题至少多送了 \(15\) 分
并且增加了一些给比状压更劣做法的部分分,以及一个用于启发正解的子任务
- \(subtask1\)
送您 \(1\) 分,这样应该就没有爆 \(0\) 的了, 不过您需要输出 \(0\)
- \(subtask2\)
给 \(dfs\)
dfs
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef long double ld;
const int maxn = 250;
int n, r;
ld p[maxn];
int d[maxn], cnt[(1 << 20) + 55];
bool vis[maxn];
ld dfs(int round){
if(round > r)return 0;
ld pre = 1, ans = 0;
for(int i = 1; i <= n; ++i)if(!vis[i]){
vis[i] = 1;
ans += pre * p[i] * (dfs(round + 1) + d[i]);
pre *= (1 - p[i]);
vis[i] = 0;
}
ans += pre * dfs(round + 1);
return ans;
}
void solve(){
scanf("%d%d",&n,&r);
for(int i = 1; i <= n; ++i)scanf("%Lf%d",&p[i], &d[i]);
printf("%Lf\n",dfs(1));
}
int main(){
int t; scanf("%d",&t);
for(int i = 1; i <= t; ++i)solve();
return 0;
}
- \(subtask3\)
给不会滚动数组的装压,以及记忆化搜索
- \(subtask4\)
状压 \(f_{i, j}\) 表示第 \(i\) 轮 修复状态为 \(j\) 的期望,转移如下
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double ld;
const int maxn = 250;
int n, r;
ld p[maxn];
int d[maxn];
ld f[2][(1 << 20) + 55];
void solve(){
scanf("%d%d",&n,&r);
for(int i = 1; i <= n; ++i)scanf("%lf%d",&p[i], &d[i]);
int mx = 1 << n;
for(int i = 0; i <= 1; ++i) for( int j = 0; j < mx; ++j ) f[i][j] = 0;
for(int round = 1; round <= r; ++round){
int zt = round & 1;
for(int fix = 0; fix < mx; ++fix){
ld pre = 1, ans = 0;
for(int i = 1; i <= n; ++i)if(!(fix & (1 << (i - 1)))){
ans += pre * p[i] * (f[1 - zt][fix | (1 << (i - 1))] + d[i]);
pre *= (1 - p[i]);
}
ans += pre * f[1 - zt][fix];
f[zt][fix] = ans;
}
}
printf("%.10f\n",f[r & 1][0]);
}
int main(){
int t; scanf("%d",&t);
for(int i = 1; i <= t; ++i) cerr << i << endl, solve();
return 0;
}
至此 \(45\) 分在原数据内只有状压的 \(30\) 分
- \(subtask5\)
新加入的用于启发正解的部分分,你可以比较方便的算出特定水管段爆掉的概率,我没有具体实现,不过应该能有人想到,而且确实对正解有启发
- \(subtask6\)
原题存在的部分分,不知道有什么做法
- \(subtask7\)
我们考虑计算 \(g_i\) 表示在 \(r\) 轮内第 \(i\) 段被修复过的概率
那么 \(ans = \sum_{i = 1}^{n}g_i d_i\)
发现一段水管被修复过,只需要让在他前面的要么被修过,要么没有爆,也就是水流流经该点,那么其实知道了前面的概率剩该点的期望只与剩余轮数有关
那么设 \(f_{i, j}\) 表示在前 \(i\) 个位置,在 \(r\) 轮中修复过 \(j\) 次的期望
那么就可以枚举当前位置是被修复过,还是根本没有爆即可进行转移
那么我们有
\(f_{i, j} = f_{i - 1, j} \times (1 - p_i)^{r - j} + f_{i - 1, j - 1} \times (1 - (1 - p_i)^{r - j + 1})\)
特别的 \(j = 0\) 时没有 \(f_{i - 1, j - 1}\) 到他的转移
那么 \(g_i\) 就非常好求了
\(g_i = \sum_{j = 0}^{n}f_{i - 1, j} \times (1 - (1 - p_i)^{r - j})\)
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef double db;
const int maxn = 255;
int n, r;
db p[maxn], f[maxn][maxn];
db qpow(db x, int y){
db ans = 1;
for(; y; y >>= 1, x = x * x)if(y & 1)ans = ans * x;
return ans;
}
int d[maxn];
void solve(){
scanf("%d%d",&n,&r);
for(int i = 1; i <= n; ++i)scanf("%lf%d",&p[i], &d[i]);
for(int i = 1; i <= n; ++i)
for(int j = 0; j <= r; ++j)
f[i][j] = 0;
f[1][0] = qpow((1 - p[1]), r);
f[1][1] = 1 - f[1][0];
for(int i = 2; i <= n; ++i){
int up = min(r, i);
for(int j = 1; j <= up; ++j)
f[i][j] += f[i - 1][j - 1] * (1 - qpow(1 - p[i], r - j + 1)) + f[i - 1][j] * qpow(1 - p[i], r - j);
f[i][0] = f[i - 1][0] * qpow(1 - p[i], r);
}
db ans = f[1][1] * d[1];
for(int i = 2; i <= n; ++i){
int up = min(r, i);
db g = 0;
for(int j = 0; j <= up; ++j) g += f[i - 1][j] * (1 - qpow(1 - p[i], r - j));
ans += g * d[i];
}
printf("%.10lf\n",ans);
}
int main(){
int t; scanf("%d",&t);
for(int i = 1; i <= t; ++i)solve();
return 0;
}
后面是数据生成器等杂项,需要的同学自取
rand
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long ull;
mt19937 rd((ull)(new char) * (ull) (new char));
double p(){
uniform_real_distribution<>d(0, 1 - 0.00011);
return d(rd);
}
int sd(int l, int r){
uniform_int_distribution<>d(l, r);
return d(rd);
}
void solve(){
// int n = sd(4, 5), r = sd();
int n = sd(15, 20), r = sd(10, 20);
printf("%d %d\n",n, r);
for(int i = 1; i <= n; ++i)printf("%.4lf %d\n",p(), sd(1, 1000));
// double pn = p();
// for(int i = 1; i <= n; ++i)printf("%.4lf %d\n",pn, sd(1, 1000));
}
int main(){
int t = 2;
printf("%d\n",t);
for(int i = 1; i <= t; ++i)solve();
return 0;
}
system
#include<bits/stdc++.h>
using namespace std;
int main(){
for(int i = 10; i <= 10; ++i){
stringstream ss;
string s;
ss <<"./rand>" << i <<".in" << endl;
// string s = "./rand >";
// s += i + '0';
// s += ".in";
ss >> s;
ss.clear();
system(s.c_str());
ss <<"./c<" << i <<".in" <<">" << i <<".out" << endl;
ss >> s;
ss.clear();
/*
s = "./c < ";
s += i + '0';
s += ".in > ";
s += i + '0';
s += ".out";*/
system(s.c_str());
}
return 0;
}
\(upd:\) 修改了 \(g\)