2019-2020 ICPC Asia Jakarta Regional Contest

2019-2020 ICPC Asia Jakarta Regional Contest
官方题解(需要翻墙)

A.Copying Homework

签到,b[i]=n+1a[i]b[i]=n+1-a[i]

B. Cleaning Robots

d[i][j]用d[i][j]表示在以ii为根节点的子树中,ii节点状态为jj的方案数。
j=0j=0,表示ii是一条路径的端点,且和ii的其它儿子节点所在路径没有产生冲突(即融合成一条路径,那么其它儿子节点一定不处于路径的端点处,否则ii所在的路径会和其儿子节点路径融合成一条路径)。
j=1j=1,表示ii是一条路径的端点,且和ii的其它儿子节点所在路径会产生冲突(即融合成一条路径,那么一定存在某个儿子节点处于路径的端点处),那么此时节点ii必须向父节点延伸。
j=2j=2,表示ii处于一条路径的中间位置。

则状态转移如下:
d[i][0]+=d[son][2]d[i][0]+=\displaystyle \prod d[son][2]
然后我们枚举ii的儿子节点sonson(以下jjkk均为ii的儿子)
d[i][0]+=(d[son][0]+d[son][1])j!=sond[j][2]d[i][0]+=(d[son][0]+d[son][1])*\displaystyle\prod_{j!=son}d[j][2]
d[i][1]=(d[son][0]+d[son][1])(j!=son(d[j][2]+d[j][0])j!=sond[j][2])d[i][1]= (d[son][0]+d[son][1])*\big(\displaystyle\prod_{j!=son}(d[j][2]+d[j][0])-\displaystyle\prod_{j!=son}d[j][2]\big)
d[i][2]=(d[son1][0]+d[son1][1])(d[son2][0]+d[son2][1])k!=son1&&k!=son2(d[k][2]+d[k][0])d[i][2]=(d[son_1][0]+d[son_1][1])*(d[son_2][0]+d[son_2][1])*\displaystyle\prod_{k!=son_1 \&\& k!=son_2}(d[k][2]+d[k][0])
其中d[i][2]d[i][2]需要枚举任意2个儿子两两结合成一条路径,转移可以通过前缀和来优化,因为可能存在为d[son][j]d[son][j]00的情况,建议转移时不要用除法。

#include<bits/stdc++.h>
using namespace std;
const int MAX=1e5+10;
const int MOD=1e9+7;
typedef long long ll;
vector<int>e[MAX];
ll d[MAX][4];
ll suf[MAX];
ll p_sum[MAX];//前缀积(d[son][2])
ll s_sum[MAX];//后缀积(d[son][2])
ll p_s[MAX];//前缀积(d[son][0]+d[son][2])
ll s_s[MAX];//后缀积(d[son][0]+d[son][2])
void dfs(int k,int fa)
{
    for(int i=0;i<e[k].size();i++)if(e[k][i]==fa)swap(e[k][i],e[k][0]);
    for(int i=1;i<e[k].size();i++)dfs(e[k][i],k);
    suf[e[k].size()]=0;
    p_sum[0]=s_sum[e[k].size()]=1;
    p_s[0]=s_s[e[k].size()]=1;
    for(int i=1;i<e[k].size();i++)
    {
        int nex=e[k][i];
        p_sum[i]=p_sum[i-1]*d[nex][2]%MOD;
        p_s[i]=(d[nex][0]+d[nex][2])%MOD*p_s[i-1]%MOD;
    }
    for(int i=e[k].size()-1;i>=1;i--)
    {
        int nex=e[k][i];
        suf[i]=(d[nex][0]+d[nex][2])%MOD*suf[i+1]%MOD;
        (suf[i]+=(d[nex][0]+d[nex][1])%MOD*s_s[i+1]%MOD)%=MOD;
        s_sum[i]=s_sum[i+1]*d[nex][2]%MOD;
        s_s[i]=(d[nex][0]+d[nex][2])%MOD*s_s[i+1]%MOD;
    }
    d[k][0]=1;
    for(int i=1;i<e[k].size();i++)d[k][0]=d[k][0]*d[e[k][i]][2]%MOD;
    for(int i=1;i<e[k].size();i++)
    {
        int nex=e[k][i];
        d[k][0]+=(d[nex][0]+d[nex][1])%MOD*p_sum[i-1]%MOD*s_sum[i+1]%MOD;
        d[k][1]+=(d[nex][0]+d[nex][1])%MOD*((p_s[i-1]*s_s[i+1]%MOD-p_sum[i-1]*s_sum[i+1]%MOD+MOD)%MOD)%MOD;
        d[k][2]+=(d[nex][0]+d[nex][1])%MOD*suf[i+1]%MOD*p_s[i-1]%MOD;
        d[k][0]%=MOD;
        d[k][1]%=MOD;
        d[k][2]%=MOD;
    }
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        e[x].push_back(y);
        e[y].push_back(x);
    }
    e[1].push_back(0);
    memset(d,0,sizeof d);
    dfs(1,0);
    cout<<(d[1][0]+d[1][2])%MOD<<endl;
    return 0;
}

