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(nlog 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
",ans);
}
USACO 2021 Open G T2
Solution
对于每一个点拆点,拆成 (4) 个点分别表示四个传送门,将传送门转为边,同时相邻两点也转成边,此时图会被划分成若干个连通块。
不难发现每一次操作,我们都会将两个连通块进行连通,其费用为 (c_i)。
那么问题变成了最小生成树,可以直接贪心。
时间复杂度 (O(nlog 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
",E[i].x,E[i].y,E[i].v);
// printf("%d
",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) 个点没选的方案数。
我们思考这个加点的过程,发现一个点要想被加进来,必须满足如下两个条件:
- 其在三角形内。
- 延长三角形三边,其在三角形对角所覆盖的范围内。
发现第二种情况完全不会判,怎么办呢,不判。
对,不判,我们直接记忆化搜索,从最大的那个三角形开始搜起,每次枚举一个在三角形内的点,递归下去并转移。
这样看起来时间复杂度是 (O(n^7)) 的,但常数很小,总之就是过了。
具体实现细节见代码:
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
",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)
",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
",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
",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(nlog 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
",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
",pre[pre[i]]+1,pre[i]-1,-1);
update_add(1,1,n,pre[i]+1,i-1,1);
// printf("Add %d,%d,%d
",pre[i]+1,i-1,1);
update_ban(1,1,n,pre[i]);
// printf("Ban %d
",pre[i]);
}
printf("%lld
",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
矩阵树定理,决定先鸽。