USACO 2021 US Open 金组题解

终于 AK 了!

Posted by ethan-zhou on April 3, 2021

T1 United Cows of Farmer John

思路

这题比较水,记 $pre_i$ 为 i 号奶牛的前驱(即 i 前面最靠后的与 i 号奶牛颜色相同的奶牛)。

令这两个奶牛领导中,在序列中靠后的那个为 i,考虑靠前的奶牛领导有几种可能,不难发现其个数为 $[pre_i+1,i-1]$ 这段区间内奶牛的种类数。于是直接上树状数组,用代表元法(将每种种类的最后出现位置加一)即可求出答案。复杂度 $O(n\log n)$

代码

#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rd scanf
#define pt printf
#define setp(x) setprecision(x)
#define mem(x,y) memset(x,y,sizeof(x))
#define sz(x) (int)x.size()
#define umn(x,y) x=min(x,y)
#define umx(x,y) x=max(x,y)
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> pi;
typedef queue<ll> qi;
typedef vector<ll> vi;
typedef vector<pi> vpi;

const ll INF=1e18;
const ll P=1e9+7;
const ll MXN=2e5+5;

inline ll mod(ll x){return x>=P?x-P:x;}
inline ll mul(ll x,ll y){if(x<y)swap(x,y);ll res=0;while(y){if(y&1)res=mod(res+x);x=mod(x<<1);y>>=1;}return res;}
inline ll qpow(ll x,ll y){ll res=1;while(y){if(y&1)res=res*x%P;x=x*x%P;y>>=1;}return res;}
ll gcd(ll x,ll y){return !y?x:gcd(y,x%y);}
inline void pbin(ll x,ll y){for(;~y;y--)cerr<<char('0'+((x>>y)&1));cerr<<endl;}



ll n,m,tmp,res;
ll c[MXN],last[MXN];
inline void mod(ll x,ll y){for(;x<=n;x+=x&(-x))c[x]+=y;}
inline ll sum(ll x){ll res=0;for(;x;x-=x&(-x))res+=c[x];return res;}
inline void solve(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++){
		scanf("%lld",&tmp);
		res+=sum(i)-sum(last[tmp]);
		if(last[tmp])mod(last[tmp],-1);
		mod(last[tmp]=i,1);
	}
	printf("%lld",res);
    
}
int main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	//ios_base::sync_with_stdio(0);cin.tie(0);
	cout<<setiosflags(ios::fixed);
	srand(time(NULL));

	ll tq=1;
	//cin>>tq;
	while(tq--)solve();
	return 0;
}

T2 Portals

题意

这题题意比较难懂,我看了半天才看明白。简化的大意就是:

  • 有 2n 个点,以及 n 个四元组。
  • 四元组 $p_1,p_2,p_3,p_4$ 代表 $p_1$ 向 $p_2$ 连一条无向边,$p_3$ 向 $p_4$ 连一条无向边
  • 你可以花 $c_i$ 的代价将第 i 个四元组重新排列
  • 保证初始状态下每个点的度数都为 2
  • 求使得整个图联通的最小代价

思路

看到这题立刻感觉这可能是一道巧妙的最小生成树题(前一阵省选集训就有一道最小生成树题我没做出来,故印象深刻),手玩了几组发现确实是这样的。

初始情况下,图被分成若干个联通块,并且对于任意四元组,有结论 $p_1$ 与 $p_2$,$p_3$ 与 $p_4$ 在同一个联通块中(都连边了所以显然)。使整个图联通,就是要把这些联通块都合并起来。

根据每个节点的度数都为 2 的关键性质,不难证明每个联通块都必定是一个环。所以对于一个四元组,如果 $p_1$ 与 $p_3$ 不在同一块中,那么我就可以通过重排这个四元组,把这两个点所在的块合并,并且产生的新的联通块仍然是一个环!如下图所示:

所以这题的做法就显而易见了,我们先将每个联通块缩成一个点,然后从每个四元组的前两个点所在块向后两个点所在块连边,边权为 $c_i$,然后跑 kruskal 即可。

代码

#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rd scanf
#define pt printf
#define setp(x) setprecision(x)
#define mem(x,y) memset(x,y,sizeof(x))
#define sz(x) (int)x.size()
#define umn(x,y) x=min(x,y)
#define umx(x,y) x=max(x,y)
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> pi;
typedef queue<ll> qi;
typedef vector<ll> vi;
typedef vector<pi> vpi;

const ll INF=1e18;
const ll P=1e9+7;
const ll MXN=2e5+5;