C.Even Path

可以发现,经过路径上的所有R[x]R[x]C[y]C[y]的奇偶性必须相同,前缀和判断一下就行。

D.Find String in a Grid

官方题解:
在这里插入图片描述
在这里插入图片描述

E .Songwriter

可以求出位于位置ii的最长下降长度,即位置ii可能的最小值,然后从前往后推导,根据大小关系以及前后差值的大小来决定是否需要补差值。
然后最后再推导一遍,把补差值的影响消除掉。

#include<bits/stdc++.h>
using namespace std;
const int MAX=1e5+10;
const int MOD=1e9+7;
typedef long long ll;
ll a[MAX];
ll b[MAX];
ll d[MAX];
int main()
{
    int n,L,R,K;
    cin>>n>>L>>R>>K;
    for(int i=1;i<=n;i++)scanf("%lld",&a[i]);
    for(int i=n;i>=1;i--)
    {
        if(i==n||a[i+1]>a[i])d[i]=0;
        else if(a[i+1]==a[i])d[i]=d[i+1];
        else if(a[i+1]<a[i])d[i]=d[i+1]+1;
    }
    for(int i=1;i<=n;i++)
    {
        b[i]=d[i]+L;
        if(i==1)continue;
        if(a[i-1]==a[i])b[i]=b[i-1];
        if(a[i-1]>a[i])
        {
            if(b[i-1]-b[i]>K)b[i]+=b[i-1]-b[i]-K;
        }
        if(a[i-1]<a[i])
        {
            b[i]=max(d[i]+L,b[i-1]+1);
            if(b[i]-b[i-1]>K)b[i-1]+=b[i]-b[i-1]-K;
        }
    }
    for(int i=n-1;i>=1;i--)
    {
        if(abs(b[i]-b[i+1])>K)b[i]+=abs(b[i]-b[i+1])-K;
        if(a[i]>a[i+1]&&b[i]<=b[i+1])b[i]=b[i+1]+1;
        if(a[i]==a[i+1]&&b[i]!=b[i+1])b[i]=b[i+1];
        if(a[i]<a[i+1]&&b[i]>=b[i+1])b[i]=b[i-1]-1;
    }
    if(*max_element(b+1,b+n+1)>R){cout<<-1<<endl;return 0;}
    if(*min_element(b+1,b+n+1)<L){cout<<-1<<endl;return 0;}
    for(int i=2;i<=n;i++)if(abs(b[i]-b[i-1])>K){cout<<-1<<endl;return 0;}
    for(int i=1;i<=n;i++)printf("%lld%c",b[i],i==n?'\n':' ');
    return 0;
}

F .Regular Forestation

首先"good cutting point"一定是位于这个树的重心,由于树的重心最多2个,我们枚举重心作为good cutting point,然后求出分割开的各个子树的最小表示法,然后进行比较,如果各个子树的最小表示法相同,即该重心为good cutting point。
求子树的最小表示法时,我们需要确定这个子树的根,可以选择子树的重心作为根,这样这个有根树的最小表示法可以唯一确定该形态的树。

