JOISC2020

[JOISC2020] 最古の遺跡 3#

好难的题。

首先考虑给出 h 怎么求 A ,设 hii 位置剩下的高度,没了就 =0

方便起见,我们把原序列翻转一下,那么题目的操作就是,每种高度的最靠左的位置不变。

我们从左到右依次求解,先令 hi=hi,若 hij<ihj 中出现过,那么 hi 一定会被震低,让 hi1 继续判断即可。直到没出现过或者 hi=0

H 为最大的,满足 1H 的高度在前 i1h 中都出现的 H ,那么 i 位置被保留当且仅当 H<hi

dpi,j 为考虑到了第 i 个位置, H=j 的方案数。

  • iA,则 hiHhi 一共有 Hc0 种取法,其中 c0jA,j<ij 个数,注意此求法相当于给同一种高度的两个数加以区分了,所以最后要除以 2n

  • iA,则 hi>H ,但是 hi 不一定会影响到 H,这是不好算的,我们考虑在 H 改变时一起计算,因此:

    • 加入该位置不改变 H,我们把该位置的贡献延后计算。
    • 加入该位置改变了 H,容易发现此时一共有 c1H 个位置被延后计算了,其中 c1jA,j<ij 个数。那么我们枚举新的 H=k,那么因为 i 是第一个改变了 H 的位置,所以 hi=j+1,那么 hi 的取值一共有 (kj1)+2,其中 kj1 是因为这些数在被延后计算的数占用过了,+2 是因为 j+1 必定没有被用过。然后还要选出剩下的 kj1 个被延后计算的位置,那就是 (c1jkj1),最后还要进行给这 kj1 个数填数,也就是:用 j+2k 这些数字给 kj1 个位置填数,满足他们的 h 恰好构成 j+2k 的方案数,我们记为 gkj1

gn 的求法,显然充要条件是对于任意 1in,最多有 i 个位置填的数 i ,那么我们记 ti,j 表示填到 i,填了 j 个位置的方案数,转移是简单的。

复杂度 O(n3)

