USACO 乱做

目录

USACO 乱做

cnyz 水平太菜,只配做 pj 难度的题了。

USACO 2021 Open G T1

Solution

类似采花?

不难想到对每个位置记录一个 \(pre_i\) 表示在它之前且离它最近与其相同品种的位置。

对于每一个 \(r\),我们考虑在它之前可能的方案数,也就是在 \([pre_r+1,r-1]\) 的这样一个区间,\(pre_r+1\) 是因为我们一定不能包含 \(pre_r\) 这个位置,\(r-1\) 是因为题目要求了区间长度至少为 \(2\)。

我们暂时无法满足 \(l\) 的限制,但是类似采花,我们每次只保留最靠右的位置,即对于每一个求过答案的 \(r\),删除 \(pre_r\),使其不能作为答案。

直接使用线段树优化,时间复杂度 \(O(n\log n)\)。

My Code
const int N=2e5;
ll sum[N*4+10],ans,orz;
void update(int p,int l,int r,int x,int v) {
    if(l==r) {sum[p]+=v;return;}
    int mid=(l+r)>>1;
    if(x<=mid) update(p*2,l,mid,x,v);
    else update(p*2+1,mid+1,r,x,v);
    sum[p]=sum[p*2]+sum[p*2+1];
}
ll query(int p,int l,int r,int x,int y) {
    if(x>y) return 0;
    if(x<=l&&r<=y) return sum[p];
    int mid=(l+r)>>1,ret=0;
    if(x<=mid) ret+=query(p*2,l,mid,x,y);
    if(y>mid) ret+=query(p*2+1,mid+1,r,x,y);
    return ret;
}
int n,a[N+10],pre[N+10],tmp[N+10];
int main() {
    n=read();
    for(int i=1;i<=n;i++) {
        a[i]=read();
        pre[i]=tmp[a[i]];
        tmp[a[i]]=i;
    }
    update(1,0,n,0,1);
    for(int i=1;i<=n;i++) {
        ans+=query(1,0,n,pre[i],i-2);
        update(1,0,n,i,1);
        if(pre[i]) update(1,0,n,pre[i]-1,-1);
    }
    printf("%lld\n",ans);
}

USACO 2021 Open G T2

Solution

对于每一个点拆点,拆成 \(4\) 个点分别表示四个传送门,将传送门转为边,同时相邻两点也转成边,此时图会被划分成若干个连通块。

不难发现每一次操作,我们都会将两个连通块进行连通,其费用为 \(c_i\)。

那么问题变成了最小生成树,可以直接贪心。

时间复杂度 \(O(n\log n)\)。

My Code
const int N=1e5;
int n,c[N+10],t[N+10][5],ans;
vector<int> G[N*4+10],zz[N*2+10];
int vis[N*4+10],tot;
void dfs(int u,int rt) {
    vis[u]=rt;
    for(auto v:G[u])
        if(!vis[v])
            dfs(v,rt);
}
int fa[N+10],etot;
struct node {
    int x,y,v;
} E[N+10];
bool cmp(node _,node __) {
    return _.v<__.v;
}
int find(int x) {
    if(x==fa[x]) return x;
    return fa[x]=find(fa[x]);
}
int main() {
    n=read();
    for(int i=1;i<=n;i++) {
        c[i]=read();
        G[(i-1)*4].pb((i-1)*4+1);
        G[(i-1)*4+1].pb((i-1)*4);
        G[(i-1)*4+2].pb((i-1)*4+3);
        G[(i-1)*4+3].pb((i-1)*4+2);
        for(int j=1;j<=4;j++) t[i][j]=read(),zz[t[i][j]].pb((i-1)*4+j-1);
    }
    for(int i=1;i<=2*n;i++) G[zz[i][0]].pb(zz[i][1]),G[zz[i][1]].pb(zz[i][0]);
    for(int i=1;i<=n;i++) {
        if(!vis[(i-1)*4]) dfs((i-1)*4,++tot);
        if(!vis[(i-1)*4+1]) dfs((i-1)*4+1,++tot);
        if(!vis[(i-1)*4+2]) dfs((i-1)*4+2,++tot);
        if(!vis[(i-1)*4+1]) dfs((i-1)*4+3,++tot);
        if(vis[(i-1)*4]!=vis[(i-1)*4+2]) E[++etot]={vis[(i-1)*4],vis[(i-1)*4+2],c[i]};
    }
    sort(E+1,E+etot+1,cmp);
    // for(int i=1;i<=etot;i++) printf("%d %d %d\n",E[i].x,E[i].y,E[i].v);
    // printf("%d\n",tot);
    for(int i=1;i<=tot;i++) fa[i]=i;
    for(int i=1;i<=etot;i++) {
        int fx=find(E[i].x),fy=find(E[i].y);
        if(fx==fy) continue;
        ans+=E[i].v,fa[fx]=fy;
    }
    write(ans);enter;
}