#include<bits/stdc++.h>
using namespace std;
const int MAX=4e3+10;
const int MOD=1e9+7;
typedef long long ll;
vector<int>e[MAX];
int siz[MAX],d[MAX];
int DFS(int k,int fa)
{
    siz[k]=1;
    d[k]=0;
    for(int i=0;i<e[k].size();i++)
    {
        if(e[k][i]==fa)continue;
        DFS(e[k][i],k);
        siz[k]+=siz[e[k][i]];
        d[k]=max(d[k],siz[e[k][i]]);
    }
}
void getroot(int k,int fa,int p,pair<int,int>&root)
{
    d[k]=max(p-siz[k],d[k]);
    if(root.first==0||d[k]<d[root.first])root.first=k;
    else if(root.first!=0&&d[k]==d[root.first])root.second=k;
    for(int i=0;i<e[k].size();i++)
    {
        if(e[k][i]==fa)continue;
        getroot(e[k][i],k,p,root);
    }
}
string dfs(int k,int fa,int T)
{
    if(k==0)return "";
    vector<string>Q;
    for(int i=0;i<e[k].size();i++)
    {
        int nex=e[k][i];
        if(nex==fa||nex==T)continue;
        Q.push_back(dfs(nex,k,T));
    }
    string ans="0";
    sort(Q.begin(),Q.end());
    for(int i=0;i<Q.size();i++)ans+=Q[i];
    ans+="1";
    return ans;
}
 
int cut(int k)
{
    if(e[k].size()<2)return -1;
    string A,B;
    for(int i=0;i<e[k].size();i++)
    {
        pair<int,int>root={0,0};
        DFS(e[k][i],k);
        getroot(e[k][i],k,siz[e[k][i]],root);
        if(i==0)
        {
            A=dfs(root.first,0,k);
            B=dfs(root.second,0,k);
        }
        if(A!=dfs(root.first,0,k)&&A!=dfs(root.second,0,k))A="";
        if(B!=dfs(root.first,0,k)&&B!=dfs(root.second,0,k))B="";
        if(A==""&&B=="")return -1;
    }
    return (int)e[k].size();
}
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        e[x].push_back(y);
        e[y].push_back(x);
    }
    pair<int,int>root={0,0};
    DFS(1,0);
    getroot(1,0,n,root);
    cout<<max(cut(root.first),cut(root.second))<<endl;
    return 0;
}

G.Performance Review

首先不考虑修改的影响,我们求出Randall在每一年的名次。
考虑修改的情况,每一次修改,名次要么加1要么减1,那么我们就把这个加1减1的影响加到后面年份的名次里即可,只要存在一个年份Randall的名次大于n了,就说明她被淘汰了,用线段树维护即可。

#include<bits/stdc++.h>
using namespace std;
const int MAX=1e5+10;
const int MOD=1e9+7;
typedef long long ll;
vector<int>e[MAX];
int a[MAX];
int b[MAX];
struct lenka
{
    int l,r,mi;
    int tag;
}A[MAX<<2];
void build(int k,int l,int r)
{
    A[k].l=l,A[k].r=r;
    A[k].tag=0;
    if(l==r){A[k].mi=b[r];return;}
    build(2*k,l,(l+r)/2);
    build(2*k+1,(l+r)/2+1,r);
    A[k].mi=min(A[2*k].mi,A[2*k+1].mi);
}
void add(int k,int x,int y,int z)
{
    if(x>y)return;
    if(x==A[k].l&&y==A[k].r)
    {
        A[k].mi+=z;
        A[k].tag+=z;
        return;
    }
    if(A[k].tag)
    {
        add(2*k,A[2*k].l,A[2*k].r,A[k].tag);
        add(2*k+1,A[2*k+1].l,A[2*k+1].r,A[k].tag);
        A[k].tag=0;
    }
    if(y<=A[2*k].r)add(2*k,x,y,z);
    else if(x>=A[2*k+1].l)add(2*k+1,x,y,z);
    else
    {
        add(2*k,x,A[2*k].r,z);
        add(2*k+1,A[2*k+1].l,y,z);
    }
    A[k].mi=min(A[2*k].mi,A[2*k+1].mi);
}
int main()
{
    int n,m,Q;
    cin>>n>>m>>Q;
    for(int i=1;i<=n;i++)scanf("%d",&a[i]);
    b[0]=1;
    for(int i=2;i<=n;i++)b[0]+=(a[i]<a[1]);
    for(int i=1;i<=m;i++)
    {
        int r,y=0;
        scanf("%d",&r);
        while(r--)
        {
            int x;
            scanf("%d",&x);
            e[i].push_back(x);
            if(x<a[1])y++;
        }
        b[i]=b[i-1]+y-(int)e[i].size();
    }
    for(int i=m;i>=1;i--)b[i]=b[i-1]-(int)e[i].size();
    build(1,1,m);
    while(Q--)
    {
        int x,y,z;
        scanf("%d%d%d",&x,&y,&z);
        if(e[x][y-1]<a[1]&&z>a[1])add(1,x+1,m,-1);
        if(e[x][y-1]>a[1]&&z<a[1])add(1,x+1,m,1);
        e[x][y-1]=z;
        puts(A[1].mi<=0?"0":"1");
    }
    return 0;
}

