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

状压 fi,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

我们考虑计算 gi 表示在 r 轮内第 i 段被修复过的概率

那么 ans=i=1ngidi

发现一段水管被修复过,只需要让在他前面的要么被修过,要么没有爆,也就是水流流经该点,那么其实知道了前面的概率剩该点的期望只与剩余轮数有关

那么设 fi,j 表示在前 i 个位置,在 r 轮中修复过 j 次的期望

那么就可以枚举当前位置是被修复过,还是根本没有爆即可进行转移

那么我们有

fi,j=fi1,j×(1pi)rj+fi1,j1×(1(1pi)rj+1)

特别的 j=0 时没有 fi1,j1 到他的转移

那么 gi 就非常好求了

gi=j=0nfi1,j×(1(1pi)rj)

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

posted @   Chen_jr  阅读(132)  评论(13编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示