暑假2019培训:Day5提高组测试赛

本文是暑假集训第5天的记录,包含三道题目。“字符串函数”题需计算函数f(x, y)的数学期望;“优秀路径”题要求树上所有“优秀的”简单路径的价值和,可用树形dp求解;“树”题涉及prufer序,求从n个点中选点组成大小为s的有标号无根树的方案数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

概述

暑假集训第5天,本来想着能A了第一题,可以万事大吉了~~
可去万万没有想到的是第一题不知道什么原因,大数据全WA了
在这里插入图片描述,只有60分,和哪些优秀暴力的人没什么区别,最终排名91中第41名……

在这里插入图片描述-------------

顺序

  • 1.字符串函数(strfun)
  • 2.优秀路径(path)
  • 3.树(tree)

1.字符串函数(strfun)

【 题目描述】

给定两个等长的由大写英文字母构成的字符串 a 和 b, 从 a 中选出子串 x, 从 b 中选出
子串 y, 其中 x,y 等长。
定义函数 f(x, y)为满足条件 xi = yi(1 <= I <= |x|)的 i 的个数, 计算 f(x, y)的数学期望。

【输入数据】

第一行一个正整数: n, 表示 a 和 b 的长度。
第二行输入字符串 a。
第三行输入字符串 b。

【输出数据】

输出一个实数表示 f(x, y)的期望, 答案保留 6 位小数。

【样例输入】

2
AB
BA

【样例输出】

0.400000

【样例解释】

x,y 的选择有 5 种情况, 分别是(“A”, “B”), (“A”, “A”), (“B”, “B”), (“B”, “A”), (“AB”, “BA”)。
其中,第 2 对和第 3 对所对应的 f(x,y)等于 1, 其他都是 0, 由于选择每一对的概率都是
1/5,所以 f(x,y)的期望为 1/5 * 0 + 1/5 * 1 + 1/5 * 1 + 1/5 * 0 + 1/5 * 0 = 2/5 = 0.4。

【数据范围】

对于 30%的数据, n <= 100。
对于 60%的数据, n <= 5000。
对于 100%的数据, n <= 2e5。

我的做法和正解一样推出了价值的 式子
m i n ( i , j ) ∗ m i n ( n − m i n ( i , j ) + 1 ) min(i,j)*min(n-min(i,j)+1) min(i,j)min(nmin(i,j)+1)
然后我用了O(26N)的复杂度,记录前面几次并无关紧要的,虽然还是能过

期望的和=和的期望
考虑每对位置(a, b)对答案的贡献。
又发现每对(x,y)的期望可由每对位置(a,b)的期望和得来。
于是考虑对于第一串的 a 位置,第二串的 b 位置对答案的贡献。

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,m;
long long k;
char cA[N],cB[N];
int l[N][30],r[N][30];
long long ans;
int main()
{
	freopen("strfun.in","r",stdin);
	freopen("strfun.out","w",stdout);
	int i,j;//价值只会是min(i,j)*min(n-i+1,n-j+1) 
    scanf("%d%s%s",&n,cA+1,cB+1);
	for(i=0;++i<=n;)//对A进行一次前缀和
	{
	    k+=(long long)(n-i+1)*(long long)(n-i+1);
		int x=cA[i]-'A'+1;
		for(j=0;++j<=26;)l[i][j]=l[i-1][j];
		l[i][x]+=i; 
	}
	for(i=n;i>=1;--i)//对A进行后缀 
	{
		int x=cA[i]-'A'+1;
		for(j=0;++j<=26;)r[i][j]=r[i+1][j];
		r[i][x]+=(n-i+1);
	}
	for(i=0;++i<=n;)//进行一次价值操作 
	{
		int x=cB[i]-'A'+1;
	    ans+=(long long)l[i][x]*(n-i+1)+(long long)r[i+1][x]*i;
	}
	printf("%.6lf",(double)ans/(double)k);

	return 0;
}

那么我要问一个很严肃的问题……
在这里插入图片描述在这里插入图片描述
这两个输入卡的我半死,第一个输入为什么会挂,为什么和第二个不一样???
不懂不懂
在这里插入图片描述


2.优秀路径(path)

【题目描述】

给定一颗大小为 n 的树, 树边有边权,顶点有点权。
Alice 认为一条路径是“优秀的”, 当且仅当路径上相邻边的边权不同。
定义一条路径的价值为路径上所有点的点权和。
求树上所有“优秀的”简单路径的价值和。

【输入数据】

