NOI Ag 线题选做

没做 NOI 的现实主义者在 NOI 前最现实的做法当然是做 CNOI 系列。我 NOI 居然没做几个题。

房屋老师来拜访 hzoi 了,拜谢。

我这个实力能不能 Ag 暂时不好说。那先尽量签。

[NOI2020] 美食家

好像签了这个然后剩下五个骗 \(100\) 分就能 Ag。不过这一年的 Day2 十分困难。

首先这玩意如果 \(w=1\) 看起来就是个矩阵乘法的形式,于是广义矩阵乘。然后 \(w\le 5\) 考虑拆点,把每个点拆成 \(5\) 个然后连边。然后美食节单独处理,复杂度是 \(O((5n)^3k\log T)\) 的,看上去很过不去。

然而发现我们最后只需要 \((1,1)\) 位置的值,于是只需要维护一个行向量的乘法即可,加上预处理转移矩阵的 \(2^n\) 次幂,复杂度 \(O((5n)^3\log T+(5n)^2k\log T)\),常数十分小可以过去。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
const int mod=998244353;
int n,m,T,k,c[60],id[60][5],cnt;
struct Mat{
    long long a[310][310];
    Mat(){memset(a,-0x3f,sizeof(a));}
    Mat operator*(const Mat&s)const{
        Mat ret;
        for(int i=1;i<=cnt;i++){
            for(int j=1;j<=cnt;j++){
                for(int k=1;k<=cnt;k++){
                    ret.a[i][j]=max(ret.a[i][j],a[i][k]+s.a[k][j]);
                }
            }
        }
        return ret;
    }
}P[31];
struct Vec{
    long long a[310];
    Vec(){memset(a,-0x3f,sizeof(a));}
    Vec operator*(const Mat&s)const{
        Vec ret;
        for(int i=1;i<=cnt;i++){
            for(int j=1;j<=cnt;j++){
                ret.a[j]=max(ret.a[j],a[i]+s.a[i][j]);
            }
        }
        return ret;
    }
}ans;
struct Ques{
    int t,x,y;
    bool operator<(const Ques&s)const{
        return t<s.t;
    }
}p[210];
int main(){
    scanf("%d%d%d%d",&n,&m,&T,&k);
    for(int i=1;i<=n;i++)scanf("%d",&c[i]);ans.a[1]=c[1];
    for(int i=1;i<=n;i++){
        for(int j=0;j<5;j++)id[i][j]=++cnt;
        for(int j=1;j<5;j++)P[0].a[id[i][j-1]][id[i][j]]=0;
    }
    for(int i=1;i<=m;i++){
        int u,v,w;scanf("%d%d%d",&u,&v,&w);
        P[0].a[id[u][w-1]][id[v][0]]=c[v];
    }
    for(int i=1;i<=30;i++)P[i]=P[i-1]*P[i-1];
    for(int i=1;i<=k;i++)scanf("%d%d%d",&p[i].t,&p[i].x,&p[i].y);p[k+1]={T,0,0};
    sort(p+1,p+k+1);
    for(int i=1;i<=k+1;i++){
        int tmp=p[i].t-p[i-1].t;
        for(int j=30;j>=0;j--){
            if((tmp>>j)&1)ans=ans*P[j];
        }
        ans.a[id[p[i].x][0]]+=p[i].y;
    }
    ans.a[1]=max(ans.a[1],-1ll);
    printf("%lld\n",ans.a[1]);
    return 0;
}

[NOI2020] 命运

会状态定义就是水题,但是我菜的连状态定义都出不来。

观察性质发现以某个点为下端的只有最深的上端有用,于是设 \(dp_{x,i}\) 为到 \(x\) 最深的没满足的上端点为 \(i\) 的方案数,如果都满足就是 \(0\)。那么转移考虑合并子树:

  1. 这条边是 \(0\):贡献显然 \(dp_{x,i}\leftarrow\sum_{\max_{a,b}=i}dp_{x,a}dp_{v,b}\)
  2. \(1\):同样 \(dp_{x,i}\leftarrow dp_{x,i}\times\sum_{j=0}^{dep_x}dp_{v,j}\)

