Codeforces Round 1361 简要题解

A. Johnny and Contribution

B. Johnny and Grandmaster

C. Johnny and Megan’s Necklace

D. Johnny and James

显然原点发出的各条射线上选择方案可以分别考虑。
考虑对于某条射线上的 m m m个点按到原点的距离考虑,距离分别为 d 1 < d 2 < ⋯ < d m d_1d1<d2<<dm。注意到若第 i i i个点被选择且是射线上从远到近第 j j j个被选的,那么它对答案的贡献即为 ( ( k − j ) − ( j − 1 ) ) d i = ( k − 2 j + 1 ) d i ((k-j)-(j-1))d_i=(k-2j+1)d_i ((kj)(j1))di=(k2j+1)di。考虑 k − 2 j + 1 k-2j+1 k2j+1的正负号易知,若我们共要在射线上选 t ≤ k t\leq k tk个点,当 t ≤ ⌊ k 2 ⌋ t\leq \lfloor \frac {k}{2} \rfloor t2k时会选择最远的 t t t个,否则会先选择最远的 ⌊ k 2 ⌋ \lfloor \frac {k}{2} \rfloor 2k个,再选择最近的 t − ⌊ k 2 ⌋ t-\lfloor \frac {k}{2} \rfloor t2k个。
再注意到在同一条射线上每选择一个点对答案的增量单调不增,因此有一个经典的贪心做法是将每条射线上选择 1 ∼ k 1\sim k 1k个的增量全部取出来排序后,取前 k k k大的增量之和即可,这也对应了一个方案。
时间复杂度为 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

#include 
#define FR first
#define SE second

using namespace std;

typedef long long ll;
typedef double db;
typedef pair<ll,ll> pr;

db val[500005];
int sz;

bool cmp(pr x,pr y) {
  return x.FR*x.FR+x.SE*x.SE<y.FR*y.FR+y.SE*y.SE;
}

inline db dis(pr x) {
  return sqrt(x.FR*x.FR+x.SE*x.SE);
}

void solve(vector<pr> &vt,int k) {
  int m=(k>>1),n=vt.size();
  sort(vt.begin(),vt.end(),cmp);
  for(int i=1;i<=min(m,n);i++) {
  	pr x=vt[n-i];
  	val[++sz]=dis(x)*((k-i)-(i-1));
  }
  db s=0;
  for(int i=m+1;i<=min(n,k);i++) {
  	pr x=vt[i-m-1];
  	val[++sz]=dis(x)*((k-m-1)-m)-2.0*s;
  	s+=dis(x);
  }
}

map <pr,int> mp;
vector <pr> vt[500005];

int main() {
  int n,k;
  scanf("%d%d",&n,&k);
  int cnt=0;
  for(int i=1;i<=n;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	if (!x&&!y) vt[++cnt].push_back(pr(x,y));
  	else {
  	  int d=__gcd(abs(x),abs(y));
  	  pr t(x/d,y/d);
  	  if (!mp.count(t)) mp[t]=++cnt;
  	  vt[mp[t]].push_back(pr(x,y));
    }
  }
  for(int i=1;i<=cnt;i++)
    solve(vt[i],k);
  sort(val+1,val+sz+1);
  reverse(val+1,val+sz+1);
  db s=0;
  for(int i=1;i<=k;i++) s+=val[i];
  printf("%.10f\n",s);
  return 0;
}

E. James and the Chase

简单分析一下可以知道点 a a a有趣的的条件,即以点 a a a为根求DFS树,包含每个点且所有非树边均为返祖边。
注意到题目要求当 < 20 % <20\% <20%的点是有趣的的时候直接输出 − 1 -1 1,这启发我们采用随机化算法。即我们随机 T T T个点,暴力DFS验证它们中是否存在一个有趣的点,不存在的话直接输出 − 1 -1 1,显然判断错误(将有解判为无解)的概率不超过 ( 1 − 20 % ) T = ( 4 5 ) T (1-20\%)^T=(\frac{4}{5})^T (120%)T=(54)T,当 T = 50 T=50 T=50的时候错误率已经非常小了。
如果存在的话,我们取其中一个点 x x x作为根,考虑求出所有有趣的的点。注意到以 x x x为根的DFS树包含所有点,且只包含树边与返祖边。这是一个很好的性质,考虑对于一个点 y ≠ x y\neq x y=x,以它为根的DFS树显然能包含它在 x x x为根的DFS树的子树中的所有点,且显然在以 x x x为根的DFS树的子树中必须恰好有一条返祖边(否则容易构造出到返祖边端点中较深的一个祖先的两条简单路径)。考虑返祖边端点的祖先 z z z,若 z = x z=x z=x显然是有趣的,否则分析一下可知 y y y有趣的当且仅当 z z z有趣的,那么可以用树上差分预处理一下子树中返祖边的信息后,一遍DFS判定所有点。
单组数据时间复杂度为 O ( T ( n + m ) ) \mathcal O(T(n+m)) O(T(n+m)),取 T = 50 T=50 T=50可以轻松通过。

