NOI.ac2020省选模拟赛13

比赛链接

A.制胡窜

problem

给出一棵\(n\)个节点的树,树的每个节点上有一个字母,选出一条路径使得路径上的字符连起来构成回文串,问这个回文串最长可以是多长。

solution

留坑待填

B.摔跤

problem

\(N\)个精灵和\(N\)个矮人正在举行摔跤大赛。\(N\)个矮人顺时针依次站在一个圆周上。
\(i\)个精灵将要和第\(A_i\)个矮人摔跤。然而,由于主办方的失误,\(A\)中可能有相同的元素。精灵将按照一定的顺序入场。入场的\(x\)号精灵将优先和\(A_x\)号矮人摔跤,但如果当前矮人已经和其他精灵摔过跤了,\(x\)号精灵将尝试和顺时针方向的下一个矮人摔跤,以此类推,直到找到一个还没有摔过跤的矮人。
\(i\)号矮人的力量值为\(P_i\)\(i\)号精灵的力量值为\(V_i\)。所有的力量值两两不同。在摔跤时,力量值高的一方一定会获胜。精灵们想知道,如果妥善安排入场顺序,最多有多少精灵获胜?

solution

可以发现有一个位置是不可能有精灵能够跨过去的,如何找到这个位置呢?

\(t_i\)表示想要与第\(i\)个矮人摔跤的数量。对\(t_i-1\)求前缀和,前缀和最小的位置就是那个不能被跨过的位置。

然后在不能被跨过的这个位置割开环,就拆成了一个序列。

从左往右走,用一个set维护当前可以出战的精灵能量值,如果对于一个矮人,set里没有精灵的能量比他大,那就让最小的精灵和他摔跤,否则就让能量值比他大的最小的精灵和他摔跤。

code

/*
* @Author: wxyww
* @Date:   2020-06-13 09:29:43
* @Last Modified time: 2020-06-13 20:28:59
*/
#include<cstdio>
#include<set>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 1000010;
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}
vector<int>t[N];
int p[N],v[N];
set<int>s;
int main() {
	int n = read();
	for(int i = 1;i <= n;++i)
		t[read()].push_back(i);
	int pos = 0,mn = n + 1,sum = 0;

	for(int i = 1;i <= n;++i) p[i] = read();
	for(int i = 1;i <= n;++i) v[i] = read();

	for(int i = 1;i <= n;++i) {
		sum += t[i].size() - 1;
		if(sum < mn) {
			mn = sum;pos = i;
		}
	}
	int ans = 0;
	for(int i = pos + 1 > n ? 1 : pos + 1;;(++i) > n ? i = 1 : 0) {
		for(vector<int>::iterator it = t[i].begin();it != t[i].end();++it) s.insert(v[*it]);

		if(*s.rbegin() < p[i])
			s.erase(s.begin());
		else {
			++ans;
			s.erase(s.lower_bound(p[i]));
		}
		if(i == pos) break;
	}
	cout<<ans<<endl;

	return 0;
}

C.病毒

problem

给出一个\(n\)个节点的树,初始的时候\(1\)号节点感染了病毒。以后每一秒病毒都会向树的叶子方向传递一层。每一秒也可以关闭一个节点,使得病毒无法向这个节点传播。问最短过多久,不在有新的节点被感染。

\(2\le n\le 400\)

solution

其实也就是要找到k个深度依次为\(,1,2,\cdots,k\)的节点,使得可以覆盖所有深度大于\(k\)的节点。

如果答案是\(k\),那么满足\(k^2\le n\)

证明:为了方便证明,我们考虑有根树森林。一个森林作为状态表示在这一秒结束时病毒将传播到森林中的每棵树的根上。
我们可以删去所有不含深度大于\(k\)的节点的子树。
考虑如下策略:考虑每棵树的根到最近的有至少两个子节点的路径上的点数,优先选择上述点数小的树。在选择完一个这样的点后,我们可以把它所在的树删去。
假设我们在依次选择了深度为\(1,2,\cdots,d\)\(d\)个点后第一次选到了一个有至少两个子节点的点。我们选择的这\(d\)个点所在的树每棵至少有\(2k-d\)个节点,因此删完后的森林中至多有\(k^2-d(2k-d)=(k-d)^2\)个点。使用归纳法,我们可以发现结论成立。