第一行输入一个整数 n, 表示树的大小。
第二行输入 n 个整数, 表示 1-n 号点的点权 ai。
接下来 n-1 行, 每行输入 3 个整数 u, v, w,表示 u, v 之间存在一条边权为 w 的边。

【输出数据】

输出一个整数表示树上所有“优秀的”简单路径的价值和。

【样例输入】

6
6 2 3 7 1 4
1 2 1
1 3 2
1 4 3
2 5 1
2 6 2

【样例输出】

134

【样例解释】

优秀路径分别为{1,2}, {1,2,6}, {1,3}, {1,4}, {2,1,3}, {2,1,4}, {2,5}, {2,6}, {3,1,4}, {3,1,2,6},
{4,1,2,6}, {5,2,6}, 路径的价值依次为 8, 12, 9, 13, 11, 15, 3, 6, 16, 15, 19, 7 。

【数据范围】

对于 30%的数据, n <= 10。
对于 50%的数据, n <= 1000。
对于 100%的数据, n <= 3e5, 1 <= u, v <= n, 1 <= ai, w <= 1e5。

树形 dp。
统计到每个点的合法链的价值和, 合法链的条数, 两条单链在端点处合并即可。
合并时注意判断链与链相接的两条边是否相同。
复杂度 O(n)

在这里插入图片描述

====不对
应该是这个:
在这里插入图片描述
暴零,暴力打错
先看看暴力怎么打吧~by hkhh点击这里进入hkhh神犇的博客


#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=3e5;
struct node{
    int Next,y,v;
}e[2*N+10];
int n,len=0,ans=0;
int b[N+10];
int linkk[2*N];
int a[N+10];
bool vis[N+10];

void insert(int x,int y,int v){
    e[++len].Next=linkk[x];
    linkk[x]=len;
    e[len].y=y,e[len].v=v;
}

void work(int num){
  for (int i=1;i<=num;i++) ans+=a[b[i]];
}

void dfs(int x,int lastv,int num){
    if (num>1) work(num);
    for (int i=linkk[x];i;i=e[i].Next){
	    int y=e[i].y;
	    if (vis[y]) continue;
	    if (e[i].v==lastv) continue;
	    vis[y]=1;
	    b[num+1]=y;
	    dfs(y,e[i].v,num+1);
	    b[num+1]=0;
	}
}

signed main(){
	freopen("path.in","r",stdin);
	freopen("path.out","w",stdout);
    scanf("%lld",&n);
    for (int i=1;i<=n;i++) scanf("%lld",&a[i]);
    for (int i=1,x,y,z;i<n;i++){
	    scanf("%lld %lld %lld",&x,&y,&z);
	    insert(x,y,z),insert(y,x,z);
	}
	for (int i=1;i<=n;i++) memset(vis,0,sizeof(vis)),b[1]=i,dfs(i,-1,1);
	printf("%lld",ans/2);
	return 0;
}

正解,是不是就要请教std,wy老师了?
正解代码在这里插入图片描述

#include <cstdio>

typedef long long ll;

const int N = 3e5 + 5;

int n, a[N], tot, first[N], next[N << 1], end[N << 1], cap[N << 1];
int que[N], qn, fa[N], fc[N];
ll ans, cnt[N], sum[N], nc[N], ns[N];

inline int Get() {
	char ch;
	while ((ch = getchar()) < '0' || ch > '9');
	int Num = ch - '0';
	while ((ch = getchar()) >= '0' && ch <= '9')
		Num = Num * 10 + ch - '0';
	return Num;
}

inline void addEdge(int u, int v, int w) {
	next[++tot] = first[u], first[u] = tot, end[tot] = v, cap[tot] = w;
	next[++tot] = first[v], first[v] = tot, end[tot] = u, cap[tot] = w;
}

