#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}\):
那么剩下的问题只有计算总方案数,设 \(f_i\) 表示 \(i\) 的子树大小 \(\geq m\) 的方案数,我们枚举子树大小 \(j\),那么选点的方案数是 \({n-i\choose j-1}\),子树连边的方案数是 \((j-1)!\),\(i\) 连边的方案数是 \((i-1)\),剩下的点连边又是独立的 \((n-j-1)!\),所以有:
时间复杂度 \(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\),由于操作了更大的边权值也会更大,那么答案就是:
其中 \(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);
}