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;
A.cpp

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;
B.cpp

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;
C.cpp

 

以上是关于HGOI20190813 省常中互测6的主要内容,如果未能解决你的问题,请参考以下文章

20160612-20160618记一周省常中训练(持续更新,欢迎催更)

cogs 1075. [省常中2011S4] 最短路径问题

HGOI 20191106 题解

美食日记20190813-红烧排骨

HZOI20190813 B,任(duty)

HGOI 20181028 题解