USACO 2021 Open G T3

Solution

不难发现最后形成图形的凸包一定是一个三角形,考虑维护这个三角形。

想到一个 dp,设 \(f_{i,j,k,w}\) 表示三角形的三个点是 \(p_i,p_j,p_k\),还有 \(w\) 个点没选的方案数。

我们思考这个加点的过程,发现一个点要想被加进来,必须满足如下两个条件:

  1. 其在三角形内。
  2. 延长三角形三边,其在三角形对角所覆盖的范围内。

发现第二种情况完全不会判,怎么办呢,不判。

对,不判,我们直接记忆化搜索,从最大的那个三角形开始搜起,每次枚举一个在三角形内的点,递归下去并转移。

这样看起来时间复杂度是 \(O(n^7)\) 的,但常数很小USACO 乱做,总之就是过了。

具体实现细节见代码:

My Code
const int N=50,mod=1e9+7;
const double pi=acos(-1),eps=1e-8;
void Add(int &x,int y) {
    x+=y;
    if(x>=mod) x-=mod;
}
struct Vector {
    int x,y;
    double len() {return sqrt(1.0*x*x+1.0*y*y);}
    int operator *(Vector _) {return x*_.x+y*_.y;}
};
int n;
int f[N+10][N+10][N+10][N+10],in[N+10][N+10][N+10];
int x[N+10],y[N+10],fac[N+10],C[N+10][N+10];
bool vis[N+10][N+10][N+10];
double angle(int a,int b,int c) {
    Vector A={x[b]-x[a],y[b]-y[a]};
    Vector B={x[c]-x[a],y[c]-y[a]};
    return acos(1.0*(A*B)/A.len()/B.len());
}
bool check(int a,int b,int c,int v) {
    //printf("chk %d,%d,%d,%d,result: %d\n",a,b,c,v,fabs(angle(v,a,b)+angle(v,b,c)+angle(v,a,c)-2*pi)<eps);
    return fabs(angle(v,a,b)+angle(v,b,c)+angle(v,a,c)-2*pi)<eps;
}
void dp(int a,int b,int c) {
    if(vis[a][b][c]) return;
    // printf("dp(%d,%d,%d)\n",a,b,c);
    vis[a][b][c]=1;
    for(int i=0;i<=in[a][b][c];i++) {
        Add(f[a][b][c][i],6ll*C[in[a][b][c]][i]%mod*fac[in[a][b][c]-i]%mod);
        // printf("f[%d] is %d\n",i,f[a][b][c][i]);
        // //f[i][j][k][t]=6LL*C[u[i][j][k]][t]%P*fac[u[i][j][k]-t]%P;
    }
    int tmp[3];
    for(int i=1;i<=n;i++)
        if(a!=i&&b!=i&&c!=i&&check(a,b,c,i)) {
            tmp[0]=a,tmp[1]=b,tmp[2]=i,sort(tmp,tmp+3),dp(tmp[0],tmp[1],tmp[2]);
            for(int q=0;q<=in[a][b][c]-in[tmp[0]][tmp[1]][tmp[2]]-1;q++)
                for(int w=0;w<=in[tmp[0]][tmp[1]][tmp[2]];w++)
                    for(int t=0;t<=w;t++)
                        Add(f[a][b][c][q+t],1ll*f[tmp[0]][tmp[1]][tmp[2]][w]*
                            fac[in[a][b][c]-in[tmp[0]][tmp[1]][tmp[2]]-1-q+w-t]%mod*
                            C[in[a][b][c]-in[tmp[0]][tmp[1]][tmp[2]]-1][q]%mod*C[w][t]%mod);
            /*
            rep(q,0,in[a][b][v]-in[tmp[0]][tmp[1]][tmp[2]]-1)
            rep(w,0,in[tmp[0]][tmp[1]][tmp[2]])
            rep(t,0,w)
            f[a][b][c][q+t]=(f[i][j][k][q+t]+1LL*f[c[0]][c[1]][c[2]][w]*
                            fac[u[i][j][k]-u[c[0]][c[1]][c[2]]-1-q+w-t]%P*
                            C[u[i][j][k]-u[c[0]][c[1]][c[2]]-1][q]%P*C[w][t])%P;
            */
            tmp[0]=a,tmp[1]=c,tmp[2]=i,sort(tmp,tmp+3),dp(tmp[0],tmp[1],tmp[2]);
            for(int q=0;q<=in[a][b][c]-in[tmp[0]][tmp[1]][tmp[2]]-1;q++)
                for(int w=0;w<=in[tmp[0]][tmp[1]][tmp[2]];w++)
                    for(int t=0;t<=w;t++)
                        Add(f[a][b][c][q+t],1ll*f[tmp[0]][tmp[1]][tmp[2]][w]*
                            fac[in[a][b][c]-in[tmp[0]][tmp[1]][tmp[2]]-1-q+w-t]%mod*
                            C[in[a][b][c]-in[tmp[0]][tmp[1]][tmp[2]]-1][q]%mod*C[w][t]%mod);
            tmp[0]=b,tmp[1]=c,tmp[2]=i,sort(tmp,tmp+3),dp(tmp[0],tmp[1],tmp[2]);
            for(int q=0;q<=in[a][b][c]-in[tmp[0]][tmp[1]][tmp[2]]-1;q++)
                for(int w=0;w<=in[tmp[0]][tmp[1]][tmp[2]];w++)
                    for(int t=0;t<=w;t++)
                        Add(f[a][b][c][q+t],1ll*f[tmp[0]][tmp[1]][tmp[2]][w]*
                            fac[in[a][b][c]-in[tmp[0]][tmp[1]][tmp[2]]-1-q+w-t]%mod*
                            C[in[a][b][c]-in[tmp[0]][tmp[1]][tmp[2]]-1][q]%mod*C[w][t]%mod);
        }
}
int main() {
    n=read();
    for(int i=1;i<=n;i++) x[i]=read(),y[i]=read();
    fac[0]=1;
    for(int i=1;i<=N;i++) fac[i]=1ll*fac[i-1]*i%mod;
    C[0][0]=1;
    for(int i=1;i<=N;i++) {
        C[i][0]=1,C[i][i]=1;
        for(int j=1;j<i;j++)
            Add(C[i][j],C[i-1][j]),
            Add(C[i][j],C[i-1][j-1]);
    }
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
            for(int k=j+1;k<=n;k++)
                for(int t=1;t<=n;t++)
                    if(t!=i&&t!=j&&t!=k&&check(i,j,k,t))
                        in[i][j][k]++;
    // for(int i=1;i<=n;i++)
    //     for(int j=i+1;j<=n;j++)
    //         for(int k=j+1;k<=n;k++)
    //             printf("in[%d][%d][%d] is %d\n",i,j,k,in[i][j][k]);
    for(int i=1;i<=n;i++)
        for(int j=i+1;j<=n;j++)
            for(int k=j+1;k<=n;k++)
                if(in[i][j][k]==n-3) {
                    dp(i,j,k);
                    write(f[i][j][k][0]),enter;
                    return WDNMD;
                }
    write(0),enter;
    return WDNMD;
}