#include 
#define FR first
#define SE second
#define inf 0x3f3f3f3f
#define MOD 1000000007

using namespace std;

typedef long long ll;
typedef pair<int,int> pr;

vector <int> e[100005];
bool vis[100005],in[100005];
int dep[100005];

bool dfs1(int x) {
  vis[x]=in[x]=1;
  for(int i=0;i<e[x].size();i++) {
  	int u=e[x][i];
  	if (!vis[u]) {
  		dep[u]=dep[x]+1;
  		if (!dfs1(u)) return 0;
	  }
	else if (!in[u]) return 0;
  }
  in[x]=0;
  return 1;
}

int siz[100005];
pr minn[100005];

void dfs2(int x) {
  for(int i=0;i<e[x].size();i++) {
  	int u=e[x][i];
  	if (dep[u]<dep[x]) {
  		siz[x]++;
  		siz[u]--;
  		minn[x]=min(minn[x],pr(dep[u],u));
	  }
	else {
		dfs2(u);
		siz[x]+=siz[u];
		minn[x]=min(minn[x],minn[u]);
	}
  }
}

bool f[100005];

void dfs3(int x) {
  if (dep[x]>1) f[x]=(siz[x]==1&&f[minn[x].SE]);
  for(int i=0;i<e[x].size();i++)
    if (dep[e[x][i]]>dep[x]) dfs3(e[x][i]);
}

int randint() {
  return ((ll)rand()*9116111716LL+rand())%1000000007;
}

int ans[100005];

int main() {
  srand(time(0));
  int cases;
  scanf("%d",&cases);
  for(;cases;cases--) {
  	int n,m;
  	scanf("%d%d",&n,&m);
  	for(int i=1;i<=n;i++) e[i].clear();
  	for(int i=1;i<=m;i++) {
  		int x,y;
  		scanf("%d%d",&x,&y);
  		e[x].push_back(y);
	  }
	int rt=0;
	for(int i=1;i<=50;i++) {
		int x=randint()%n+1;
		for(int j=1;j<=n;j++) vis[j]=in[j]=0;
		dep[x]=1;
		if (dfs1(x)) {
			rt=x;
			break;
		}
	}
	if (!rt) {
		puts("-1");
		continue;
	}
	for(int i=1;i<=n;i++) {
      siz[i]=0;
      minn[i]=pr(inf,inf);
    }
    dfs2(rt);
    for(int i=1;i<=n;i++) f[i]=0;
    f[rt]=1;
    dfs3(rt);
    int sz=0;
    for(int i=1;i<=n;i++)
      if (f[i]) ans[++sz]=i;
    if (sz*5<n) {
    	puts("-1");
    	continue;
	}
	for(int i=1;i<=sz;i++) printf("%d ",ans[i]);
	printf("\n");
  }
  return 0;
}

F. Johnny and New Toy

考虑建出 W W W序列的笛卡尔树,且令 P P P序列每个点成为左右两侧相邻的 W W W序列较大的点对应方向的孩子。那么可以发现一次操作中 M = x M=x M=x的话可能的左右端点是确定的,且相当于交换了笛卡尔树上点 x x x的左右子树。
注意到是否交换点 x x x的左右子树只会决定 x x x的左子树与右子树之间的逆序对数,且这些逆序对与其他点是否交换是无关的,因此可以对每个点分别决策,求出交换与不交换较少的逆序对数贡献到答案中。
注意到初始序列 W W W是随机的,因此根据Treap的理论分析,笛卡尔树期望最大深度是 O ( log ⁡ n ) \mathcal O(\log n) O(logn)的。那么每次修改操作显然只会影响它们到根路径上的点,用数据结构维护每个点左右子树中的权值集合,每次修改相当于删去一个权值再加入一个权值,在另一棵子树对应的数据结构中二分即可求出这个点交换后逆序对数的该变量,进而求出答案。
这里的数据结构我选择了动态开点线段树,期望时间复杂度为 O ( ( n + q ) log ⁡ 2 n ) \mathcal O((n+q)\log^2n) O((n+q)log2n),也可以优化到期望 O ( n log ⁡ n + q log ⁡ 2 n ) \mathcal O(n\log n+q\log^2n) O(nlogn+qlog2n)

#include 
#define last last2

using namespace std;

typedef long long ll;

