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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章