inline ll mod(ll x){return x>=P?x-P:x;}
inline ll mul(ll x,ll y){if(x<y)swap(x,y);ll res=0;while(y){if(y&1)res=mod(res+x);x=mod(x<<1);y>>=1;}return res;}
inline ll qpow(ll x,ll y){ll res=1;while(y){if(y&1)res=res*x%P;x=x*x%P;y>>=1;}return res;}
ll gcd(ll x,ll y){return !y?x:gcd(y,x%y);}
inline void pbin(ll x,ll y){for(;~y;y--)cerr<<char('0'+((x>>y)&1));cerr<<endl;}


struct edge{
	ll ts,tt,tw;
	edge(ll ts=0,ll tt=0,ll tw=0):ts(ts),tt(tt),tw(tw){}
	bool operator < (const edge &b)const{return tw<b.tw;}
}ne[MXN];

ll n,res,colc,color[MXN];
vector<ll> e[MXN];
void dfs(ll p){
	color[p]=colc;
	for(size_t i=0;i<e[p].size();i++){
		ll nx=e[p][i];
		if(color[nx])continue;
		dfs(nx);
	}
}

ll fa[MXN];
ll find(ll x){return x==fa[x]?x:fa[x]=find(fa[x]);}
inline void solve(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++){
		ll x,y,z,w,va;
		scanf("%lld%lld%lld%lld%lld",&va,&x,&y,&z,&w);
		ne[i]=edge(x,z,va);
		e[x].pb(y),e[y].pb(x);
		e[z].pb(w),e[w].pb(z);
	}
	for(int i=1;i<=(n<<1);i++)
		if(!color[i]){
			++colc;
			fa[colc]=colc;
			dfs(i);
		}
	for(int i=1;i<=n;i++)
		ne[i].ts=color[ne[i].ts],ne[i].tt=color[ne[i].tt];
	sort(ne+1,ne+1+n);
	for(int i=1;i<=n;i++)
		if(find(ne[i].ts)!=find(ne[i].tt)){
			fa[fa[ne[i].ts]]=fa[ne[i].tt];
			res+=ne[i].tw;
		}
	printf("%lld",res);
}
int main(){
	//freopen(".in","r",stdin);
	//freopen(".out","w",stdout);
	ios_base::sync_with_stdio(0);cin.tie(0);
	cout<<setiosflags(ios::fixed);
	srand(time(NULL));

	ll tq=1;
	//cin>>tq;
	while(tq--)solve();
	return 0;
}

T3 Permutation

思路

第一次接触计算几何类的题,没想到还写对了哈哈!

Key Observation

对于一个合法的加点顺序,在任意时刻,这个图形都是一个内部被划分为若干个小三角形的大三角形。这个结论可以归纳证明:

  • 在加入了前三个点后,图形为一个三角形,结论成立。
  • 设加入了前 k 个点时,结论仍然成立,且最外围的三角形由 A,B,C 构成,讨论加入第 k+1 个点 P 之后的情况:
    • 如果新加入的点在三角形 ABC 外,则这个点能且只能向 A,B,C 连边,并且这个点只能在下图中的蓝色阴影中(否则将只能连两条边)。以 P 在 A 上方阴影处为例,结论显然成立,而 B,C 都是对称的,所以也成立。

    • 如果新加入的点在三角形 ABC 内,并在某个小三角形 abc 内,则能且只能由 P 向 a,b,c 连边,可见结论依然成立。

  • 所以,对于 $\forall k \in [3,n]$,结论成立。

DP 状态与转移

到这里,dp 状态也就呼之欲出了:$dp[i][j][k][l]$ 表示当前图形的最外围的三角形由 i,j,k 构成,并且在这个三角形的内部(不包括顶点)我们已经选了 l 个点的总方案数。

此外,我们还需要预处理一个辅助的数组 $cnt[i][j][k]$ 代表三角形 ijk 之内有多少个点(不包括三个顶点)。

转移的顺序是什么?将三元组 $(i,j,k)$ 按照 $cnt[i][j][k]$ 排序即可,因为在转移的过程中,最外围的三角只会扩张,不会缩小,所以这样就没有后效性了。

如何转移呢?考虑我为人人型 dp,在当前的状态下,我取的下一个点 P 可能是:

  • 三角形 ijk 内部的点:$dp[i][j][k][l+1]+=dp[i][j][k][l]\times(cnt[i][j][k]-l)$
  • 三角形 ijk 外部的点:枚举新加的点,新的状态的前三维需要重新计算,代表新的大三角的顶点,而第四维还是 l+1。

于是我们就实现了一个 $O(n^5)$ 的 dp,是可以通过 $n=40$ 的这题的。但是在上述算法预处理,以及重新计算dp的前三维时,需要判断某个点是否在另外三个点组成的三角形内,我们下面介绍如何实现。

判断是否在三角形内