有了这个结论之后,我们知道答案不超过\(20\)。所以我们枚举一个答案\(k\),然后想办法check这个答案是否可行。

如何\(check\)呢?考虑状态压缩,先求一遍树的\(dfs\)序,然后将所有深度大于\(k\)的节点放到一个数组里面。考虑用\(f[i][j]\)表示到了dfs序中的第i个点,现在已经选择的深度状态是\(j\)是否可行。对于深度大于k的节点我们无法转移,对于深度小于等于k的节点,我们可以选择用当前这个节点来覆盖他的子树。在\(dfs\)序的序列上转移即可。

code

/*
* @Author: wxyww
* @Date:   2020-06-13 21:17:41
* @Last Modified time: 2020-06-13 22:07:37
*/
#include<cstdio>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<queue>
#include<vector>
#include<ctime>
#include<cmath>
using namespace std;
typedef long long ll;
const int N = 403;
ll read() {
	ll x = 0,f = 1;char c = getchar();
	while(c < '0' || c > '9') {
		if(c == '-') f = -1; c = getchar();
	}
	while(c >= '0' && c <= '9') {
		x = x * 10 + c - '0'; c = getchar();
	}
	return x * f;
}

struct node {
	int v,nxt;
}e[N << 1];
int head[N],ejs;
void add(int u,int v) {
	e[++ejs].v = v;e[ejs].nxt = head[u];head[u] = ejs;
}

int dep[N],dfn[N],siz[N];

int n,tot;
void dfs(int u,int fa) {
	dfn[u] = ++tot;
	dep[dfn[u]] = dep[dfn[fa]] + 1;
	siz[dfn[u]] = 1;
	for(int i = head[u];i;i = e[i].nxt) {
		int v = e[i].v;
		if(v == fa) continue;
		dfs(v,u);
		siz[dfn[u]] += siz[dfn[v]];
	}

}
int f[N][524288];
int lst[N],tmp[N];
int solve(int x) {
	// memset(f,0,sizeof(f));


	int m = 0;
	for(int i = 1;i <= n;++i) {
		if(dep[i] >= x) {
			// printf("%d ",i);
			tmp[++m] = i;
		}
		
	}
	int p = m + 1;

	for(int i = 1;i <= m + 1;++i) {
		for(int j = 0;j < (1 << x);++j)
			f[i][j] = 0;
	}

	for(int i = n + 1;i >= 1;--i) {
		while(tmp[p - 1] >= i) --p;
		lst[i] = p;
	}

	f[1][0] = 1;

	p = 1;
	// cout<<siz[5]<<endl;
	for(int i = 2;i <= n;++i) {
		if(dep[i] > x) continue;

		while(tmp[p] < i && p < m) ++p;
		
		// printf("%d\n",p);

		for(int j = 0;j < (1 << x);++j) {
			if(!(j >> (dep[i] - 1) & 1)) {
				if(i == 5 && j == 0) {
					// cout<<lst[i + siz[i]]<<endl;
					
				}
				f[lst[i + siz[i]]][j | (1 << (dep[i] - 1))] |= f[p][j];
			}
		}
	}

	// cout<<f[3][1]<<endl;

	for(int i = 0;i < (1 << x);++i) if(f[m + 1][i]) return 1;
	return 0;

}
int main() {
	n = read();
	for(int i = 1;i < n;++i) {
		int u = read(),v = read();
		add(u,v);add(v,u);
	}
	dep[0] = -1;
	dfs(1,0);
	// puts("");
	// for(int i = 1;i <= n;++i) printf("%d %d\n",i,dfn[i]);
	// puts("");
	// cout<<solve(3);
	// return 0;
	for(int i = 1;i <= n;++i) {
		if(solve(i)) {
			cout<<i<<endl;return 0;
		}
	}

	return 0;
}
posted @ 2020-06-14 10:18  wxyww  阅读(17)  评论(0编辑  收藏  举报