清北学堂考前综合刷题班第二场

国庆集训Day2

A. 小灯笼

题目描述

一条平直的公路上有\(n\)个小路灯,第\(i\)个路灯的坐标是\(a_i\)。小A需要把其中的\(k\)个点亮,使得每个小路灯与距离最近的被点亮的小路灯的距离的最大值最小。求这个最小值。

输入格式

\(1\)\(2\)个正整数\(n,k\)

接下来\(n\)行,每行一个整数\(a_i\),表示第\(i\)个小路灯的坐标,保证\(a_i\)是严格单调递增的。

输出格式

\(1\)\(1\)个数表示答案。

样例数据

input

6 3
5
6
12
19
20
27

output

6

30分:

不妨设\(dp[i,j]\)表示前\(i\)个小路灯其中第\(i\)个亮,总共j个。
\(dp[i,j]=dp[l,j-1]+g(l+1,i)\)


100分:二分

一看题面,最小化最大值就知道这极有可能是二分。
框架不难,按照贪心原则,当前灯能不亮就不亮。

C++ AC代码

#include<iostream>
#include<cstdio>
using namespace std;
const int SIZE = 100000 + 5, INF = 1 << 30;
int n, k;
int a[SIZE];
bool valid(int x)
{
	int last = -INF, cnt = 0;
	for(int j = 1; j <= n; ++ j)
	{
		if(a[j] - last <= x) continue;
		int i = j;
		++ j;
		while(j <= n)
		{
			if(a[j] - a[i] > x)
			{
				++ cnt;
				last = a[j - 1];
				-- j;	
				break;
			}
			++ j;
		}
	}
	if(a[n] - last > x) ++ cnt;
	if(cnt <= k) return true;
	return false;
}
int main()
{
	scanf("%d %d", &n, &k);
	int L = INF, R = -1, mid;
	for(int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
	R = a[n] - a[1];
	for(int i = 2; i <= n; ++ i) L = min(L, a[i] - a[i - 1]);
	while(L < R)
	{
		mid = L + ((R - L) >> 1);
		if(valid(mid)) R = mid;
		else L = mid + 1;
	}
	printf("%d\n", L);
	return 0;
}

B. 序列

题目描述

设数列\(P=[p_1,p_2,……,p_n]\),定义\(f(p,k)=[p_2,p_3,……,p_k,p_1,p_{k+2},p_{k+3},……,p_{2k},p_{k+1},……]\),也就是把\(P\)\(k\)个分成一段(最后如果不足\(k\)个,把它们分到新的一段),然后将每段的首个元素移动到该段末尾。求\(f(f(……f(f([1,2,……,n],2),3),……),n)\)

输入格式

\(1\)\(1\)个正整数\(n\)

输出格式

\(1\)\(n\)个数表示答案。

样例数据

input

4

output

4 2 3 1

此题实在是思维好题!
首先,我们可以观察整个数组改变的位置。
明显,我们可以把每次操作中不是首项的数不动,其他的数位置\(+i\)

C++ AC代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn = 1000000 + 5;
int n, p[maxn << 1];
int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; ++ i) p[i] = i;
	int delta = 0;
	for(int i = 2; i <= n; ++ i)
	{
		int cur = 0;
		for(int j = delta + 1; j <= n + delta; j += i) swap(p[j], cur);	
		++ delta;
		swap(p[n + delta], cur);
	}
	for(int i = n; i < n << 1; ++ i) printf("%d ", p[i]);
	return 0;
} 

C. 路灯

题目描述

一条平直的公路上有\(n\)个路灯,第\(i\)个路灯的坐标是\(a_i\)。小A需要把其中的\(k\)个点亮,使得每个路灯与距离最近的被点亮的路灯的距离和最小。求这个最小值。

输入格式

\(1\)\(2\)个正整数\(n,k\)

接下来\(n\)行,每行一个整数\(a_i\),表示第\(i\)个路灯的坐标,保证\(a_i\)是严格单调递增的。

输出格式

\(1\)\(1\)个数表示答案。

样例数据

input

6 3
5
6
12
19
20
27

output

8

100分 DP

考虑\(dp[i,j,k]\)代表\([i,j]\)中点亮了\(k\)盏路灯的最小值。
则:\(dp[i,j,k]=min(dp[i,k,x]+dp[k+1,j,k-x])\)
初始化:\(dp[i,j,1]=min(a[k]*(k-i)-s[k-1]+s[i-1]+s[j]-s[k]-(j-k)*a[k])\)