namespace SGT {

const int Maxn=80000000;

int ch[Maxn][2],siz[Maxn],tot;

int update(int l,int r,int o,int p,int q) {
  if (!o) o=++tot;
  siz[o]+=q;
  if (l==r) return o;
  else {
  	int m=((l+r)>>1);
  	if (m>=p) ch[o][0]=update(l,m,ch[o][0],p,q);
  	else ch[o][1]=update(m+1,r,ch[o][1],p,q);
  	return o;
  }
}

int query1(int l,int r,int o,int p) {
  if (!o) return 0;
  if (l==r) return siz[o];
  else {
  	int m=((l+r)>>1);
  	if (m>=p) return query1(l,m,ch[o][0],p);
  	else return siz[ch[o][0]]+query1(m+1,r,ch[o][1],p);
  }
}

int query2(int l,int r,int o,int p) {
  if (!o) return 0;
  if (l==r) return siz[o];
  else {
  	int m=((l+r)>>1);
  	if (m<p) return query2(m+1,r,ch[o][1],p);
  	else return siz[ch[o][1]]+query2(l,m,ch[o][0],p);
  }
}

}

int num[200005],val[200005],n;
ll ans;

namespace Treap {

int ch[200005][2];
int fa[200005],dep[200005];
bool dir[200005];

void dfs1(int x) {
  if (ch[x][0]) {
  	fa[ch[x][0]]=x;
  	dep[ch[x][0]]=dep[x]+1;
  	dir[ch[x][0]]=0;
  	dfs1(ch[x][0]);
  }
  if (ch[x][1]) {
  	fa[ch[x][1]]=x;
  	dep[ch[x][1]]=dep[x]+1;
  	dir[ch[x][1]]=1;
  	dfs1(ch[x][1]);
  }
}

int root1[200005],root2[200005],siz[200005][2];
vector <int> vt[200005][2];
ll sum[200005];

void dfs2(int x) {
  if (ch[x][0]) dfs2(ch[x][0]);
  if (ch[x][1]) dfs2(ch[x][1]);
  siz[x][0]=vt[x][0].size();
  siz[x][1]=vt[x][1].size();
  for(int i=0;i<siz[x][0];i++)
    root1[x]=SGT::update(1,n,root1[x],vt[x][0][i],1);
  for(int i=0;i<siz[x][1];i++) {
    sum[x]+=SGT::query1(1,n,root1[x],vt[x][1][i]);
    root2[x]=SGT::update(1,n,root2[x],vt[x][1][i],1);
  }
  ans+=min(sum[x],(ll)siz[x][0]*siz[x][1]-sum[x]);
}

int st[200005];

void build(int n) {
  int top=0;
  for(int i=1;i<n;i++) {
  	int last=0;
  	while (top&&val[i]<val[st[top]]) {
  		ch[st[top]][1]=last;
  		last=st[top--];
	  }
	ch[i][0]=last;
	st[++top]=i;
  }
  while (top>1) {
  	ch[st[top-1]][1]=st[top];
  	top--;
  }
  dep[st[1]]=1;
  dfs1(st[1]);
  for(int i=1;i<=n;i++) {
  	int cur;
  	bool v;
  	if (dep[i-1]>dep[i]) {
  		cur=i-1;
  		v=1;
	  }
	else {
		cur=i;
		v=0;
	}
	while (cur) {
		vt[cur][v].push_back(num[i]);
		v=dir[cur];
		cur=fa[cur];
	}
  }
  dfs2(st[1]);
}

void update(int x,bool v,int p,int q) {
  while (x) {
  	ans-=min(sum[x],(ll)siz[x][0]*siz[x][1]-sum[x]);
  	if (!v) {
  		sum[x]-=SGT::query2(1,n,root2[x],p);
  		sum[x]+=SGT::query2(1,n,root2[x],q);
  		root1[x]=SGT::update(1,n,root1[x],p,-1);
  		root1[x]=SGT::update(1,n,root1[x],q,1);
	  }
	else {
		sum[x]-=SGT::query1(1,n,root1[x],p);
		sum[x]+=SGT::query1(1,n,root1[x],q);
		root2[x]=SGT::update(1,n,root2[x],p,-1);
		root2[x]=SGT::update(1,n,root2[x],q,1);
	}
	ans+=min(sum[x],(ll)siz[x][0]*siz[x][1]-sum[x]);
	v=dir[x];
	x=fa[x];
  }
}

}

int main() {
  scanf("%d",&n);
  for(int i=1;i<=n;i++) scanf("%d",&num[i]);
  for(int i=1;i<n;i++) scanf("%d",&val[i]);
  Treap::build(n);
  int m;
  scanf("%d",&m);
  for(int i=1;i<=m;i++) {
  	int x,y;
  	scanf("%d%d",&x,&y);
  	if (x!=y) {
  		Treap::update(((Treap::dep[x-1]>Treap::dep[x])?x-1:x),(Treap::dep[x-1]>Treap::dep[x]),num[x],num[y]);
  		Treap::update(((Treap::dep[y-1]>Treap::dep[y])?y-1:y),(Treap::dep[y-1]>Treap::dep[y]),num[y],num[x]);
  		swap(num[x],num[y]);
	  }
	printf("%lld\n",ans);
  }
  return 0;
} 

你可能感兴趣的:(codeforces,图论,笛卡尔树)