假设我们要判定 P 是否在三角形 ABC 内,即判定 P 是否在 AB,BC,CA 同侧。即:$\vec{AP}\times\vec{AB}$,$\vec{BP}\times\vec{BC}$,$\vec{CP}\times\vec{CA}$ 的奇偶性相同,手写个向量类型就能够实现了。

代码

#include<bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define pb push_back
#define mp make_pair
#define rd scanf
#define pt printf
#define setp(x) setprecision(x)
#define mem(x,y) memset(x,y,sizeof(x))
#define sz(x) (int)x.size()
#define umn(x,y) x=min(x,y)
#define umx(x,y) x=max(x,y)
typedef long long ll;
typedef unsigned long long ull;
typedef pair<ll,ll> pi;
typedef queue<ll> qi;
typedef vector<ll> vi;
typedef vector<pi> vpi;

const ll INF=1e18;
const ll P=1e9+7;
const ll MXN=45;

inline ll mod(const ll &x){return x>=P?x-P:x;}
inline ll mul(ll x,ll y){if(x<y)swap(x,y);ll res=0;while(y){if(y&1)res=mod(res+x);x=mod(x<<1);y>>=1;}return res;}
inline ll qpow(ll x,ll y){ll res=1;while(y){if(y&1)res=res*x%P;x=x*x%P;y>>=1;}return res;}
ll gcd(ll x,ll y){return !y?x:gcd(y,x%y);}
inline void pbin(ll x,ll y){for(;~y;y--)cerr<<char('0'+((x>>y)&1));cerr<<endl;}



ll n,top;
ll cnt[MXN][MXN][MXN],dp[MXN][MXN][MXN][MXN];
struct vec{
	ll x,y;
	vec(ll x=0,ll y=0):x(x),y(y){}
	vec operator + (const vec &b)const{return vec(x+b.x,y+b.y);}
	vec operator - (const vec &b)const{return vec(x-b.x,y-b.y);}
	ll operator * (const vec &b)const{return x*b.y-b.x*y;}
}arr[MXN];
inline bool intri(const vec &x,const vec &a,const vec &b,const vec &c){
	ll res_ab=(b-a)*(x-a);
	ll res_bc=(c-b)*(x-b);
	ll res_ca=(a-c)*(x-c);
	return (res_ab>0 && res_bc>0 && res_ca>0) || (res_ab<0 && res_bc<0 && res_ca<0);
}
inline bool expand(const ll &x,ll &a,ll &b,ll &c){
	if(intri(arr[a],arr[x],arr[b],arr[c]))a=x;
	else if(intri(arr[b],arr[x],arr[c],arr[a]))b=x;
	else if(intri(arr[c],arr[x],arr[a],arr[b]))c=x;
	else return 0;

	if(a>b)swap(a,b);
	if(b>c)swap(b,c);
	if(a>b)swap(a,b);

	return 1;
}
struct status{
	ll i,j,k;
	bool operator < (const status &b)const{return cnt[i][j][k]<cnt[b.i][b.j][b.k];}
}sta[MXN*MXN*MXN];
inline void solve(){
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)scanf("%lld%lld",&arr[i].x,&arr[i].y);

	ll mx=0;
	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 l=1;l<=n;l++)
					cnt[i][j][k]+=intri(arr[l],arr[i],arr[j],arr[k]);
				mx=max(mx,cnt[i][j][k]);
				dp[i][j][k][0]=6;
				sta[++top]=status{i,j,k};
			}
	if(mx<n-3){
		printf("0");
		return;
	}
	sort(sta+1,sta+1+top);
	//当前边框为i,j,k,边框内选了l个
	for(int stai=1;stai<=top;stai++){
		ll i=sta[stai].i,j=sta[stai].j,k=sta[stai].k;

		ll tmp=cnt[i][j][k],ni,nj,nk;
		for(int l=0;l<=tmp;l++){
			dp[i][j][k][l+1]=(dp[i][j][k][l+1]+dp[i][j][k][l]*(cnt[i][j][k]-l))%P;
			ni=i,nj=j,nk=k;
			for(int m=1;m<=n;m++)
				if(expand(m,ni,nj,nk)){
					dp[ni][nj][nk][l+1]=mod(dp[ni][nj][nk][l+1]+dp[i][j][k][l]);
					ni=i,nj=j,nk=k;
				}
		}
	}
	printf("%lld",dp[sta[top].i][sta[top].j][sta[top].k][n-3]);
}
int main(){
	ios_base::sync_with_stdio(0);cin.tie(0);
	srand(time(NULL));

	ll tq=1;
	while(tq--)solve();
	return 0;
}

作者:@ethan-enhe
本文为作者原创,转载请注明出处:本文链接



icon