Solution Set - 杂题分享
A.[THUPC2018]淘米神的树
先考虑开局只有一个黑点,将黑点做根,问有多少种排列满足父亲在儿子前。很平凡的问题,设
再考虑两个黑点,建一个虚点为根,连上两个黑点,一开始虚点为黑点,一此操作后,a,b为黑点,与原问题等价。现在树成为基环树。枚举删除一条环上的边的答案,当环上的某点作为环上最后一个染黑的点会在断左边边和断右边边中重复出现,答案要乘
处理出除环上点的子树大小,给环上点重编号,虚点是0,其他点一次是1~k,设
对a做前缀和为c
可多项式快速插值,时间复杂度
B.Mergesort Strikes Back
题意
一个长度为
题解
在每个片段中,合并时的相对顺序保持不变,因此它们所贡献的期望逆序对个数只是
首先考虑对两个序列归并,找到比前位所有元素的大的元素,一次为分块开头,对每个块以开头元素的大小排序。而对归并k层分成的
再预处理一些东西,可以在
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAX=2e5+10;
#define int long long
int n,k,mod,x,y,cnt[MAX],f[MAX],ans,inv;vector<int>v;
inline int read(){
int x=0,f=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c<='9'&&c>='0'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
inline int power(int a,int b){
int res=1;
while(b){
if(b&1) res=res*a%mod;
a=a*a%mod;b>>=1;
}return res;
}
void solve(int l,int r,int k){
if(k==1){v.push_back(r-l+1);return;}
int mid=l+r>>1;
solve(l,mid,k-1);solve(mid+1,r,k-1);
}inline int work(int x,int y){
int res=x*y%mod*inv%mod;
for(int i=1;i<=y;++i) res=(res-f[i+x]+f[i])%mod;
return res;
}
signed main(){
n=read();k=read();mod=read();inv=mod+1>>1;
solve(1,n,k);
x=v[0];
for(auto i:v){
if(i!=x){
y=i;cnt[y]++;
}else cnt[x]++;
}ans=x*(x-1)%mod*inv%mod*inv%mod*cnt[x]%mod;
ans=(ans+y*(y-1)%mod*inv%mod*inv%mod*cnt[y]%mod)%mod;
for(int i=1;i<=n*2;++i) f[i]=(f[i-1]+power(i,mod-2))%mod;
ans=(ans+work(x,x)*cnt[x]%mod*(cnt[x]-1)%mod*inv)%mod;
ans=(ans+work(x,y)*cnt[x]%mod*cnt[y])%mod;
ans=(ans+work(y,y)*cnt[y]%mod*(cnt[y]-1)%mod*inv)%mod;
ans=(ans+mod)%mod;
cout<<ans;
}
Bonus: solve this problem when the segments' length can be arbitary and
首先一个显然,
C.Cutting the Line
D.Expected Earnings
题意
袋子里有
求以最优方案操作时获得的最大期望收益。
题解
数据范围使我们可以
现在考虑优化B的计算。这个式子没什么前途,我们给
设
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAX=10010;
const double eps=1e-18;
int n,p;double c,q[2][MAX],dp[2][MAX],P;
inline int read(){
int x=0,f=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c<='9'&&c>='0'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int main(){
n=read();c=read()*0.000001;
for(int i=0;i<=n;++i) q[p][i]=read()*0.000001;
for(int i=n-1;i>=0;--i){
p^=1;memset(dp[p],0,sizeof(dp[p]));
memset(q[p],0,sizeof(q[p]));
for(int j=0;j<=i;++j){
q[p][j]=q[p^1][j+1]*(j+1)/(i+1)+q[p^1][j]*(i+1-j)/(i+1);
if(q[p][j]<eps) continue;
P=q[p^1][j+1]*(j+1)/q[p][j]/(i+1);
dp[p][j]=max(0.0,P*(dp[p^1][j+1]+1)+(1-P)*(dp[p^1][j])-c);
// cout<<i<<' '<<j<<" "<<dp[p][j]<<endl;
}
}printf("%.10f",dp[p][0]);
}
E.In a Trap
怎么我找到的题都这么简单啊T_T
题意
有一颗以1为根的树,每个点上有一个点权ai,每次询问路径u到v上最大的
题解
从 u 到 v 的路径可以分为 256 个节点的块和(可能)少于 256 个节点的单个块。我们可以通过迭代其所有节点来单独考虑最后一个块。将u点及向上255个点分为一块,设
怎么水了
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAX=50010;
int n,q,x,y,a[MAX],f[MAX][300],dep[MAX],fa[MAX],tr[1<<20][2],vis[1<<20],las[MAX],tot,head[MAX];
struct edge{
int t,nxt;
} e[MAX<<1];
inline int read(){
int x=0,f=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c<='9'&&c>='0'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
inline void add(int x,int y){
for(int i=16,p=0;i>=0;--i){
int u=(x>>i)&1;
if(!tr[p][u]) tr[p][u]=++tot;
p=tr[p][u];vis[p]=y;
}
}inline int ask(int x,int y){
int res=0;
for(int i=16,p=0;i>=0;--i){
int u=(x>>i)&1;
if(vis[tr[p][u^1]]==y){
res|=1<<i;p=tr[p][u^1];
}else p=tr[p][u];
}return res;
}
void dfs(int u,int dad){
fa[u]=dad;dep[u]=dep[dad]+1;
if(dep[u]>=256){
int i;
for(i=u;dep[u]-dep[i]<256;i=fa[i])
add(a[i]^(dep[u]-dep[i]),u);
las[u]=i;
for(int i=0;i<256;++i) f[u][i]=ask(i<<8,u);
}for(int i=head[u];i;i=e[i].nxt){
int v=e[i].t;
if(v==dad) continue;
dfs(v,u);
}
}
int main(){
n=read();q=read();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<n;++i){
x=read();y=read();
e[++tot]={y,head[x]};head[x]=tot;
e[++tot]={x,head[y]};head[y]=tot;
}tot=0;dfs(1,0);
while(q--){
x=read();y=read();int ans=0,dis=0;
while(dep[y]-dep[x]>=256){
ans=max(ans,f[y][dis]);++dis;y=las[y];
}dis<<=8;
while(y!=fa[x]){
ans=max(ans,a[y]^dis);++dis;y=fa[y];
}printf("%d\n",ans);
}
}
F.Become Big For Me
题意
有序列a,通过乘除序列子集的lcm,将1变成
题解
用lcm表示gcd,可以想到
首先想办法将
对于要求gcd,拆开每一位质数考虑,我们选取它两两相加最小,及最小次和次小次相加。考虑最小值是全序列除去非最小值的最小值,次小值是序列除去最小值的最小,找次小值可以枚举删掉序列一个数的最小值减最小值的和再加上最小值。于是将
子集总大小最大是
点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int MAX=1e5+10,V=1e6;
// #define int long long
int n,a[MAX],G,pre[MAX],nxt[MAX],cur,cnt;
vector<int>c;vector<pair<int,vector<int> > >ans;
inline int read(){
int x=0,f=1;char c=getchar();
while(c>'9'||c<'0'){if(c=='-')f=-1;c=getchar();}
while(c<='9'&&c>='0'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int gcd(int a,int b){
if(!b) return a;
return gcd(b,a%b);
}
inline void solve(vector<int>res,int op){
int n=res.size();vector<int>a;op^=1;
for(int i=1;i<1<<n;++i){
a.clear();
for(int j=0;j<n;++j)
if((i>>j)&1) a.push_back(res[j]);
ans.push_back({(__builtin_popcount(i)&1)^op,a});
}
}
inline vector<int>work(int x,int G){
vector<int>res;int Q=a[x%n+1];
res.push_back(x%n+1);
for(int i=2;i<=V;++i){
int w1=1,w2=1;
while(G%i==0) w1*=i,G/=i;
while(Q%i==0) w2*=i,Q/=i;
if(w1==w2) continue;
for(int j=1;j<=n;++j)
if(j!=x&&(a[j]/w1)%i){
Q=gcd(Q,a[j]);res.push_back(j);break;
}
}return res;
}
signed main(){
n=read();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i) pre[i]=gcd(pre[i-1],a[i]);
for(int i=n;i;--i) nxt[i]=gcd(nxt[i+1],a[i]);
G=pre[n];cnt=2;
for(int i=1;i<=n;++i){
cur=gcd(pre[i-1],nxt[i+1]);
// cout<<i<<" "<<cur<<" "<<G<<endl;
if(cur^G){
solve(work(i,cur),0);--cnt;
}
}c=work(0,G);
while(cnt>0) --cnt,solve(c,0);
while(cnt<0) ++cnt,solve(c,1);
printf("%ld\n",ans.size());
for(auto i:ans){
printf("%d %ld ",i.first,i.second.size());
sort(i.second.begin(),i.second.end());
for(auto j:i.second) printf("%d ",j);
cout<<endl;
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律