USACO 2021 Open P T1

Solution

银组加强版,同样考虑记录 \(pre_i\)。

但是会发现一件事情,不同的领队算不同的方案,那么此时方案就有点难算了,但还是继承 G 组 T1 的想法。

同样的,答案就是 \([pre_r+1,i-2]\),考虑更新。

更新就是,我们发现 \([pre_{pre_r}+1,pre_r-1]\) 这些位置已经不再可能成为以 \(r\) 这个位置作为领队的答案,区间减一,同时 \([pre_r+1,i-1]\) 可以成为以 \(r\) 这个位置作为领队的答案,区间加一。

注意我们并未处理 \(pre_r\) 的情况,此时我们发现,无论如何,\(pre_r\) 绝对不会成为答案,直接删除这个节点。

使用线段树维护,时间复杂度 \(O(n\log n)\)。

My Code
const int N=2e5;
int sum[N*4+10],add[N*4+10],ban[N*4+10];
void pushup(int p) {
    sum[p]=sum[p*2]+sum[p*2+1];
    ban[p]=ban[p*2]+ban[p*2+1];
}
void addadd(int p,int l,int r,int v) {
    add[p]+=v;
    sum[p]+=1ll*v*(r-l+1-ban[p]);
}
void pushdown(int p,int l,int r) {
    int mid=(l+r)>>1;
    if(add[p]) {
        addadd(p*2,l,mid,add[p]);
        addadd(p*2+1,mid+1,r,add[p]);
    }
    add[p]=0;
}
void update_add(int p,int l,int r,int x,int y,int v) {
    if(x>y) return;
    if(ban[p]==r-l+1) return;
    if(x<=l&&r<=y) {
        addadd(p,l,r,v);
        return;
    }
    pushdown(p,l,r);
    int mid=(l+r)>>1;
    if(x<=mid) update_add(p*2,l,mid,x,y,v);
    if(y>mid) update_add(p*2+1,mid+1,r,x,y,v);
    pushup(p);
}
void update_ban(int p,int l,int r,int x) {
    if(x==0) return;
    if(ban[p]==r-l+1) return;
    if(l==r) {
        ban[p]=1;sum[p]=0;
        return;
    }
    pushdown(p,l,r);
    int mid=(l+r)>>1;
    if(x<=mid) update_ban(p*2,l,mid,x);
    else update_ban(p*2+1,mid+1,r,x);
    pushup(p);
}
int query(int p,int l,int r,int x,int y) {
    if(x>y) return 0;
    if(ban[p]==r-l+1) return 0;
    if(x<=l&&r<=y) return sum[p];
    pushdown(p,l,r);
    int mid=(l+r)>>1,ret=0;
    if(x<=mid) ret+=query(p*2,l,mid,x,y);
    if(y>mid) ret+=query(p*2+1,mid+1,r,x,y);
    return ret;
}
int n,a[N+10],tmp[N+10],pre[N+10];
ll ans;
int main() {
    n=read();
    for(int i=1;i<=n;i++) a[i]=read();
    for(int i=1;i<=n;i++) {
        pre[i]=tmp[a[i]];
        tmp[a[i]]=i;
        // printf("%d ",pre[i]);
    }
    // puts("");
    for(int i=1;i<=n;i++) {
        ans+=query(1,1,n,pre[i]+1,i-2);
        // printf("Add query %d,%d,%d\n",pre[i]+1,i-2,query(1,1,n,pre[i]+1,i-2));
        update_add(1,1,n,pre[pre[i]]+1,pre[i]-1,-1);
        // printf("Add %d,%d,%d\n",pre[pre[i]]+1,pre[i]-1,-1);
        update_add(1,1,n,pre[i]+1,i-1,1);
        // printf("Add %d,%d,%d\n",pre[i]+1,i-1,1);
        update_ban(1,1,n,pre[i]);
        // printf("Ban %d\n",pre[i]);
    }
    printf("%lld\n",ans);
    return 0;
}

