小路灯
题目描述
一条平直的公路上有 (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
样例解释
点亮第 (2,5,6) 个路灯,每个路灯与距离最近的被点亮的路灯的距离分为是 (1,0,6,1,0,0),最大值是 (6)。可以证明没有比 (6) 更小的方案。
数据规模与约定
对于 (30\%) 的数据,(n,k≤100)。
对于 (100\%)% 的数据,(1≤n≤100000),(1≤k≤n),(|a_i|≤10^9)。
时间限制:(1s)
空间限制:(512MB)
Solution
30pts
(n,k<=100)
考虑用 (dp) 来做:(f[i][j]) 表示前 (i) 个小路灯点亮了 (j) 个,且第 (j) 个是点亮的,之前每个小路与距离最近的被点亮的小路灯的距离的最大值最小是多少。
(f[i][j]=min(max(f[r][j-1],g(r,i))),0<=r<i),(g(r,i)) 表示第 (r,i) 个路灯是被点亮的,([r,i]) 内的路灯到最近的被点亮的小路灯的距离的最大值。
时间复杂度 (O(n^2*k))。
100pts
题目要求最大值最小,这一类问题我们通常用二分解决。
我们二分答案 (mid) 表示这个最大值是多少,也就是说,现在每个路灯所能照到的范围就是 ([x-mid,x+mid])(因为区间外的路灯会使答案增大),我们只需要判断是否能照亮小于等于 (k) 的路灯使得区间的整体被覆盖。
贪心思路:如果我们从左往右考虑每个路灯被哪个路灯照亮,那么一定是所能照到的最靠右的那个路灯。
所以我们可以对于每个路灯,先判断它已经是否被照亮了,如果没被照亮,我们就在右侧找能照到这个路灯的最靠右路灯。
时间复杂度 (O(nlog{n}))。
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define ll long long
using namespace std;
int read()
{
char ch=getchar();
int a=0,x=1;
while(ch<'0'||ch>'9')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
a=(a<<1)+(a<<3)+(ch^48);
ch=getchar();
}
return a*x;
}
const int N=1e6+5;
int n,k,l,r;
int a[N];
bool check(int x)
{
int cnt=0,last=0; //cnt表示所需要的路灯数,last表示上一个被照亮的路灯是哪个
for(int i=1;i<=n;i++)
{
if(a[i]-a[last]<=x) continue; //已经被照亮了就跳过不考虑
for(int j=i+1;j<=n+1;j++) //否则在右侧找一个能照到它的最靠右的一个
{
if(a[j]-a[i]>x) //第j个脱离了照亮范围,说明第j-1个是最靠右的
{
cnt++;
last=j-1;
break;
}
}
}
return cnt<=k; //所照亮的路灯数小于等于k个就是合法的
}
int main()
{
n=read();k=read();
for(int i=1;i<=n;i++) a[i]=read();
a[0]=-2e9;a[n+1]=2e9; //注意坐标有负,所以a[0]要设为-INF
l=0;r=a[n]-a[1]+1;
while(l<r) //二分路灯的照亮半径
{
int mid=(l+r)>>1;
if(check(mid)) r=mid;
else l=mid+1;
}
printf("%d
",l);
return 0;
}
序列
题目描述
设数列 (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
样例解释
(P:[1,2,3,4]→[2,1,4,3]→[1,4,2,3]→[4,2,3,1])。
数据规模与约定
对于 (40\%) 的数据,(1≤n≤1000)。
对于 (60\%) 的数据,(1≤n≤100000)。
对于 (100\%)% 的数据,(1≤n≤1000000)。
时间限制:(2s)
空间限制:(512MB)
Solution
40pts
(n<=1000)
暴力模拟即可 时间复杂度 (O(n^2))。
60pts
(n<=100000)
对序列的插入和删除一共有 (frac{n}{2}+frac{n}{3}+……+frac{n}{n}=n*ln{n})
用线段树或平衡树维护。
时间复杂度 (O(n*log^2{n}))。
100pts
(n<=1000000)
如果把不动的位置固定,动的位置相当于依次向后移动一位。
每次 (f) 作用只会使结尾位置 (++)。
什么意思呢?考虑到每次将若干个长度为 (k) 的段的第一个数插在最后一个数后面,其实在原序列的位置关系就是这一段的第一个数挪到了下一段为位置上去,下一段的第一个数挪到了下下一段为位置上去,...,而最后一段的第一个数挪到了序列长度 (+1) 的位置上去。
用一个 (2) 倍长度的数组就能维护。
时间复杂度 (O(n*ln{n}))。
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<ctime>
#include<cmath>
#define ll long long
using namespace std;
const int N=2e6+5;
int n;
int a[N];
int main()
{
cin>>n;
for(int i=1;i<=n;i++) a[i]=i;
for(int i=2;i<=n;i++)
{
int now=0;
for(int j=i-1;j<=i+n-1;j+=i)
{
swap(now,a[j]);
}
if(!a[i+n-1]) a[i+n-1]=now;
}
for(int i=n;i<2*n;i++) printf("%d ",a[i]);
printf("
");
return 0;
}
路灯
题目描述
一条平直的公路上有 (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
样例解释
点亮第 (2,4,6) 个路灯,每个路灯与距离最近的被点亮的路灯的距离和是 (8) 。可以证明没有比 (8) 更小的方案。
数据规模与约定
对于 (30\%) 的数据,(k=2)。
对于 (50\%) 的数据,(n≤10)。
对于 (100\%) 的数据,(1≤n≤200),(1≤k≤30),(k≤n),(|a_i|≤50000)。
时间限制:(1s)
空间限制:(512MB)
Solution
50pts
(n<=7)
(dfs) 搜索被点亮的路灯然后取 (min)。
时间复杂度 (O(n*2^n))。
100pts
(n<=200)
如果你能得且仅得小路灯的 (30) 分,那么你就能得到这道题的满分。
还是考虑用 (dp) 做:(f[i][j]) 表示前 (i) 个小路灯点亮了 (k) 个,且第 (k) 个是点亮的,之前每个小路与距离最近的被点亮的小路灯的距离的和最小是多少。
(f[i][j]=min(f[r][j-1]+h(r,i))),0<=r<i),(h(r,i)) 表示第 (r,i) 个路灯是被点亮的,([r,i]) 内的路灯到最近的被点亮的小路灯的距离之和。
时间复杂度 (O(n^2*k))。
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<ctime>
#include<cmath>
#define ll long long
using namespace std;
ll read()
{
char ch=getchar();
ll a=0,x=1;
while(ch<'0'||ch>'9')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
a=(a<<1)+(a<<3)+(ch^48);
ch=getchar();
}
return a*x;
}
const int N=1000;
int n,m;
int a[N],f[N][N],h[N][N];
int main()
{
n=read();m=read();
for(int i=1;i<=n;i++) a[i]=read();
a[n+1]=2e9;a[0]=-2e9;
for(int i=0;i<=n;i++)
for(int j=i;j<=n+1;j++)
for(int k=i;k<=j;k++)
h[i][j]+=min(a[k]-a[i],a[j]-a[k]); //预处理h数组
memset(f,0x3f,sizeof(f));
f[0][0]=0;
for(int i=1;i<=n;i++)
for(int k=1;k<=m;k++)
for(int j=0;j<i;j++)
f[i][k]=min(f[i][k],f[j][k-1]+h[j][i]);
int ans=2e9;
for(int i=1;i<=n;i++)
ans=min(ans,f[i][m]+h[i][n+1]); //因为最后一个路灯不一定被点亮,所以我们要枚举最后一个被点亮的灯的位置
//由于第n+1个路灯的坐标设为了INF,所以这里的h[i][n+1]相当于i之后的路灯到i的距离和
printf("%d
",ans);
return 0;
}
匹配
题目描述
给定一颗 (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
样例解释
只有 ((3,5,7)) 构成了“组合”,这个“组合”的权是 (105)。
数据规模与约定
对于 (20\%) 的数据,(1≤n≤100)。
对于 (40\%) 的数据,(1≤n≤2000)。
对于 (100\%) 的数据,(1≤n≤100000),(1≤a_i≤10),(1≤u,v≤n)。
时间限制:(1s)
空间限制:(512MB)
Solution
20pts
(n<=100)
暴力求出两两之间距离 然后 (n^3) 枚举。
时间复杂度 (O(n^3))。
40pts
(n<=2000)
暴力求出两两之间距离 枚举三个点的中间点,然后找到所有与中间点距离为 (2) 的点。
(O()点数(^2)) 求出其中两两距离为 (4) 的“匹配”。
时间复杂度 (O(n^2))。
100pts
(n<=100000)
(A_i<=10) 是为了不超 (longlong)。
可以发现三个点是“组合”当且仅当与中间点距离为 (2) 且互相在中间点的不同子树中。
一次树形 (dp) 求出每个点子节点的权的和 (f), 对于一个中间点,它的贡献就是每个子节点(包括父亲)的 (f) 值集合中任取 (3) 个的乘积的和。
求法有很多,具体见代码。
时间复杂度 (O(n))。
#include<algorithm>
#include<iostream>
#include<cstdlib>
#include<cstring>
#include<cstdio>
#include<ctime>
#include<cmath>
#define ll long long
using namespace std;
int read()
{
char ch=getchar();
int a=0,x=1;
while(ch<'0'||ch>'9')
{
if(ch=='-') x=-x;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
a=(a<<1)+(a<<3)+(ch^48);
ch=getchar();
}
return a*x;
}
const int N=2e5+5;
int n,Edge;
int val[N],head[N],Fa[N],size[N],dp[N];
ll ans;
struct node
{
int from,to,nxt;
}a[N];
void add(int from,int to)
{
Edge++;
a[Edge].from=from;
a[Edge].to=to;
a[Edge].nxt=head[from];
head[from]=Edge;
}
void dfs(int u,int fa)
{
Fa[u]=fa;
for(int i=head[u];i;i=a[i].nxt)
{
int v=a[i].to;
if(v==fa) continue;
dfs(v,u);
size[u]++; //u有几个子树
dp[u]+=val[v];
}
}
int main()
{
n=read();
for(int i=1;i<=n;i++) val[i]=read();
for(int i=1;i<n;i++)
{
int u=read();
int v=read();
add(u,v);add(v,u);
}
dfs(1,0);
for(int u=1;u<=n;u++)
{
ll s1=val[Fa[Fa[u]]]; //s1维护当前离u距离为2的数的val之和
ll s2=0; //s2维护从这些数中任选两个的乘积之和
if(u>1) s1+=dp[Fa[u]]-val[u];
for(int i=head[u];i;i=a[i].nxt)
{
int v=a[i].to;
if(v==Fa[u]) continue;
ans+=s2*dp[v]; //v的子树中与u距离为2的点与前面s2中已选的两个点凑成三个点的组合
s2+=s1*dp[v]; //更新s2,多出的情况就是:从前面选一个,再从dp[v]中选一个
s1+=dp[v]; //更新s1
}
}
printf("%lld
",ans);
return 0;
}