HGOI20190813 省常中互测6
Posted ljc20020730
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HGOI20190813 省常中互测6相关的知识,希望对你有一定的参考价值。
Problem A 蛋糕
将$n \times m $大小的蛋糕切成每块为$1 \times 1$大小的$n\times m$块。
交换任意两块蛋糕的切割顺序的方案算作一种。
对于$100 \%$的数据满足$1 \leq n,m \leq 300$
Solution : 一个比较明显的DP
设$f[i][j]$表示蛋糕大小为$i \times j$时候的答案。
当前步可以在第$k(1\leq k \leq i-1)$行切一刀分成$[1,k]$和$[k+1,i]$两部分;
或者可以在第$k(q\leq k \leq j-1)$列切一刀分成$[1,k]$和$[k+1,j]$ 两部分。
问题就可以转化为两个子问题了(由乘法原理可以合并),然后把所有子问题相加就是最后的答案。
枚举状态是$O(n^2)$,然后枚举转移是$O(n)$的复杂度,总复杂度是$O(n^3)$
# include<bits/stdc++.h> # define int long long using namespace std; const int N=305,mo=1e9+7; int f[N][N]; int dfs(int i,int j) if (i==1 && j==1) return 1; if (i<0 || j<0) return 0; if (f[i][j]!=-1) return f[i][j]; int ret=0; for (int k=1;k<=i-1;k++) ret=(ret+dfs(k,j)*dfs(i-k,j)%mo)%mo; for (int k=1;k<=j-1;k++) ret=(ret+dfs(i,k)*dfs(i,j-k)%mo)%mo; return f[i][j]=ret; signed main() int n,m; scanf("%lld%lld",&n,&m); memset(f,-1,sizeof(f)); printf("%lld\n",dfs(n,m)); return 0;
Problem B 找钱
有$n$种面值为$a_i$的纸币,小L手上有$b_i$张,而商店手上有$c_i$张。
小L到商店去购买价格为$X$的商品,问有多少种不同的付钱-找钱的方法。
一种不同的且合法的方案满足,小$L$付出的纸币必须是必要的且付钱方案或者找钱的方案有不同。
对于$100\%$的数据满足$n \leq 10^3 ,\leq a_i,b_i,c_i \leq 10^4$
Solution : 还是一个比较明显的(背包)DP
设$f[i][j]$表示小L在后$n$种纸币(纸币币值需要单调)中找出钱数位$j$的方案数。
设$g[i][j]$表示商店在前$i$种纸币中找出钱数为$j$的方案数。
可以有一个显然的多重背包的转移,
$g[i][j]=g[i][j]+g[i-1][j-k*a[i]] , f[i][j]=f[i][j]+f[i+1][j-k*a[i]]$
初始值为$f[n+1][0] = 1, g[0][0] = 1$ ,复杂度大概是$O(n X \sum c[i])$
上面的DP可以用前缀和优化,上一层的转移区间每一次会向右平移$a[i]$个单位,于是我们只需要计算上一个区间里转移区间内的和即可。
所以我们只需要枚举余数$j$和跳$a[i]$单位的步数$k$即可用$j+k\times a[i]$拼成一个物品了。
具体来说,若把上一层转移而来的元素记为$j$那么,当前元素就是$j + k\times a[i]$,当前元素的转移区间就是$[j,j+k\times a[i]]$
所以,每当$k$+1,转移区间就向右平移$a[i]$个单位,于是我们只需要维护一个变量$Sum$记录一下$i-1$层的转移区间的和即可。每一次滑动$O(1)$计算。
用上述方法,我们可以将复杂度优化到$O(n Max\a_i , X\)$ 。
# include <bits/stdc++.h> # define ll long long using namespace std; const int mo=1e9+7; int a[1010],b[1010],c[1010]; int f[1010][20010],n,m,g[1010][20010]; signed main() // freopen("deal12.in","r",stdin); // freopen("deal13.out","w",stdout); scanf("%d%d",&n,&m); int Lim=m; for (int i=1;i<=n;i++) scanf("%d%d%d",&a[i],&b[i],&c[i]),Lim=max(Lim,a[i]); g[0][0]=1; // for (int i=1;i<=n;i++) // for (int j=0;j<=m;j++) // for (int k=0;k<=min(j/a[i],c[i]);k++) // g[i][j]=(g[i][j]+g[i-1][j-k*a[i]])%mo; // // for (int i=1;i<=n;i++) for (int j=0;j<=a[i]-1;j++) int sum=0; for (int k=0;j+k*a[i]<=Lim;k++) sum=(sum+g[i-1][j+k*a[i]])%mo; if (k>c[i]) sum=((sum-g[i-1][j+a[i]*(k-c[i]-1)])%mo+mo)%mo; g[i][j+a[i]*k]=sum; f[n+1][0]=1; // for (int i=n;i>=1;i--) // for (int j=0;j<=2*m;j++) // for (int k=0;k<=min(j/a[i],b[i]);k++) // f[i][j]=(f[i][j]+f[i+1][j-k*a[i]])%mo; // // for (int i=n;i>=1;i--) for (int j=0;j<=a[i]-1;j++) int sum=0; for (int k=0;j+k*a[i]<=2*Lim;k++) sum=(sum+f[i+1][j+k*a[i]])%mo; if (k>b[i]) sum=((sum-f[i+1][j+a[i]*(k-b[i]-1)])%mo+mo)%mo; f[i][j+a[i]*k]=sum; ll ans=0; for (int i=0;i<=Lim;i++) int w=0; for (int j=1;j<=n;j++) if (a[j]>i) w=j; break; if (w) ans=(ans+(ll)g[n][i]*f[w][m+i]%mo)%mo; printf("%lld\n",ans); return 0;
Problem C 城镇
初始离散的$n$个点,每次添加$1$条边,共添加$n-1$次构成一棵树。
每一次连边之后,输出在该边所在的连通块中的直径为多少。
对于$100 \%$的数据 $n \leq 3\times 10^5$
Solution :
首先想到每一次合并直径,若一个连通块中直径为$s1,t1$,另外一个连通块中直径为$s2,t2$
那么合并后的连通块直径为$s1,t1,s2,t2$四个点中取2个点组成路径的最长路。
如果不是这样,那么必然存在一条更加长的直径,与直径的最长性矛盾,所以定理显然成立。
于是我们就可以用并查集维护连通块。
我们发现每次合并时将size较小的块合并到size较大的块上即可。
这样子做的复杂度是$O(n \ log_2 \ n)$
由于还有求$LCA$所以本题的时间复杂度是$O(n \ log_2^2 \ n)$
# include<bits/stdc++.h> using namespace std; const int N=3e5+10; struct rec int pre,to; a[N<<1]; struct A int size,s,t; mem[N]; int head[N],tot,gi[N],rec[N],dep[N],g[N][22]; int n; void adde(int u,int v) a[++tot].pre=head[u]; a[tot].to=v; head[u]=tot; int father(int x) if (gi[x]==x) return x; return gi[x]=father(gi[x]); void dfs(int u,int fa) rec[++rec[0]]=u; g[u][0]=fa; dep[u]=dep[fa]+1; for (int i=head[u];i;i=a[i].pre) int v=a[i].to; if (v==fa) continue; dfs(v,u); int lca(int u,int v) if (dep[u]<dep[v]) swap(u,v); for (int i=21;i>=0;i--) if (dep[g[u][i]]>=dep[v]) u=g[u][i]; if (u==v) return u; for (int i=21;i>=0;i--) if (g[u][i]!=g[v][i]) u=g[u][i],v=g[v][i]; return g[u][0]; int getdist(int u,int v) int l=lca(u,v); return dep[u]+dep[v]-2*dep[l]; int main() scanf("%d",&n); for (int i=1;i<=n;i++) gi[i]=i,mem[i].size=1,mem[i].s=mem[i].t=i; int m=n-1; while (m--) int u,v; scanf("%d%d",&u,&v); int fx=father(u),fy=father(v); if (mem[fx].size>mem[fy].size) swap(fx,fy),swap(u,v); adde(u,v); adde(v,u); rec[0]=0; dfs(u,v); gi[fx]=fy; mem[fy].size+=mem[fx].size; for (int i=1;i<=21;i++) for (int j=1;j<=rec[0];j++) int u=rec[j]; g[u][i]=g[g[u][i-1]][i-1]; int t1=mem[fx].s,t2=mem[fx].t,t3=mem[fy].s,t4=mem[fy].t; int d1=getdist(t1,t3),d2=getdist(t1,t4); int d3=getdist(t2,t3),d4=getdist(t2,t4); int d5=getdist(t1,t2),d6=getdist(t3,t4); int num=0,d=0; if (d1>d) num=1,d=d1; if (d2>d) num=2,d=d2; if (d3>d) num=3,d=d3; if (d4>d) num=4,d=d4; if (d5>d) num=5,d=d5; if (d6>d) num=6,d=d6; if (num==1) mem[fy].s=t1,mem[fy].t=t3; else if (num==2) mem[fy].s=t1,mem[fy].t=t4; else if (num==3) mem[fy].s=t2,mem[fy].t=t3; else if (num==4) mem[fy].s=t2,mem[fy].t=t4; else if (num==5) mem[fy].s=t1,mem[fy].t=t2; else if (num==6) mem[fy].s=t3,mem[fy].t=t4; printf("%d\n",d); return 0;
以上是关于HGOI20190813 省常中互测6的主要内容,如果未能解决你的问题,请参考以下文章