冲刺清北营 10

是这样的

九转大肠

突然不会分析复杂度了,结果调了一个多小时。被自己菜到。

假如我们已经知道父亲的答案,那么算出经过每种数量个节点的方案数就能算出儿子的答案。我们需要计算一个经过某种数量节点的方案,还要记一个经过了几个儿子来乘上阶乘。那直接背包,设 \(dp_{i,j}\) 为走了 \(i\) 个儿子,一共走了 \(j\) 个节点的方案数,容易发现转移每个儿子复杂度都是 \(O(n^2)\),而有 \(n\) 个节点,因此总复杂度 \(O(n^3)\) 是对的。

统计答案的时候要删掉这个儿子的答案,做个带删背包就好了。具体怎么删就是把 01 背包的循环倒过来做(形如完全背包)。

好像轻微卡常,场上挂了个火车头。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
using namespace std;
const int mod=998244353;
int n,size[510],jc[510],cnt[510],mul[510],dp[510][510],ans[510][510],inv[510],invmul[510];
struct node{
    int v,next;
}edge[510];
int t,head[510];
void add(int u,int v){
    edge[++t].v=v;edge[t].next=head[u];head[u]=t;
}
void dfs1(int x,int f){
    size[x]=mul[x]=invmul[x]=1;
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f){
            cnt[x]++;
            dfs1(edge[i].v,x);
            size[x]+=size[edge[i].v];
            mul[x]=1ll*mul[edge[i].v]*mul[x]%mod;
            invmul[x]=1ll*invmul[x]*invmul[edge[i].v]%mod;
        }
    }
    mul[x]=1ll*mul[x]*jc[cnt[x]]%mod;
    invmul[x]=1ll*invmul[x]*inv[cnt[x]]%mod;
}
void dfs2(int x,int f,int val){
    for(int i=0;i<=cnt[x];i++)for(int j=0;j<size[x];j++)dp[i][j]=0;
    dp[0][0]=1;
    static int tmp[510];tmp[0]=0;int ret=1;
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f)tmp[++tmp[0]]=edge[i].v,ret=1ll*ret*mul[edge[i].v]%mod;
    }
    for(int i=1;i<=tmp[0];i++){
        for(int j=i;j>=1;j--){
            for(int k=size[x]-1;k>=size[tmp[i]];k--)if(dp[j-1][k-size[tmp[i]]])dp[j][k]=(dp[j][k]+dp[j-1][k-size[tmp[i]]])%mod;
        }
    }
    static int sum[510];
    for(int i=1;i<=tmp[0];i++){
        for(int j=1;j<=tmp[0];j++){
            for(int k=size[tmp[i]];k<size[x];k++)dp[j][k]=(dp[j][k]-dp[j-1][k-size[tmp[i]]]+mod)%mod;
        }
        for(int j=0;j<=n;j++)sum[j]=0;
        for(int j=0;j<tmp[0];j++){
            for(int k=0;k<size[x];k++)sum[k]=(sum[k]+1ll*dp[j][k]*jc[j]%mod*jc[tmp[0]-1-j])%mod;
        }
        for(int j=0;j<=n;j++){
            for(int k=0;j+k+1<=n;k++){
                ans[tmp[i]][j+k+1]=(ans[tmp[i]][j+k+1]+1ll*ans[x][j]*sum[k]%mod)%mod;
            }
        }
        for(int j=tmp[0];j>=1;j--){
            for(int k=size[x]-1;k>=size[tmp[i]];k--)if(dp[j-1][k-size[tmp[i]]])dp[j][k]=(dp[j][k]+dp[j-1][k-size[tmp[i]]])%mod;
        }
    }
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f){
            dfs2(edge[i].v,x,1ll*val*ret%mod*invmul[edge[i].v]%mod);
            for(int j=0;j<=n;j++)ans[edge[i].v][j]=1ll*ans[edge[i].v][j]*ret%mod*val%mod;
        }
    }
}
int main(){
    scanf("%d",&n);jc[0]=inv[0]=inv[1]=1;
    for(int i=2;i<=n;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
    for(int i=1;i<=n;i++)jc[i]=1ll*jc[i-1]*i%mod,inv[i]=1ll*inv[i-1]*inv[i]%mod;
    for(int i=2;i<=n;i++){
        int f;scanf("%d",&f);add(f,i);
    }
    dfs1(1,0);ans[1][1]=1;
    dfs2(1,0,1);ans[1][1]=mul[1];
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            printf("%d ",ans[i][j]);
        }
        puts("");
    }
    return 0;
}

原本味道

看到题目最后一句话我直接原地跑路。哦我不会啊那没事了。

首先按照时间轴构造序列 \(t\) 满足 \(t_i\) 为该时间右端点移动值减左端点移动值(不管左端点在右端点前的限制)。每次可以直接得到右端点的位置,只需要找到左端点和右端点的距离。

设这个距离为 \(d\),那么按照时间轴操作的话就是每次 \(d\) 变成 \(\max(d+t_i,1)\)。容易发现答案就是最后一次 \(d=1\) 的时间之后的 \(t\) 后缀和。通过反证不难证明这也是 \(t\) 的最大后缀和。

显然 \(t\) 有循环节 \(\text{lcm}(a_1+b_1,a_2+b_2)\)。可以预处理一个循环节每个前缀的后缀 \(\max\),然后后缀 \(\max\) 的位置就只有三种情况:最后的散段,倒数第二段的一段,开头的段。