int main() {
	freopen("path.in", "r", stdin);
	freopen("path.out", "w", stdout);
	n = Get();
	for (int i = 1; i <= n; ++i) a[i] = Get();
	for (int i = 1, u, v, w; i < n; ++i) 
		u = Get(), v = Get(), w = Get(), addEdge(u, v, w);
	
	que[qn = 1] = 1, fa[1] = 0, fc[1] = 0;
	for (int ql = 1, u; u = que[ql], ql <= qn; ++ql) 
		for (int k = first[u], v; v = end[k], k; k = next[k])
			if (v ^ fa[u]) fa[v] = u, fc[v] = cap[k], que[++qn] = v;
	
	
	for (int qr = qn, u; u = que[qr], qr >= 1; --qr) {
		ll sur = a[u], cur = 1;
		for (int k = first[u], v, w; v = end[k], w = cap[k], k; k = next[k])
				if (v ^ fa[u]) {
					ans += (sur - ns[w]) * cnt[v] + sum[v] * (cur - nc[w]); 
					sur += sum[v] + cnt[v] * a[u], cur += cnt[v];
					ns[w] += sum[v] + cnt[v] * a[u], nc[w] += cnt[v];
				}
		
		cnt[u] = 1;
		for (int k = first[u], v, w; v = end[k], w = cap[k], k; k = next[k])
			if (v ^ fa[u]) {
				ns[w] -= sum[v] + cnt[v] * a[u], nc[w] -= cnt[v];
				if (fc[u] ^ w) sum[u] += sum[v], cnt[u] += cnt[v];
			}
		sum[u] += cnt[u] * a[u];
	}
	
	printf("%lld\n", ans);
	fclose(stdin);
	fclose(stdout);
}

//真是好看 的代码,嗯~
在这里插入图片描述


树(tree)

【 题目描述】

有 n 个点从 1 到 n 进行编号, 第 i 个点的限制为度数不能超过 ai。
对每个 s(1 <= s <= n), 问从这 n 个点中选出一些点组成大小为 s 的有标号无根树的方
案数。

【输入数据】

第一行一个整数 n。
第二行 n 个整数表示 ai。

【输出数据】

输出一行n个整数, 第i个整数表示s = i时的答案。答案模1004535809=479*(2^21)+1。

【样例输入】

3 2
2 1

【样例输出】

3 3 2

【数据范围】

对于 20%的数据, n <= 6。
对于 60%的数据, n <= 50。
对于 100%的数据, n <= 100。

本题严重超纲,既然要涉及到prufer 序???
全场只有一人学过,也就这一人AC了这题
在这里插入图片描述
不知道prufer点击这里

prufer 序的编码方式:迭代删点
设树的大小为 n
n = 2 时, prufer 序列为空
n > 2 时, 每次当前标号最小的叶子结点, 并将与叶子节点相连的结点加入序列中。
prufer 序转树
设序列长度为 n
n = 0 时,显然是一个{1-2}
n > 0 时, 令设一个集合 G= {1, 2, 3, …, n}, 每次找出集合中最小的不在当前 prufer
序中出现的点, 将它和 prufer 序的第一个点连边, 在集合中删去该点, 删去 prufer 序的
第一位。
prufer 序和树存在一一对应的关系。

#include <cstdio>
typedef long long ll;
const int Mod = 1004535809;
const int MaxN = 110;
int n, dp[MaxN][MaxN][MaxN], A[MaxN], fac[MaxN], Ie[MaxN];
int pow(int x, int k) {
	ll res = 1, r = x;
	for (; k; k >>= 1, r = r * r % Mod)
		if (k & 1) res = res * r % Mod;
	return res;
}
int main() {
	freopen("tree.in", "r", stdin);
	freopen("tree.out", "w", stdout);
	scanf("%d", &n);
	for (int i = 1; i <= n; ++i) {
		scanf("%d", &A[i]);
		if (A[i] >= n) A[i] = n - 1;
	}
	
	fac[0] = 1;
	for (int i = 1; i <= n; ++i) fac[i] = (ll)fac[i - 1] * i % Mod;
	
	Ie[n] = pow(fac[n], Mod - 2);
	for (int i = n; i; --i) Ie[i - 1] = (ll)Ie[i] * i % Mod;
	
	dp[0][0][0] = 1;
	for (int i = 1; i <= n; ++i) {
		for (int j = 0; j < i; ++j) {
			for (int k = 0; k <= n; ++k) 
				if (dp[i - 1][j][k]) {
					if ((dp[i][j][k] += dp[i - 1][j][k]) >= Mod) dp[i][j][k] -= Mod;
					for (int c = 0; c < A[i]; ++c) {
						if (k + c > n) break;
						if ((dp[i][j + 1][k + c] += (ll)dp[i - 1][j][k] * Ie[c] % Mod) >= Mod) dp[i][j + 1][k + c] -= Mod;
					}
				}
		}
	}
	printf("%d ", n);
	for (int i = 2; i <= n; ++i) printf("%d ", (ll)dp[n][i][i - 2] * fac[i - 2] % Mod);
	fclose(stdin);
	fclose(stdout);
}

laal
那好像就结束了~
所以

~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值