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
本文为作者原创,转载请注明出处:本文链接
阅读量: