IOI2020 部分题解

DIT2 连接擎天树

题意:

一个 \(N\) 个点的无向图,给定每两个点之间的简单路径条数 \(P_{i,j}\) ,构造一个满足要求的无向图或判断无解。

\((1\le N\le10^3,0\le P_{i,j}\le 3)\)

题解:

这个 \(P_{i,j}=3\) 非常烦,但是可以发现若 \(P_{i,j}=3\) 则无解。

考虑一个最简单的情况(其它情况一定可以从这种情况加一些点得到):

\(2\)\(4\)\(3\) 条路径,则 \(1\)\(3\) 必有 \(4\) 条路径。

所以有 \(P_{i,j}=3\) 判无解即可。

现在只需要构造一个符合要求的基环树森林。

先定义一下一个基环树子树是没有环上的边的一个极大连通子图(假设一棵树也是一个基环树子树)。

首先假装是一定能构造出来的:

  • 若两点之间路径数为 \(1\) ,则必在一个基环树的子树中。

  • 若两点之间路径数为 \(2\) ,则必在一个基环树中,但不在一个基环树子树中。

判断构造是否合法只需要按照定义判定一下。如果之间没有路径也会被判断到的。

实现:

先遍历每一个点,以这个点为该基环树的根(若访问过则跳过)。

首先有一个大队列,用来 BFS 整棵基环树每棵子树的代表。

接着大队列里有一个小队列,用来 BFS 一棵基环树子树中的元素。

构造树只需要链,构造环顺次连接,此时一棵基环树子树中的元素其实是等价的。

这样实际上是做到了每一个可能在一棵基环树子树,一棵基环树中的点都放在了一起,因此只需特判了。

时间复杂度: \(O(N^2)\)

代码:

#include <bits/stdc++.h>
#include "supertrees.h"
using namespace std;
typedef vector<vector<int> > va;
const int N=1005;
int n,idc[N],idt[N];
bool vis[N];
inline void add(int u,int v,va&g) {g[u][v]=g[v][u]=1;}
int construct(va p){
	n=p.size();va g(n,vector<int>(n));
	for(int i=0;i<n;i++)
		if(find(p[i].begin(),p[i].end(),3)!=p[i].end()) return 0;
	for(int i=0;i<n;i++)
		if(!vis[i]){
			queue<int> qc;qc.push(i);vector<int> c;
			while(!qc.empty()){
				int u=qc.front();qc.pop();if(vis[u]) continue;
				queue<int> qt;qt.push(u);vector<int> t;c.push_back(u);
				while(!qt.empty()){
					int x=qt.front();qt.pop();if(vis[x]) continue;
				t.push_back(x);idt[x]=u;idc[x]=i;vis[x]=1;
				for(int j=0;j<n;j++)
					if(p[x][j]==1) qt.push(j);
					else if(p[x][j]==2) qc.push(j);
			}
			for(int j=0;j+1<t.size();j++) add(t[j],t[j+1],g);
		}
		int sz=c.size();if(sz==2) return 0;
		if(sz>2) for(int j=0;j<sz;j++) add(c[j],c[(j+1)%sz],g);
	}
	for(int i=0;i<n;i++)
		for(int j=i+1;j<n;j++)
			if(idt[i]==idt[j]) {if(p[i][j]!=1) return 0;}
			else if(idc[i]==idc[j]) {if(p[i][j]!=2) return 0;}
	build(g);return 1;
}

D1T3 嘉年华奖券

题意:

给定 \(N\) 个大小为 \(M\) 的集合 \(X\) ,进行 \(K\) 次操作:从每个集合选取一个数并删掉,并钦定一个数,使得所有选取的数和这个数的绝对值之和最小,对答案的贡献为这个最小的绝对值。求答案并构造方案使得答案最大。

\(2\le N\le1500\)\(N\) 为偶数 , \(1\le K\le M\le1500,1\le X_{i,j-1}\le X_{i,j}\le10^9\)

题解:

假设每次选出的数集为 \(S\)

这个数显然就是这些数的中位数,再化简可得贡献是(以从小到大排序): \(\sum_{i=\frac{n}{2}+1}^{n}S_i-\sum_{i=1}^{\frac{n}{2}}S_i\)

接下来考虑哪些数是正贡献,哪些数是负贡献,哪些数没贡献。

一定是每个集合开头的若干个数有负贡献,末尾的若干个数有正贡献。若中间的数有贡献,不如将其替换成更靠近开头或结尾的更优。

又因为每个集合恰好选 \(K\) 个数,则只会选前 \(K\) 大或前 \(K\) 小的,不妨做一个对应:第 \(i\) 大的和第 \(K-i+1\) 小的只能选一个。