循环节貌似很大,然而 \(t\) 只会在 \(k(a_1+b_1),k(a_2+b_2),k(a_1+b_1)+a_1,k(a_2+b_2)+a_2\) 这样的点处改变,那么记下这些点的位置就可以快速跳循环节了。

代码是贺的。

#include <cstdio>
#include <algorithm>
#include <iostream>
#include <cstring>
#include <vector>
#include <queue>
#define int long long
using namespace std;
int a1,b1,a2,b2,q,n,mx[10000010],sum[10000010];
struct node{
    int v,x;
    bool operator<(const node&s)const{
        return v<s.v;
    }
}val[10000010];
int gcd(int a,int b){
    return b?gcd(b,a%b):a;
}
signed main(){
    scanf("%lld%lld%lld%lld%lld",&a1,&b1,&a2,&b2,&q);
    int lcm=(a1+b1)*(a2+b2)/gcd(a1+b1,a2+b2);
    int len1=lcm/(a1+b1),len2=lcm/(a2+b2);
    val[0]={0,0};int cnt=0,pre=0;
    for(int i=1,j=1;i<=(len1<<1)&&j<=(len2<<1);){
        int ret=(i>>1)*(a1+b1),tmp=(j>>1)*(a2+b2);
        if(i&1)ret+=a1;
        if(j&1)tmp+=a2;
        int p=(i&1)-(j&1);
        if(ret<tmp)val[++cnt]={ret,p},pre=ret,i++;
        else if(ret>tmp)val[++cnt]={tmp,p},pre=tmp,j++;
        else val[++cnt]={ret,p},pre=ret,i++,j++;
    }
    for(int i=1;i<=cnt;i++){
        mx[i]=max(mx[i-1]+(val[i].v-val[i-1].v)*val[i].x,0ll);
        sum[i]=sum[i-1]+(val[i].v-val[i-1].v)*val[i].x;
    }
    int s=0,mxx=0;
    for(int i=cnt;i>=1;i--){
        s+=(val[i].v-val[i-1].v)*val[i].x;
        mxx=max(mxx,s);
    }
    while(q--){
        int n;scanf("%lld",&n);
        int bel=(n-1)/lcm;n=(n-1)%lcm+1;
        node tmp={n,0};
        int pos=upper_bound(val+1,val+cnt+1,tmp)-val-1;
        int ans=max((n-val[pos].v)*val[pos+1].x+mx[pos],0ll);
        if(bel){
            ans=max(ans,sum[pos]+(n-val[pos].v)*val[pos+1].x+mxx);
            ans=max(ans,sum[pos]+(n-val[pos].v)*val[pos+1].x+mxx+(bel-1)*s);
        }
        int num=(bel*lcm+n-1)/(a1+b1)*a1+min((bel*lcm+n-1)%(a1+b1)+1,a1);
        printf("%lld\n",num-ans);
    }
    return 0;
}

顶级厨师

codechef。

这几天 T3 好像好几个比 T2 好改。

首先考虑 \(O(n^3)\) 的 dp:设 \(dp_{i,j,k}\) 为当前选 \(i=1/2/3\),另外两个分别在 \(j,k\) 上一次选的最大值,随便 dp 一下就行了。

然后发现值域 \(10000\)。发现对于选的某一个,如果 \(O(\sqrt w)\) 次连续都不选那么就会掉到 \(0\),于是后两维是 \(O(\sqrt w)\) 的,总复杂度 \(O(nw)\) 就做完了。

#include <algorithm>
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int n,dp[2][3][210][210],A[1010][3];
int main(){
    int tim;scanf("%d",&tim);
    while(tim--){
        scanf("%d",&n);
        for(int i=1;i<=n;i++)scanf("%d%d%d",&A[i][0],&A[i][1],&A[i][2]);
        memset(dp[0],-0x3f,sizeof(dp[0]));
        dp[0][0][0][0]=dp[0][1][0][0]=dp[0][2][0][0]=0;
        int cur=0;
        for(int i=1;i<=n;i++){
            cur^=1;
            memset(dp[cur],-0x3f,sizeof(dp[cur]));
            for(int a=0;a<3;a++)for(int b=0;b<3;b++)for(int j=0;j<=200;j++)for(int k=0;k<=200;k++){
                if(a==b)dp[cur][b][j+(j!=0)][k+(k!=0)]=max(dp[cur][b][j+(j!=0)][k+(k!=0)],dp[cur^1][a][j][k]+A[i][b]-(j!=0)*(j+1)-(k!=0)*(k+1));
                else{
                    if((a==0&&b==1)||(a==1&&b==2)||(a==2&&b==0))dp[cur][b][k+(k!=0)][1]=max(dp[cur][b][k+(k!=0)][1],dp[cur^1][a][j][k]+A[i][b]-(k!=0)*(k+1)-1);
                    else dp[cur][b][1][j+(j!=0)]=max(dp[cur][b][1][j+(j!=0)],dp[cur^1][a][j][k]+A[i][b]-(j!=0)*(j+1)-1);
                }
            }
        }
        int ans=0;
        for(int i=0;i<3;i++)for(int j=0;j<=200;j++)for(int k=0;k<=200;k++)ans=max(ans,dp[cur][i][j][k]);
        printf("%d\n",ans);
    }
    return 0;
}
posted @ 2023-05-01 20:18  gtm1514  阅读(12)  评论(0编辑  收藏  举报