code
#include<bits/stdc++.h>
using namespace std;
const int N = 1202;
const int mod = 1e9+7;
int n;
bool key[N];
template <typename T>inline void read(T &x)
{
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
int binom[N][N],g[N],t[N][N];
int dp[N][N];
const int inv2=(mod+1)/2;
int main()
{
    read(n);
    int m=n*2;
    for(int i=1;i<=n;i++)
    {
        int x;
        read(x);
        x=m-x+1;
        key[x]=1;
    }
    binom[0][0]=1;
    for(int i=1;i<=m;i++)
    {
        binom[i][0]=1;
        for(int j=1;j<=i;j++)
        binom[i][j]=(binom[i-1][j-1]+binom[i-1][j])%mod;
    }
    g[0]=1;
    for(int L=1;L<=n;L++)
    {
        for(int i=0;i<=L;i++)
        for(int j=0;j<=L;j++)
        t[i][j]=0;
        t[0][0]=1;
        for(int i=1;i<=L;i++)
        for(int j=0;j<=i-1;j++)
        if(t[i-1][j])
        {
            t[i][j]=(t[i][j]+t[i-1][j])%mod;
            t[i][j+1]=(t[i][j+1]+2ll*(L-j)*t[i-1][j]%mod)%mod;
            t[i][j+2]=(t[i][j+2]+1ll*(L-j)*(L-j-1)%mod*t[i-1][j]%mod)%mod;
        }
        g[L]=t[L][L];
    }
    dp[0][0]=1;
    int c0=0,c1=0;
    for(int i=1;i<=m;i++)
    {
        if(!key[i])
        {
            for(int j=0;j<=n;j++)
            if(dp[i-1][j])dp[i][j]=1ll*dp[i-1][j]*(2*j-c0-j)%mod;
            c0++;
        }
        else
        {
            for(int j=0;j<=n;j++)if(dp[i-1][j])
            {
                dp[i][j]=(dp[i][j]+dp[i-1][j])%mod;
                for(int k=j+1;k<=n;k++)
                dp[i][k]=(dp[i][k]+1ll*dp[i-1][j]*(k-j+1)%mod*binom[c1-j][k-j-1]%mod*g[k-j-1]%mod)%mod;
            }
            c1++;
        }
    }
    int ans=dp[m][n];
    for(int i=1;i<=n;i++)ans=1ll*ans*inv2%mod;
    cout<<ans;
    return 0;
}

[JOISC2020] 首都#

简单题。

假设我们已知 x 属于最终的首都,那么最优策略一定是:把所有颜色和 x 相同的节点到 x 路径上的节点加进首都,然后继续迭代。

那么我们只需要点分治,把分治节点看做 x 即可,复杂度 O(nlog2n)

code
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
template <typename T>inline void read(T &x)
{
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
vector<int> G[N];
int n,m,col[N];
bool ban[N];
int ans=1e9;
int siz[N],son[N],vis[N],fa[N];
void findroot(int x,int pre,int tot,int &root)
{
    siz[x]=1;son[x]=0;
    for(int y:G[x])
    {
        if(vis[y]||y==pre)continue;
        findroot(y,x,tot,root);
        son[x]=max(son[x],siz[y]);
        siz[x]+=siz[y];
    }
    son[x]=max(son[x],tot-siz[x]);
    if(!root||son[x]<son[root])root=x;
}
bool ins[N];
int subtree[N],tag[N];
void extend(int x,int pre)
{
    subtree[col[x]]=tag[col[x]]=0;
    ins[x]=0;siz[x]=1;fa[x]=pre;
    for(int y:G[x])
    {
        if(vis[y]||y==pre)continue;
        extend(y,x);
        siz[x]+=siz[y];
    }
}
void update(int x,int pre,int v)
{
    int c=col[x];
    if(!subtree[c])subtree[c]=v;
    else if(subtree[c]!=v)
    {
        ban[c]=1;
    }
    for(int y:G[x])
    {
        if(vis[y]||y==pre)continue;
        update(y,x,v==-1?y:v);
    }
}
queue<int> q;
vector<int> pos[N];
bool flag=0;
void apply(int x,int t)
{
    if(!t)x=fa[x];
    while(x)
    {
        int c=col[x];
        if(ban[c])
        {
            flag=1;
            return;
        }
        if(ins[x])break;
        ins[x]=1;
        for(int y:pos[c])if(!ins[y])
        {
            ins[y]=1;
            q.push(y);
        }
        x=fa[x];
    }
}
void divide(int r)
{
    vis[r]=1;extend(r,0);
    flag=0;
    apply(r,1);
    q.push(r);
    int res=0;
    while(!q.empty())
    {
        int x=q.front();
        q.pop();
        int c=col[x];
        if(tag[c]);
        else
        {
            tag[c]=1;
            res++;
        }
        apply(x,0);
    }
    if(!flag)ans=min(ans,res-1);
    update(r,0,-1);
    for(int y:G[r])
    {
        if(vis[y])continue;
        int z=0;
        findroot(y,r,siz[y],z);
        divide(z);
    }
}
int main()
{
    read(n);read(m);
    for(int i=1;i<n;i++)
    {
        int x,y;
        read(x);read(y);
        G[x].push_back(y);
        G[y].push_back(x);
    }
    for(int i=1;i<=n;i++)read(col[i]),pos[col[i]].push_back(i);
    int r=0;findroot(1,0,n,r);
    divide(r);
    cout<<ans;
    return 0;
}

[JOISC2020] ビルの飾り付け 4#

不错的题。

我们首先有个暴力的 dpfi,j,0/1 表示前 i 个,一共选了 jA,第 i 个是 A/B 是否可行。

关键性质:对于一个 fi,,0/1 ,其可行的 j 是一段区间。

证明:

首先有一些数的选择是固定的,比如如果选了 Ai 的话 i1/i+1 就没法选,那么 i 必须选 Bi,然后可以继续迭代,求出某些固定的数。考虑相邻两个不固定的位置 i,i+1 ,不妨假设 AiBi,Ai+1Bi+1,那么此时若 Bi<Ai+1,两边显然不影响,否则一定 Ai<Ai+1<BiBi+1,那么 Ai+1,Bi 不能同时选,这样可以得到一堆类似的要求,显然这样的限制对于一整段区间来说,若 i 可行,那么 i1 一定可行,加上必选的就说明了是一段区间。

那么直接维护区间转移即可, 最后倒着输出方案。

code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+7;
#define PII pair<int,int>
#define mk(x,y) make_pair(x,y)
#define X(x) x.first
#define Y(x) x.second
PII dp[N][2];
int n;
int a[2][N];
PII operator +(PII A,PII B){return mk(min(X(A),X(B)),max(Y(A),Y(B)));}
void print(int i,int d,int v)
{
    if(i>1)
    {
        for(int c=0;c<=1;c++)if(dp[i-1][c].first<=dp[i-1][c].second)
        if(a[c][i-1]<=a[d][i])
        {
            int delta=(d==0);
            int w=v-delta;
            if(dp[i-1][c].first<=w&&w<=dp[i-1][c].second)
            {
                print(i-1,c,w);
                break;
            }
        }
    }
    if(d==0)putchar('A');
    else putchar('B');
}
int main()
{
    cin>>n;n*=2;
    for(int i=1;i<=n;i++)scanf("%d",&a[0][i]);
    for(int i=1;i<=n;i++)scanf("%d",&a[1][i]);
    dp[1][0]=mk(1,1);
    dp[1][1]=mk(0,0);
    for(int i=2;i<=n;i++)
    {
        for(int c=0;c<=1;c++)dp[i][c]=mk(n+1,0);
        for(int c=0;c<=1;c++)if(dp[i-1][c].first<=dp[i-1][c].second)
        for(int d=0;d<=1;d++)
        if(a[c][i-1]<=a[d][i])
        {
            int delta=(d==0);
            int l=dp[i-1][c].first+delta,r=dp[i-1][c].second+delta;
            dp[i][d]=dp[i][d]+mk(l,r);
        }
    }
    for(int c=0;c<=1;c++)
    if(dp[n][c].first<=n/2&&dp[n][c].second>=n/2)
    {
        print(n,c,n/2);
        return 0;
    }
    printf("-1\n");
    return 0;
}

[JOISC2020] 星座 3#

两个星星会形成矩形,当且仅当中间的 A 都小于他们,这个东西有点像笛卡尔树。

我们设 dpi,j 表示笛卡尔树上 i 的子树内,最高的星星高度为 j,最多保留的权值和。

转移:

  • i 不放星星:枚举左边的最高星星 l,右边的最高星星 r,则要求 min(l,r)Ai,然后合并到 dpi,max(l,r)

  • i 放星星:枚举放的星星 x,则左右两边的星星都要 Ai,这个最值是固定的,合并到 dpi,x

用线段树合并维护转移。

第二种转移只需要直到前缀最值即可,对于第一种转移,我们假设合并到了 j,若 jAi,则另一边的高度要 j,否则另一边的高度要 Ai,可以在线段树合并的过程中记录前缀最值。

复杂度 O(nlogn)

code
#include<bits/stdc++.h>
using namespace std;
const int N = 2e5+7;
int n,m;
int h[N];
vector<int> star[N];
struct node
{
    int x,y,v;
}p[N];
template <typename T>inline void read(T &x)
{
    x=0;char c=getchar();bool f=0;
    for(;c<'0'||c>'9';c=getchar())f|=(c=='-');
    for(;c>='0'&&c<='9';c=getchar())x=(x<<1)+(x<<3)+(c^48);
    x=(f?-x:x);
}
int st[N][20],lg[N];
inline int Max(int x,int y){return h[x]>h[y]?x:y;}
inline int locate(int l,int r){int k=lg[r-l+1];return Max(st[l][k],st[r-(1<<k)+1][k]);}
typedef long long LL;
int rot[N],lson[N*32],rson[N*32],tot=0;
LL mxv[N*32],tag[N*32];
void pushtag(int k,LL v)
{
    if(!k)return;
    tag[k]+=v;
    mxv[k]+=v;
}
void pushup(int k)
{
    mxv[k]=max(mxv[lson[k]],mxv[rson[k]]);
}
void pushdown(int k)
{
    if(tag[k])
    {
        pushtag(lson[k],tag[k]);
        pushtag(rson[k],tag[k]);
        tag[k]=0;
    }
}
LL querymax(int k,int l,int r,int L,int R)
{
    if(!k||L>R)return 0ll;
    if(L<=l&&r<=R)return mxv[k];
    pushdown(k);
    LL res=0;int mid=(l+r)>>1;
    if(L<=mid)res=max(res,querymax(lson[k],l,mid,L,R));
    if(R>mid)res=max(res,querymax(rson[k],mid+1,r,L,R));
    return res;
}
void upd(int &k,int l,int r,int x,LL v)
{
    if(!k)k=++tot;
    if(l==r)
    {
        mxv[k]=max(mxv[k],v);
        return;
    }
    pushdown(k);
    int mid=(l+r)>>1;
    if(x<=mid)upd(lson[k],l,mid,x,v);
    else upd(rson[k],mid+1,r,x,v);
    pushup(k);
}
void modify(int k,int l,int r,int L,int R,LL v)
{
    if(!k||L>R)return;
    if(R<l||L>r)return;
    if(L<=l&&r<=R)
    {
        pushtag(k,v);
        return;
    }
    pushdown(k);
    int mid=(l+r)>>1;
    if(L<=mid)modify(lson[k],l,mid,L,R,v);
    if(R>mid)modify(rson[k],mid+1,r,L,R,v);
    pushup(k);
}
int Merge(int x,int y,int l,int r,LL lval,LL rval,LL lmax,LL rmax,int lim)
{
    if(!x&&!y)return 0;
    pushdown(x);pushdown(y);
    if(!y)
    {
        modify(x,l,r,0,lim,rmax);
        modify(x,l,r,lim+1,n,rval);
        return x;
    }
    if(!x)
    {
        modify(y,l,r,0,lim,lmax);
        modify(y,l,r,lim+1,n,lval);
        return y;
    }
    if(l==r)
    {
        LL val=0;
        if(l<=lim)
        {
            val=max(val,mxv[x]+rmax);
            val=max(val,mxv[y]+lmax);
            val=max(val,mxv[x]+mxv[y]);
        }
        else
        {
            val=max(val,mxv[x]+rval);
            val=max(val,mxv[y]+lval);
        }
        mxv[x]=val;
        return x;
    }
    int mid=(l+r)>>1;
    rson[x]=Merge(rson[x],rson[y],mid+1,r,lval,rval,max(lmax,mxv[lson[x]]),max(rmax,mxv[lson[y]]),lim);
    lson[x]=Merge(lson[x],lson[y],l,mid,lval,rval,lmax,rmax,lim);
    pushup(x);
    return x;
}
void print(int k,int l,int r)
{
    if(!k)
    {
        for(int i=l;i<=r;i++)cout<<0<<' ';
        return;
    }
    if(l==r)
    {
        cout<<mxv[k]<<' ';
        return;
    }
    pushdown(k);
    int mid=(l+r)>>1;
    print(lson[k],l,mid);
    print(rson[k],mid+1,r);
    pushup(k);
}
int dpsolve(int l,int r)
{
    if(l>r)return 0;
    int root=0;
    if(l==r)
    {
        for(int i:star[l])
        upd(root,0,n,p[i].y,p[i].v);
        return root;
    }
    int x=locate(l,r);
    int ls=dpsolve(l,x-1);
    int rs=dpsolve(x+1,r);
    LL lval=querymax(ls,0,n,0,h[x]);
    LL rval=querymax(rs,0,n,0,h[x]);
    root=Merge(ls,rs,0,n,lval,rval,0,0,h[x]);
    for(int i:star[x])upd(root,0,n,p[i].y,lval+rval+p[i].v);
    return root;
}
int main()
{
    //freopen("a.in","r",stdin);
    read(n);
    for(int i=1;i<=n;i++)read(h[i]),st[i][0]=i;
    read(m);
    LL res=0;
    for(int i=1;i<=m;i++)
    {
        read(p[i].x);
        read(p[i].y);
        read(p[i].v);
        star[p[i].x].push_back(i);
        res+=p[i].v;
    }
    for(int i=2;i<=n;i++)lg[i]=lg[i>>1]+1;
    for(int k=1;k<=lg[n];k++)
    for(int i=1;i+(1<<k)-1<=n;i++)
    st[i][k]=Max(st[i][k-1],st[i+(1<<(k-1))][k-1]);
    int root=dpsolve(1,n);
    LL ans=mxv[root];
    cout<<res-ans;
    return 0;
}

[JOISC2020] カメレオンの恋#

神奇的交互题。

考虑询问两个人 i,j,如果 i,j 没有关系 或者 i,j 双向喜欢,结果是 2 ,而结果是 1 时一定有如下三种之一:

  • i,j 同色

  • i 喜欢 j

  • j 喜欢 i

我们给这样的 i,j 连一条边,那么每个点的度数都是 1 (双向喜欢) 或 31 的情况直接找到同色了,我们来看 3 的时候,设 i 的三条边分别连了 x,y,z,我们把 {i,x,y},{i,y,z},{i,x,z} 都问一遍,可以发现当期仅当除了 x 外的一个和 i 同色,一个 i 喜欢时询问的结果是 1,那么我们就可以直到谁喜欢 i,这样都问一遍之后就可以把所有的单向喜欢关系找到,剩下的就是同色了。

上述做法的瓶颈在于我们要 O(n2) 次询问建出这个图,但是每个点只有最多三条边,并且这是一个二分图。

我们来看一下子任务 4,此时我们直到每个人的性别,也就是在二分图的哪一侧,我们二分另一侧的一个前缀,如果颜色数小于点数,那么说明有边存在,这样可以花 O(nlogn) 代价找到所有边。

不知道性别的情况,我们逐个加点,维护此时的二分图,然后把 i 在里面二分,更新出边,然后把新的图重新二分图染色即可。

我们之所以要求在二分图一侧二分,是因为必须控制只有和 i 相连的边,没有别的边。

可能需要卡一下二分的常数来减少询问次数。

code
#include<bits/stdc++.h>
using namespace std;
using namespace std;
extern "C" int Query(const std::vector<int> &p);
extern "C" void Answer(int a, int b);
int n;
vector<int> bigraph[2];
const int N = 1200;
vector<int> G[N];
bool check(int x,vector<int> S)
{
    S.push_back(x);
    return Query(S)<(int)S.size();
}
void addedge(int x,int c)
{
    int L=0,R=(int)bigraph[c].size()-1;
    while(L<=R)
    {
        vector<int> Se;
        for(int i=L;i<=R;i++)Se.push_back(bigraph[c][i]);
        if(!check(x,Se))return;
        int l=L,r=R,mid=0,pos=R+1;
        while(l<=r)
        {
            mid=(l+r)>>1;
            vector<int> S;
            for(int i=l;i<=mid;i++)S.push_back(bigraph[c][i]);
            if(check(x,S))
            {
                pos=mid;
                r=mid-1;
            }
            else l=mid+1;
        }
        if(pos<=R)
        {
            G[x].push_back(bigraph[c][pos]);
            G[bigraph[c][pos]].push_back(x);
        }
        L=pos+1;
    }
}
int col[N];
void dfs(int x,int c)
{
    col[x]=c;
    bigraph[c].push_back(x);
    for(int y:G[x])
    if(col[y]==-1)dfs(y,c^1);
}
bool waye[N][N];
extern "C" void Solve(int _n)
{
    n=_n;
    bigraph[0].push_back(1);
    for(int i=2;i<=2*n;i++)
    {
        addedge(i,0);
        addedge(i,1);
        bigraph[0].clear();
        bigraph[1].clear();
        for(int j=1;j<=i;j++)col[j]=-1;
        for(int j=1;j<=i;j++)if(col[j]==-1)dfs(j,0);
    }
    for(int i=1;i<=2*n;i++)
    for(int y:G[i])waye[i][y]=1;
    for(int i=1;i<=2*n;i++)
    {
        if(G[i].size()==3)
        {
            int x=G[i][0],y=G[i][1],z=G[i][2];
            if(Query({i,x,y})==1)waye[i][z]=waye[z][i]=0;
            if(Query({i,x,z})==1)waye[i][y]=waye[y][i]=0;
            if(Query({i,y,z})==1)waye[i][x]=waye[x][i]=0;
        }
    }
    for(int i=1;i<=2*n;i++)
    for(int j=i+1;j<=2*n;j++)
    if(waye[i][j])Answer(i,j);
}
posted @   Larunatrecy  阅读(20)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 因为Apifox不支持离线,我果断选择了Apipost!
· 通过 API 将Deepseek响应流式内容输出到前端
点击右上角即可分享
微信分享提示
主题色彩