#16 CF1667E & CF1307G & CF799F

Centroid Probabilities

题目描述

点此看题

解法

小清新计数题,比较适合娱乐。

一开始的想法是把 \(i\) 前面的点当成一个子树,\(i\) 后面的点有若干个子树。然后正难则反,计算某一个子树 \(\geq m=\frac{n+1}{2}\) 的方案数,但是这样只能列出 \(O(n^2)\) 的式子,计算不是很方便。

上面的都是废话,有一个很巧妙的转化:限制可以等价于 \(i\) 的子树大小 \(\geq m\),并且 \(i\) 的儿子子树全部都 \(<m\)

\(dp_i\) 表示 \(i\) 为重心的方案数,计算它可以正难则反,这是由于对于 \(j>i\)\(j\) 作为重心且为 \(i\) 子树的概率是 \(\frac{1}{i}\)

\[dp_i=f_i-\frac{\sum_{j=i}^n dp_j}{i} \]

那么剩下的问题只有计算总方案数,设 \(f_i\) 表示 \(i\) 的子树大小 \(\geq m\) 的方案数,我们枚举子树大小 \(j\),那么选点的方案数是 \({n-i\choose j-1}\),子树连边的方案数是 \((j-1)!\)\(i\) 连边的方案数是 \((i-1)\),剩下的点连边又是独立的 \((n-j-1)!\),所以有:

\[\begin{aligned}f_i&=\sum_{j=m}^{n-i+1}{n-i\choose j-1}\cdot (j-1)!\cdot (i-1)\cdot (n-j-1)!\\&=\sum_{j=m}^{n-i+1}\frac{(n-i)!(n-j-1)!(i-1)}{(n-i-j+1)!}\\&=(n-i)!(i-1)!\sum_{j=m}^{n-i+1}\frac{(n-j-1)!}{(n-i-j+1)!(i-2)!}\\&=(n-i)!(i-1)!\sum_{j=m}^{n-i+1}{n-j-1\choose i-2}\\&=(n-i)!(i-1)\cdot {n-m\choose i-1}\\&=\frac{(n-i)!(n-m)!}{(n-m-i+1)!}\end{aligned} \]

时间复杂度 \(O(n)\)

#include <cstdio>
const int M = 200005;
#define int long long
const int MOD = 998244353;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,sum,f[M],fac[M],inv[M],is[M];
void init(int n)
{
	fac[0]=inv[0]=inv[1]=is[0]=1;
	for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%MOD;
	for(int i=2;i<=n;i++) inv[i]=inv[MOD%i]*(MOD-MOD/i)%MOD;
	for(int i=1;i<=n;i++) is[i]=is[i-1]*inv[i]%MOD;
}
signed main()
{
	n=read();init(n);m=(n+1)>>1;
	for(int i=n-m+1;i>=1;i--)
	{
		f[i]=fac[n-i]*fac[n-m]%MOD*is[n-m-i+1]-sum*inv[i];
		f[i]=(f[i]%MOD+MOD)%MOD;sum=(sum+f[i])%MOD;
	}
	for(int i=1;i<=n;i++)
		printf("%lld ",f[i]);
	puts("");
}

Cow and Exercise

题目描述

点此看题

解法

写一写感性理解的做法,如果要更加严谨还是要用线性规划,但是我不会