线段树合并即可。但是 pushup 不取模居然能过所有大样例。这告诉我们场上一定要拍。上次调了一下午也是 pushup 没取模。

#include <cstdio>
#include <cstring>
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
const int mod=998244353;
int n,m;
struct gra{
    int v,next;
}edge[1000010];
int tot,head[500010];
void add(int u,int v){
    edge[++tot].v=v;edge[tot].next=head[u];head[u]=tot;
}
int mx[500010],dep[500010];
void dfs1(int x,int f){
    dep[x]=dep[f]+1;
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f)dfs1(edge[i].v,x);
    }
}
struct node{
    #define lson tree[rt].ls
    #define rson tree[rt].rs
    int ls,rs,sum,lz;
}tree[500010<<5];
int t,rt[500010];
void pushup(int rt){
    tree[rt].sum=(tree[lson].sum+tree[rson].sum)%mod;
}
void pushtag(int rt,int val){
    tree[rt].sum=1ll*tree[rt].sum*val%mod;
    tree[rt].lz=1ll*tree[rt].lz*val%mod;
}
void pushdown(int rt){
    if(tree[rt].lz!=1){
        if(lson)pushtag(lson,tree[rt].lz);
        if(rson)pushtag(rson,tree[rt].lz);
        tree[rt].lz=1;
    }
}
void update(int &rt,int L,int R,int pos,int val){
    if(!rt)rt=++t,tree[rt].lz=1;
    if(L==R){
        tree[rt].sum=val;return;
    }
    pushdown(rt);
    int mid=(L+R)>>1;
    if(pos<=mid)update(lson,L,mid,pos,val);
    else update(rson,mid+1,R,pos,val);
    pushup(rt);
}
int query(int rt,int L,int R,int l,int r){
    if(!rt)return 0;
    if(l<=L&&R<=r)return tree[rt].sum;
    pushdown(rt);
    int mid=(L+R)>>1,val=0;
    if(l<=mid)val=(val+query(lson,L,mid,l,r))%mod;
    if(mid<r)val=(val+query(rson,mid+1,R,l,r))%mod;
    return val;
}
int merge(int x,int y,int l,int r,int sumx,int sumy,int val){
    if(!x&&!y)return 0;
    if(!x){
        pushtag(y,sumx);
        return y;
    }
    if(!y){
        pushtag(x,(val+sumy)%mod);
        return x;
    }
    if(l==r){
        tree[x].sum=(1ll*(val+sumy)%mod*tree[x].sum%mod+1ll*tree[y].sum*sumx%mod+1ll*tree[x].sum*tree[y].sum%mod)%mod;
        return x;
    }
    pushdown(x);pushdown(y);
    int mid=(l+r)>>1;
    tree[x].rs=merge(tree[x].rs,tree[y].rs,mid+1,r,(sumx+tree[tree[x].ls].sum)%mod,(sumy+tree[tree[y].ls].sum)%mod,val);
    tree[x].ls=merge(tree[x].ls,tree[y].ls,l,mid,sumx,sumy,val);
    pushup(x);
    return x;
}
void dfs(int x,int f){
    update(rt[x],0,n,mx[x],1);
    for(int i=head[x];i;i=edge[i].next){
        if(edge[i].v!=f){
            dfs(edge[i].v,x);
            rt[x]=merge(rt[x],rt[edge[i].v],0,n,0,0,query(rt[edge[i].v],0,n,0,dep[x]));
        }
    }
}
int main(){
    scanf("%d",&n);
    for(int i=1;i<n;i++){
        int u,v;scanf("%d%d",&u,&v);
        add(u,v);add(v,u);
    }
    dfs1(1,0);
    scanf("%d",&m);
    for(int i=1;i<=m;i++){
        int u,v;scanf("%d%d",&u,&v);
        mx[v]=max(mx[v],dep[u]);
    }
    dfs(1,0);
    printf("%d\n",query(rt[1],0,n,0,0));
    return 0;
}
posted @ 2023-07-05 10:41  gtm1514  阅读(33)  评论(0编辑  收藏  举报