USACO 2021 Open P T3

Solution 设 $f_{i,l,r,0/1,0/1}$ 表示第 $i$ 行我取的区间是 $[l,r]$,此时 $l/r$ 处于扩张/缩小状态。

这玩意暴力枚举是 \(O(n^5)\) 的,然而可以直接二维前缀和,于是变成了 \(O(n^3)\) 没了。

注意转移可能要细心的推一下。

My Code
const int N=150,mod=1e9+7;
int n,sg[N+10],f[N+10][N+10][N+10][2][2],g[N+10][N+10][N+10][2][2],ans;
char ch[N+10];
void Del(int &x,int y) {
    x-=y;
    if(x<0) x+=mod;
}
void Add(int &x,int y) {
    x+=y;
    if(x>=mod) x-=mod;
}
int gt(int i,int a,int x,int b,int y,int l,int r) {
    int res=0;
    Add(res,g[i][x][y][l][r]);
    Del(res,g[i][a-1][y][l][r]);
    Del(res,g[i][x][b-1][l][r]);
    Add(res,g[i][a-1][b-1][l][r]);
    return res;
}
int main() {
    n=read();
    for(int i=1;i<=n;i++) {
        gs(ch+1);
        for(int j=1;j<=n;j++) sg[j]=sg[j-1]+(ch[j]=='G');
        for(int l=1;l<=n;l++)
            for(int r=l;r<=n;r++)
                if(sg[r]-sg[l-1]==r-l+1) {
                    Add(f[i][l][r][0][0],gt(i-1,l,r,l,r,0,0));
                    Add(f[i][l][r][0][0],1);
                    Add(f[i][l][r][1][0],gt(i-1,1,l,l,r,1,0));
                    Add(f[i][l][r][1][0],gt(i-1,1,l-1,l,r,0,0));
                    Add(f[i][l][r][0][1],gt(i-1,l,r,r,n,0,1));
                    Add(f[i][l][r][0][1],gt(i-1,l,r,r+1,n,0,0));
                    Add(f[i][l][r][1][1],gt(i-1,1,l-1,r+1,n,0,0));
                    Add(f[i][l][r][1][1],gt(i-1,1,l,r+1,n,1,0));
                    Add(f[i][l][r][1][1],gt(i-1,1,l-1,r,n,0,1));
                    Add(f[i][l][r][1][1],gt(i-1,1,l,r,n,1,1));
                    Add(ans,f[i][l][r][0][0]);
                    Add(ans,f[i][l][r][0][1]);
                    Add(ans,f[i][l][r][1][0]);
                    Add(ans,f[i][l][r][1][1]);
                }
        for(int l=1;l<=n;l++)
            for(int r=1;r<=n;r++)
                for(int x=0;x<=1;x++)
                    for(int y=0;y<=1;y++)
                        Add(g[i][l][r][x][y],f[i][l][r][x][y]),
                        Add(g[i][l][r][x][y],g[i][l-1][r][x][y]),
                        Add(g[i][l][r][x][y],g[i][l][r-1][x][y]),
                        Del(g[i][l][r][x][y],g[i][l-1][r-1][x][y]);
    }
    write(ans),enter;
    return WDNMD;
}

USACO 2021 Open P T2

矩阵树定理,决定先鸽。

上一篇:USACO 4.2


下一篇:[USACO 2008 Jan G]Cell Phone Network