接着在这 \(NK\) 个对应中,选出 \(\frac{NK}{2}\) 组对应,使这组对应选择小的那个,按照负贡献从小到大排序,可以得到理论上的最优解。

这个负贡献其实就是对应的两个数之和。

接下来其实随便放都可以了,一定能满足正贡献的数不小于负贡献的数:

设正贡献的数为 \(X_1\) ,对应的数是 \(Y_1\) ,设负贡献的数为 \(X_2\) ,对应的数是 \(Y_2\)

\(X_1\ge Y_1,X_2\le Y_2,X_1+Y_1\ge X_2+Y_2\) ,所以 \(X_1>X_2\)

接下来随便构造一下就行了,只需要保证每次选择都恰好有 \(\frac{n}{2}\) 个是大的或小的。

一个集合一个集合地取,给小的少的多分配一些小的即可。

时间复杂度: \(O(NM\log (NM))\)

代码:

#include <bits/stdc++.h>
#include "tickets.h"
#define fi first
#define se second
using namespace std;
typedef long long ll;
typedef vector<int> vec;
typedef vector<vec> vvec;
const int N=1505;
int n,m,d[N],s[N];
ll sum;
vector<pair<int,int> > v,l;
ll find_maximum(int k,vvec x){
	n=x.size();m=x[0].size();
	vvec ans(n,vec(m,-1));
	for(int i=0;i<n;i++)
		for(int j=0;j<k;j++) {sum+=x[i][m-1-j];v.push_back(make_pair(x[i][j]+x[i][m-k+j],i));}
	sort(v.begin(),v.end());
	for(int i=0;i<n*k/2;i++) {sum-=v[i].fi;d[v[i].se]++;}
	for(int i=0;i<n;i++){
		l.clear();
		for(int j=0;j<k;j++) l.push_back(make_pair(s[j],j));
		sort(l.begin(),l.end());
		for(int j=0;j<k;j++)
			if(j<d[i]) {ans[i][j]=l[j].se;s[l[j].se]++;}
			else ans[i][m-k+j]=l[j].se;
	}
	allocate_tickets(ans);return sum;
}

D2T3 网络站点

题意:

调用两次程序:

  • 第一次:

给定 \(R\) 棵树,并给树重新标上一个排列,返回这个排列。

  • 第二次:

程序会随便给你一棵树(不告诉你是哪棵),并给定树上的起点终点和起点的邻居,询问下一步走哪里。(都是你返回的标号)

邻居集合为 \(C\) ,每棵树大小为 \(N\)

\(1\le R\le10,2\le N\le10^3,\sum|c|\le10^5\)

题解:

显然先考虑用 DFS序 标号,因为这样是连续的,可以确定要往哪棵子树走。

但是不能区分是往最后一颗子树或者往父亲走。

假如知道这棵子树的出树的时间戳,就可以区分了。

考虑把一棵子树的根结点的编号改成出树的时间戳。

但是这样会让这个根结点做儿子的时候出问题,因为根结点不再是整棵子树中最小的,而是最大的了。

这个时候可以通过多次尝试或观察或揣摩出题人意图发现,若这一层的编号都是出树的时间戳,那么其父亲仍然是入树的时间戳即可。

正确性模拟一遍很显然。

询问的时候其实可以直接将所有的子树判断一遍,并不复杂,但也可以更简单,见代码。

其实我觉得这个二分虽然能省码量和时间(这时间最坏情况下还是一样的),但是不如直接判断一遍写着舒服。

时间复杂度: \(O(RN+|C|)\)

代码:

#include <bits/stdc++.h>
#include "stations.h"
#define y e[x][i]
using namespace std;
typedef vector<int> vec;
void dfs(int x,int fa,bool d,int&sum,const vector<vec> &e,vec&res){
	if(d) res[x]=sum++;
	for(int i=0;i<e[x].size();i++)
		if(y!=fa) dfs(y,x,d^1,sum,e,res);
	if(!d) res[x]=sum++;
}
vec label(int n,int k,vec u,vec v){
	vec res(n);vector<vec> e(n,vec());
	for(int i=0;i<n-1;i++) {e[u[i]].push_back(v[i]);e[v[i]].push_back(u[i]);}
	int sum=0;dfs(0,-1,1,sum,e,res);return res;
}
int find_next_station(int s,int t,vec c){
	if(c[0]>s){
		if(t<s||c.back()<t) return c.back();
		return *lower_bound(c.begin(),c.end(),t);
	}
	if(t<c[0]||s<t) return c[0];
	return *(upper_bound(c.begin(),c.end(),t)-1);
}
posted @ 2020-12-19 19:32  shrtcl  阅读(415)  评论(0编辑  收藏  举报