H.Twin Buildings

假设所有的Li>WiL_i>W_i,我们按LL从小到大排序,然后枚举LiL_i作为一条边,则ans=max(Limin(Wi,Wj)),j>ians=max(L_i*min(W_i,W_j)),j>i。其中WjW_j用个后缀即可记录。注意数据值较大,用double会WA。

#include<bits/stdc++.h>
using namespace std;
const int MAX=1e5+10;
const int MOD=1e9+7;
typedef long long ll;
struct lenka{ll l,w;}A[MAX];
int cmp(const lenka&x,const lenka&y){return x.l<y.l;}
ll d[MAX];
int main()
{
    int n;
    cin>>n;
    for(int i=1;i<=n;i++)
    {
        scanf("%lld%lld",&A[i].l,&A[i].w);
        if(A[i].l<A[i].w)swap(A[i].l,A[i].w);
    }
    sort(A+1,A+n+1,cmp);
    for(int i=n;i>=1;i--)d[i]=(i==n?A[i].w:max(A[i].w,d[i+1]));
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        ans=max(ans,A[i].l*A[i].w);
        if(i<n)ans=max(ans,A[i].l*min(A[i].w,d[i+1])*2);
    }
    printf("%lld.%lld\n",ans/2,ans%2*5);
    return 0;
}

I.Mission Possible

官方题解:
在这里插入图片描述
在这里插入图片描述

J.Tiling Terrace

考虑到障碍最多只有50个,用d[i][j]d[i][j]表示前ii个位置中,放置了jj个type3的地砖的情况下,所能放置的最多type2地砖个数,这样单个未被利用的位置就会达到最少,然后我们根据价值和数量情况,用type1地砖去替换type2地砖(或者不替换)。

#include<bits/stdc++.h>
using namespace std;
const int MAX=1e5+10;
const int MOD=1e9+7;
typedef long long ll;
char s[MAX];
int d[MAX][51];
int BLOCK=0;
int main()
{
    int n,m,G1,G2,G3;
    cin>>n>>m>>G1>>G2>>G3;
    scanf("%s",s+1);
    memset(d,-1,sizeof d);
    d[0][0]=0;
    for(int i=1;i<=n;i++)
    for(int j=0;j<=50;j++)
    {
        d[i][j]=d[i-1][j];
        if(i>=2&&s[i]=='.'&&s[i-1]=='.'&&d[i-2][j]!=-1)d[i][j]=max(d[i][j],d[i-2][j]+1);
        if(i>=3&&j&&s[i]=='.'&&s[i-1]=='#'&&s[i-2]=='.')d[i][j]=max(d[i][j],d[i-3][j-1]);
    }
    for(int i=1;i<=n;i++)BLOCK+=(s[i]=='#');
    int ans=0;
    for(int i=0;i<=50;i++)
    {
        if(d[n][i]==-1)continue;
        int one=n-d[n][i]*2-i*3-(BLOCK-i);
        if(m<=one)ans=max(ans,m*G1+d[n][i]*G2+i*G3);
        else
        {
            ans=max(ans,one*G1+d[n][i]*G2+i*G3);
            if(G1*2<=G2)continue;
            int res=m-one;
            if(res/2>=d[n][i])ans=max(ans,(d[n][i]*2+one)*G1+i*G3);
            else
            {
                if(res%2&&G1>G2)ans=max(ans,((res/2)*2+1+one)*G1+i*G3+(d[n][i]-res/2-1)*G2);
                else ans=max(ans,((res/2)*2+one)*G1+i*G3+(d[n][i]-res/2)*G2);
            }
        }
    }
    cout<<ans<<endl;
    return 0;
}