C++ AC代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;
const int maxn = 200 + 5, maxk = 55; 
int n, k, a[maxn], s[maxn] = {}, dp[maxn][maxn][maxk];
int main()
{
	scanf("%d %d", &n, &k);
	for(int i = 1; i <= n; ++ i) scanf("%d", &a[i]);
	for(int i = 1; i <= n; ++ i) s[i] = s[i - 1] + a[i];
	memset(dp, 0x3f, sizeof(dp));
	for(int i = 1; i <= n; ++ i)
	{
		for(int j = i; j <= n; ++ j)
		{
			for(int p = i; p <= j; ++ p)
			{
				dp[i][j][1] = min(dp[i][j][1], (p * 2 - i - j) * a[p] - s[p - 1] + s[i - 1] + s[j] - s[p]);
			} 
		}
	}
	for(int len = 2; len <= n; ++ len)
	{
		int i = 1, j = len;
		for(; j <= n; ++ i, ++ j)
		{
			for(int l = 2; l <= k; ++ l)
			{
				for(int p = 1; p < l; ++ p)
				{
					for(int x = i; x <= j; ++ x) 
					{
						dp[i][j][l] = min(dp[i][j][l], dp[i][x][p] + dp[x + 1][j][l - p]);
					}
				} 
			}
		}
	}
	printf("%d\n", dp[1][n][k]);
	return 0;
} 

DP(正解)

我们设\(dp[i,j]\)选点亮\(i\)个灯共亮了\(j\)

D. 匹配

题目描述
给定一颗\(n\)个点\(n−1\)条边的树,每条边的长度都是\(1\)\(i\)号节点的权是\(a_i\)。如果三个节点两两在树上的距离都是\(4\),那么称这三个节点构成了一个“组合”,一个“组合”的权是三个节点的权的乘积。求所有“组合”的权的和。

输入格式

\(1\)行一个整数\(n\)

接下来\(1\)\(n\)个正整数,第\(i\)个数表示\(a_i\)

接下来\(n−1\)行,每行\(2\)个正整数\(u,v\),表示\(u\)\(v\)间有一条边。保证输入的是一颗树。

输出格式

\(1\)\(1\)个数表示答案。

样例数据

input

7
1 2 3 4 5 6 7
1 2
2 3
1 4
4 5
1 6
6 7

output

105

这道题实际上是树上的基本应用。
我们很容易知道:一个“组合”成立,当且仅当这三个点\(x,y,z\)距离公共点为\(2\)

DP暴力求解,时间复杂度为\(O(n)\)

C++ AC代码

#include<iostream>
#include<cstring>
#include<cstdio>
#include<vector>
#include<cmath>
using namespace std;
const int SIZE = 100000 + 5;
vector <int> G[SIZE];
int n, a[SIZE];
long long f[SIZE] = {}, fa[SIZE] = {}, ans = 0;
void dfs(int u, int Fa)
{
	fa[u] = Fa;
	for(int i = 0; i < G[u].size(); ++ i)
	{
		int v = G[u][i];		
		if(v == Fa) continue;
		f[u] += a[v]; 
		dfs(v, u);
	}
	return;
}
void solve(int u)
{
	long long p1 = 0, p2 = 0, p3 = 0;
	for(int i = 0; i < G[u].size(); ++ i)
	{
		int v = G[u][i];
		if(v != fa[u]) 
		{
			solve(v); 
			p3 += p2 * f[v];
			p2 += p1 * f[v];
			p1 += f[v];
		}
		else
		{
			long long val = f[v] - a[u] + a[fa[v]];
			p3 += p2 * val;
			p2 += p1 * val;
			p1 += val;
		}
	}
	ans += p3;
	return;
}
int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; ++ i) scanf("%lld", &a[i]);
	for(int i = 1; i < n; ++ i)
	{
		int u, v;
		scanf("%d %d", &u, &v);
		G[u].push_back(v), G[v].push_back(u);
	}
	dfs(1, 0);
	solve(1);
	printf("%lld\n", ans);
	return 0;
}
posted @ 2020-10-02 22:12  大秦帝国  阅读(78)  评论(0编辑  收藏  举报