这回讲了点简单的动态规划,终于写的出来blog了
gym105239I Path And k Vertices
题面:有一个\(n\)个点的树,每个点有点权\(a_i\),可以在任意叶子节点到根节点的路径中选\(k\)个点,求点权和的最大值。
题解:DFS的时候使用数据结构分别维护该节点到根的最大的\(k\)个点和该节点到根的剩下点,本人使用std::set
。
复杂度\(\text O(n(\log k+\log n))\)
话说这真的是DP吗
代码:
#include<cstdio>
#include<set>
#define int long long
const int N=300005;
int n,head[N],pedge,root,now,ans,w[N],od[N],k;
std::set<int>s1,s2;
struct Edge{
int to,next;
}edge[N];
inline void ins(int u,int v){edge[++pedge]={v,head[u]},head[u]=pedge;}
inline void Max(int&x,int y){x<y&&(x=y);}
inline bool cmp(int x,int y){return x>y;}
void dfs(int u){
now+=w[u],s1.insert(w[u]);
if(s1.size()>k)now-=*s1.begin(),s2.insert(*s1.begin()),s1.erase(*s1.begin());
if(!od[u])Max(ans,now);
for(int e=head[u];e;e=edge[e].next)dfs(edge[e].to);
if(s1.find(w[u])!=s1.end()){
now-=w[u],s1.erase(w[u]);
if(s1.size()<k&&s2.size())now+=*s2.rbegin(),s1.insert(*s2.rbegin()),s2.erase(*s2.rbegin());
}
else s2.erase(w[u]);
}
signed main(){
scanf("%lld%lld",&n,&k);
for(int i=1,u,t;i<=n;i++){
scanf("%lld%lld",&u,&t),w[i]=t;
if(u)ins(u,i),od[u]++;
else root=i;
}
dfs(root),printf("%lld\n",ans);
return 0;
}
gym105239C Colored Tree
题面:有\(n\)个点,点有点权\(w_i\)和颜色\(c_i\),定义好的集合满足:点集中的点不能相邻,点集中的颜色互不相同。求好的集合的点权和最大值。
题解:定义\(f(u,S,t)\),表示节点\(u\)的状态为\(t\)(\(t=0\)表示不选择\(u\),\(t=1\)表示选择\(u\)),且\(u\)的子树的选取的集合构成的颜色集合。
每次让\(u\)的字节点\(v\)更新\(u\)的答案有转移方程
\(f(u,S,1)=\max_{T\sube S}(f(v,T,0)+f(u,S\setminus T,1))\)
\(f(u,S,0)=\max_{T\sube S}(\max(f(v,T,0),f(v,T,1))+f(u,S\setminus T,1))\)
复杂度\(\text O(n3^{|C|})\)
代码:
#include<cstdio>
#define int long long
const int N=1005,A=1<<10,M=A+5;
int n,w[N],c[N],head[N],pedge,f[N][M][2],C,ans;
struct Edge{
int to,next;
}edge[N<<1];
inline void Ins(int u,int v){edge[++pedge]={v,head[u]},head[u]=pedge;}
inline void ins(int u,int v){Ins(u,v),Ins(v,u);}
inline void Max(int&x,int y){x<y&&(x=y);}
inline int max(int x,int y){return x>y?x:y;}
void dfs(int u,int father){
f[u][1<<c[u]][1]=w[u];
for(int e=head[u];e;e=edge[e].next){
int v=edge[e].to;
if(v^father){
dfs(v,u);
for(int s=(1<<C)-1;s;s--)for(int t=s;t;t=(t-1)&s)Max(f[u][s][0],max(f[v][t][0],f[v][t][1])+f[u][s^t][0]),Max(f[u][s][1],f[v][t][0]+f[u][s^t][1]);
}
}
}
signed main(){
scanf("%lld%lld",&n,&C);
for(int i=1;i<=n;i++)scanf("%lld",w+i);
for(int i=1;i<=n;i++)scanf("%lld",c+i),c[i]--;
for(int i=1,u,v;i<n;i++)scanf("%lld%lld",&u,&v),ins(u,v);
dfs(1,0);
for(int i=0;i<1<<C;i++)Max(ans,max(f[1][i][0],f[1][i][1]));
return printf("%lld\n",ans),0;
}
gym105244G Evolutionary Tree Weights
题面:有\(G\)个由A
,G
,C
,T
组成长度为\(m\)的字符串,\(T\)个询问,每次询问给出一棵树和树的叶节点分别对应哪些字符串,定义边权为两条边所对应的字符串不同的位置的个数。你可以在除了叶子节点的每一个点填上一个由A
,G
,C
,T
组成长度为\(m\)的字符串,求字符串树的边权和的最小值。
题解:发现每一位对答案的影响独立,分别讨论每一位,令\(f(u,c)\)表示节点\(u\)填\(c\)后子树的边权和,那么对\(u\)的子节点\(v\),\(f(u,c)=\sum_{v\in son(u)}\max_{k=0}^3(f(v,k)+[k\neq c])\)
代码:
#include<cstdio>
#include<cstring>
#define int long long
const int N=505,INF=0x3f3f3f3f;
char s[N][N];
int g,k,T,n,m,head[N],pedge,a[N],b[N],f[N][4],ans;
bool od[N];
struct Edge{
int to,next;
}edge[N];
inline void ins(int u,int v){edge[++pedge]={v,head[u]},head[u]=pedge;}
inline void Min(int&x,int y){y<x&&(x=y);}
inline int min(int x,int y){return x<y?x:y;}
inline int min(int x,int y,int z,int w){return min(min(x,y),min(z,w));}
inline int trans(char c){
return c=='A'?0:c=='G'?1:c=='C'?2:3;
}
void dfs(int u){
if(od[u])return;
f[u][0]=f[u][1]=f[u][2]=f[u][3]=0;
for(int e=head[u];e;e=edge[e].next){
int v=edge[e].to;
dfs(v);
for(int c=0;c<4;c++){
int minn=INF;
for(int k=0;k<4;k++)Min(minn,f[v][k]+(k!=c));
f[u][c]+=minn;
}
}
}
signed main(){
scanf("%lld",&g);
for(int i=1;i<=g;i++)scanf("%s",s[i]+1);
for(k=strlen(s[1]+1),scanf("%lld",&T);T--;){
scanf("%lld%lld",&n,&m);
if(n==0){
puts("0");
continue;
}
pedge=ans=0;
for(int i=1;i<=n;i++)head[i]=od[i]=0;
for(int i=2,p;i<=n;i++)scanf("%lld",&p),ins(p,i);
for(int i=1;i<=m;i++)scanf("%lld%lld",a+i,b+i),od[a[i]]=1;
for(int i=1;i<=k;i++){
for(int j=1;j<=m;j++)f[a[j]][0]=f[a[j]][1]=f[a[j]][2]=f[a[j]][3]=INF,f[a[j]][trans(s[b
[j]][i])]=0;
dfs(1),ans+=min(f[1][0],f[1][1],f[1][2],f[1][3]);
}
printf("%lld\n",ans);
}
return 0;
}
gym105173I Password
题面:一个长度为\(n\)的序列\(a\),每一项都是\(1\)到\(k\)的整数,对于每一个长度为\(k\)的子区间,如果内容为\(1\)到\(k\)的排列,那么是好的子区间。问有多少种序列满足每个数至少被包含在一个子区间中。
题解:
令\(f(i)\)表示长度为\(i\)的满足所有位置被至少一个好的子区间包含的方案数。\(g(i)\)表示在一个\(1\)到\(k\)的排列插入\(i\)个数后最后\(k\)个数仍然是一个\(1\)到\(k\)的排列,且插入\(i-1\)个数后不是一个\(1\)到\(k\)的排列的方案数。
时间复杂度\(\text O(nk)\)
代码:
#include<cstdio>
#define int long long
const int N=100005,mod=998244353;
int n,k,fac[N],f[N],g[N];
inline int mul(int x,int y){return x*y%mod;}
inline void Sub(int&x,int y){if((x-=y)<0)x+=mod;}
inline void Add(int&x,int y){if((x+=y)>=mod)x-=mod;}
signed main(){
scanf("%lld%lld",&n,&k),fac[0]=1;
for(int i=1;i<=k;i++)fac[i]=mul(fac[i-1],i);
for(int i=1;i<=k;i++){
g[i]=fac[i];
for(int j=1;j<i;j++)Sub(g[i],mul(fac[j],g[i-j]));
}
f[k]=fac[k];
for(int i=k;i<=n;i++)for(int j=1;j<=k&&i+j<=n;j++)Add(f[i+j],mul(f[i],g[j]));
printf("%lld\n",f[n]);
return 0;
}
gym105139K Points on the Number Axis B
题面:有\(n\)个数,每次等概率的选择一对相邻的数对\((x_i,x_{i+1})\),替换成\((x_i+x_{i+1})/2\),求最后的数的期望。
题解:现将求期望转化成求所有方案的总和除以总方案数\((n-1)!\),令第\(i\)个数对方案总和的贡献为\(a_i\times f(i-1,n-i)\),其中\(f(i,j)\)表示一个数左边有\(i\)个右边有\(j\)个数后对所有方案总和的贡献。
类似与网格图路径计数可以得出:
\[f(i,j)={i+j\choose i}\prod_{k=1}^i\left(k-\frac12\right)\prod_{k=1}^j\left(k-\frac12\right) \]预处理后复杂度\(\text O(n+\log p)\)
代码:
#include<cstdio>
#define int long long
const int N=1000005,mod=998244353,inv2=(mod+1)/2;
int n,fac[N],pro[N],finv[N],a[N],ans;
inline int mul(int x,int y){return x*y%mod;}
inline int mul(int x,int y,int z){return mul(mul(x,y),z);}
inline int mul(int x,int y,int z,int w){return mul(mul(x,y),mul(z,w));}
inline void Mul(int&x,int y){x=mul(x,y);}
inline void Add(int&x,int y){if((x+=y)>=mod)x-=mod;}
inline void Sub(int&x,int y){if((x-=y)<0)x+=mod;}
inline int sub(int x,int y){return Sub(x,y),x;}
inline int pow(int x,int y){
int ret=1;
for(;y;Mul(x,x),y>>=1)if(y&1)Mul(ret,x);
return ret;
}
inline int inv(int x){return pow(x,mod-2);}
inline int C(int x,int y){return mul(fac[x],finv[y],finv[x-y]);}
signed main(){
scanf("%lld",&n),fac[0]=pro[0]=1;
for(int i=1;i<=n;i++)scanf("%lld",a+i),fac[i]=mul(fac[i-1],i),pro[i]=mul(pro[i-1],sub(i,inv2));
finv[n]=inv(fac[n]);
for(int i=n;i;i--)finv[i-1]=mul(finv[i],i);
for(int i=1;i<=n;i++)Add(ans,mul(a[i],pro[i-1],pro[n-i],C(n-1,i-1)));
return printf("%lld\n",mul(ans,finv[n-1])),0;
}
标签:int,void,long,学习,20240807,inline,mul,mod
From: https://www.cnblogs.com/junjunccc/p/18349052