K.Addition Robot

可以发现function f(L, R, A, B):实际上可以转化为矩阵乘法的形式,可以把每一个字符转化为相应的矩阵。
我们建立线段树,节点存储矩阵信息即可。

#include<bits/stdc++.h>
using namespace std;
const int MAX=1e5+10;
const int MOD=1e9+7;
typedef long long ll;
struct lenka
{
    int a[2][2];
    lenka(){}
    lenka(int x,int x1,int x2,int x3)
    {
        a[0][0]=x,a[0][1]=x1;
        a[1][0]=x2,a[1][1]=x3;
    }
};
lenka multiply(const lenka &a,const lenka&b)
{
    lenka c=lenka(0,0,0,0);
    for(int i=0;i<2;i++)
    for(int j=0;j<2;j++)
    for(int k=0;k<2;k++)(c.a[i][j]+=1ll*a.a[i][k]*b.a[k][j]%MOD)%=MOD;
    return c;
}
char s[MAX];
struct Data
{
    int l,r;
    int tag;
    lenka a,b;
}A[MAX<<2];
void build(int k,int l,int r)
{
    A[k].l=l,A[k].r=r;
    A[k].tag=0;
    if(l==r)
    {
        if(s[r]=='A')A[k].a=lenka(1,0,1,1),A[k].b=lenka(1,1,0,1);
        else A[k].a=lenka(1,1,0,1),A[k].b=lenka(1,0,1,1);
        return;
    }
    build(2*k,l,(l+r)/2);
    build(2*k+1,(l+r)/2+1,r);
    A[k].a=multiply(A[2*k].a,A[2*k+1].a);
    A[k].b=multiply(A[2*k].b,A[2*k+1].b);
}
void change(int k,int x,int y)
{
    if(x==A[k].l&&y==A[k].r)
    {
        A[k].tag^=1;
        swap(A[k].a,A[k].b);
        return;
    }
    if(A[k].tag)
    {
        change(2*k,A[2*k].l,A[2*k].r);
        change(2*k+1,A[2*k+1].l,A[2*k+1].r);
        A[k].tag=0;
    }
    if(y<=A[2*k].r)change(2*k,x,y);
    else if(x>=A[2*k+1].l)change(2*k+1,x,y);
    else
    {
        change(2*k,x,A[2*k].r);
        change(2*k+1,A[2*k+1].l,y);
    }
    A[k].a=multiply(A[2*k].a,A[2*k+1].a);
    A[k].b=multiply(A[2*k].b,A[2*k+1].b);
}
lenka ask(int k,int x,int y)
{
    if(x==A[k].l&&y==A[k].r)return A[k].a;
    if(A[k].tag)
    {
        change(2*k,A[2*k].l,A[2*k].r);
        change(2*k+1,A[2*k+1].l,A[2*k+1].r);
        A[k].tag=0;
    }
    if(y<=A[2*k].r)return ask(2*k,x,y);
    if(x>=A[2*k+1].l)return ask(2*k+1,x,y);
    lenka L=ask(2*k,x,A[2*k].r);
    lenka R=ask(2*k+1,A[2*k+1].l,y);
    return multiply(L,R);
}
int main()
{
    int n,m;
    cin>>n>>m;
    scanf("%s",s+1);
    build(1,1,n);
    while(m--)
    {
        int op,L,R;
        scanf("%d%d%d",&op,&L,&R);
        if(op==1)change(1,L,R);
        else
        {
            ll x,y;
            scanf("%lld%lld",&x,&y);
            lenka ans=ask(1,L,R);
            ll X=x*ans.a[0][0]%MOD+y*ans.a[1][0]%MOD;
            ll Y=x*ans.a[0][1]%MOD+y*ans.a[1][1]%MOD;
            printf("%lld %lld\n",X%MOD,Y%MOD);
        }
    }
    return 0;
}