首先用最小费用流最大流跑出原图的所有割边,并且我们可以按照路径长度递增的顺序得到所有割边。如果某条路径含有第 \(i\) 条割边那么我们把它划分进第 \(i\) 个路径集(多条割边取最小的 \(i\)

路径集的权值定义为其中长度最小的路径(也就是增广路的权值),那么利用平均的思想,我们一定是操作前 \(k\) 个路径集对应的割边,并使得它们的权值相等

最优的操作方案的要求是:操作之后不能出现权值比前 \(k\) 个路径集小的路径集。就算不知道真实的 \(k\) 也可以算答案,因为对于 \(j<k\),操作后的权值会更大,对于 \(j>k\),由于操作了更大的边权值也会更大,那么答案就是:

\[\min\{\frac{x+sum_j}{j}\} \]

其中 \(sum_j\) 表示前 \(j\) 个路径集的权值和,时间复杂度 \(O(n^4+nq)\)

#include <cstdio>
#include <iostream>
#include <queue>
using namespace std;
const int M = 55;
const int inf = 0x3f3f3f3f;
#define db double
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,q,tot,f[M],dis[M],lst[M],flow[M],pre[M],in[M];
struct edge{int v,f,c,next;}e[2*M*M];vector<db> d;
int bfs()
{
	for(int i=1;i<=n;i++) dis[i]=inf;
	queue<int> q;q.push(1);in[1]=1;
	dis[1]=0;flow[1]=inf;
	while(!q.empty())
	{
		int u=q.front();q.pop();in[u]=0;
		for(int i=f[u];i;i=e[i].next)
		{
			int v=e[i].v,c=e[i].c;
			if(e[i].f && dis[v]>dis[u]+c)
			{
				dis[v]=dis[u]+c;
				pre[v]=u;lst[v]=i;
				flow[v]=min(flow[u],e[i].f);
				if(!in[v]) in[v]=1,q.push(v);
			}
		}
	}
	return flow[n]>0;
}
signed main()
{
	n=read();m=read();tot=1;
	for(int i=1;i<=m;i++)
	{
		int u=read(),v=read(),c=read();
		e[++tot]=edge{v,1,c,f[u]},f[u]=tot;
		e[++tot]=edge{u,0,-c,f[v]},f[v]=tot;
	}
	db sum=0;
	while(bfs())
	{
		sum+=dis[n];
		d.push_back(sum);
		int u=n;
		while(u!=1)
		{
			e[lst[u]].f-=flow[n];
			e[lst[u]^1].f+=flow[n];
			u=pre[u];
		}
	}
	q=read();
	while(q--)
	{
		int x=read();db ans=inf;
		for(int i=0;i<d.size();i++)
			ans=min(ans,1.0*(x+d[i])/(i+1));
		printf("%.8f\n",ans);
	}
}

Beautiful fountains rows

题目描述

点此看题

解法

不知道哪个大佬想到的随机化做法,简直太神仙了好不好。

主要矛盾是第二个限制,一次需要考虑 \(n\) 个区间让我们很难受,我们考虑写出一个共同的判据。

结合异或的奇妙性质,我们考虑给每个区间随机赋权,那么如果 \([a+1,b]\) 和一个区间的交是偶数(也就是 \([a,b]\) 和这个区间没有交或者是交为奇数),得到的异或值是 \(0\),我们把和每个区间得到的结果再异或起来,如果结果的异或值是 \(0\),那么我们就认为是满足第二个限制的(把值域开到 \(2^{64}\) 出错的概率就极其小)

二阶差分可以求出 \(sum_i\) 表示前缀异或和,那么 \([a,b]\) 合法的判据就是 \(sum_a=sum_b\),直接用 \(\tt map\) 做即可。现在再来考虑第一个限制,只需要把和所有区间都不交的部分减掉即可,时间复杂度 \(O(n\log n)\)

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <random>
#include <ctime>
#include <map>
using namespace std;
const int M = 200005;
#define int long long
#define ull unsigned long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
	while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,m,ans,a[M],c[M];ull b[M];
mt19937_64 z(time(0));
signed main()
{
	n=read();m=read();
	for(int i=1;i<=n;i++)
	{
		int l=read(),r=read();ull x=z();
		b[l+1]^=x;b[r+1]^=x;
		a[l]++;a[r+1]--;
	}
	for(int i=1;i<=m;i++)
		a[i]+=a[i-1],b[i]^=b[i-1];
	map<ull,pair<int,int>> mp;
	for(int i=1;i<=m;i++)
	{
		b[i]^=b[i-1];
		pair<int,int> tmp=mp[b[i]];
		tmp.first++;tmp.second+=i-1;
		mp[b[i]]=tmp;
		ans+=tmp.first*i-tmp.second;
	}
	for(int i=1;i<=m;i++)
	{
		c[i]=(a[i]==0)?c[i-1]+1:0;
		ans-=c[i]*(c[i]+1)/2;
	}
	printf("%lld\n",ans);
}
posted @ 2022-05-18 14:50  C202044zxy  阅读(148)  评论(0编辑  收藏  举报