L .Road Construction

建立一个二分图,二分图的其中一边节点代表城市之间的边,另一边节点代表工人,不过这样的二分图的边数达到nkn*k
我们可以把节点工人替换成材料,每个材料有对应的工人数量,这样总边数为mi10000\sum m_i\le10000
然后就是匹配过程,因为有nn条边,所以这nn个城市构成了一个图,这个图有且仅有一个环,那么位于环外的边都需要建成,环内至少要建(环的大小-1)条边。那么匹配的时候先对环外的边进行匹配,然后再对环内的边进行匹配,复杂度O(nmi)O(n*\sum m_i)

#include<bits/stdc++.h>
using namespace std;
const int MAX=2e4+10;
const int MOD=1e9+7;
typedef long long ll;
vector<int>E[2010];
int edge_link[2010];
vector<int>matr_link[MAX];
int v[MAX];
int cnt[MAX];
int dfs(int k)
{
    for(int i=E[k].size()-1;i>=0;i--)
    {
        int nex=E[k][i];
        if(v[nex])continue;
        v[nex]=1;
        if(cnt[nex]>0)
        {
            cnt[nex]--;
            edge_link[k]=nex;
            matr_link[nex].push_back(k);
            return 1;
        }
        for(int j=0;j<matr_link[nex].size();j++)
        {
            if(dfs(matr_link[nex][j]))
            {
                edge_link[k]=nex;
                matr_link[nex][j]=k;
                return 1;
            }
        }
    }
    return 0;
}
vector<int>edge;
int e[2010][2010];
int in[2010];
void find_cycle(int n)
{
    queue<int>p;
    for(int i=1;i<=n;i++)if(in[i]==1)p.push(i);
    while(!p.empty())
    {
        int now=p.front();p.pop();
        for(int i=1;i<=n;i++)
        {
            if(e[now][i]==0)continue;
            edge.push_back(e[now][i]);
            in[i]--;
            if(in[i]==1)p.push(i);
        }
    }
    sort(edge.begin(),edge.end());
    edge.erase(unique(edge.begin(),edge.end()),edge.end());
}
map<int,int>ma;
struct lenka{int x,y;}EDG[2010];
int worker[2010];
int tot=0;
int main()
{
    int n,m;
    cin>>n>>m;
    for(int i=1;i<=n;i++)
    {
        int x,y;
        scanf("%d%d",&x,&y);
        e[x][i]=e[i][x]=i;
        EDG[i]=(lenka){i,x};
        in[x]++;
        in[i]++;
        while(y--)
        {
            scanf("%d",&x);
            if(ma[x]==0)ma[x]=++tot;
            E[i].push_back(ma[x]);
        }
    }
    for(int i=1;i<=m;i++)
    {
        scanf("%d",&worker[i]);
        if(ma[worker[i]]==0)ma[worker[i]]=++tot;
        worker[i]=ma[worker[i]];
    }
    memset(cnt,0,sizeof cnt);
    for(int i=1;i<=m;i++)cnt[worker[i]]++;
    find_cycle(n);
    int ans=0;
    for(int i=0;i<edge.size();i++)
    {
        //printf("edge %d is outside of the cycle \n",edge[i]);
        memset(v,0,sizeof v);
        ans+=dfs(edge[i]);
    }
    if(ans!=(int)edge.size()){puts("-1");return 0;}
    for(int i=1;i<=n;i++)
    {
        if(edge_link[i])continue;
        memset(v,0,sizeof v);
        ans+=dfs(i);
    }
    if(ans<n-1){puts("-1");return 0;}
    memset(v,0,sizeof v);
    for(int i=1;i<=m;i++)
    {
        int tag=0;
        for(int j=1;j<=n;j++)
        {
            if(v[j]==0&&edge_link[j]==worker[i])
            {
                tag=v[j]=1;
                printf("%d %d\n",EDG[j].x,EDG[j].y);
                break;
            }
        }
        if(tag==0)puts("0 0");
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章