C. Minimize the Thickness
样例输入:
4
6
55 45 30 30 40 100
4
10 23 7 13
5
10 55 35 30 65
6
4 1 1 1 1 4
样例输出:
3
4
2
3
题意:给定一个长度为n的数组,我们可以将其分为若干个互不相交的区间,每个区间内所有元素的和必须要相同,问在这样的划分条件下的区间的最大长度的最小值是多少?
分析:我们直接暴力枚举第一段区间的长度,然后我们只要保证后面的每一段区间的元素和都跟当前段的元素和相同即可,这个可以用前缀和优化一下,复杂度就是O(nlogn)。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=2e5+10;
int a[N],n;
long long s[N];
int find(long long x)
{
int l=1,r=n;
while(l<r)
{
int mid=l+r>>1;
if(s[mid]>=x) r=mid;
else l=mid+1;
}
if(s[l]!=x) return -1;
else return l;
}
int main()
{
int T;
cin>>T;
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),s[i]=s[i-1]+a[i];
int ans=n;
for(int i=1;i<=n;i++)//枚举段长
{
long long t=0;
int l=1;
int tans=0;
while(l<=n)
{
t+=s[i];
int r=find(t);
if(r==-1)
{
tans=n;
break;
}
tans=max(tans,r-l+1);
l=r+1;
}
ans=min(ans,tans);
}
printf("%d\n",ans);
}
return 0;
}
D. Masha and a Beautiful Tree
样例输入:
4
8
6 5 7 8 4 3 1 2
4
3 1 4 2
1
1
8
7 8 4 3 1 2 6 5
样例输出:
4
-1
0
-1
题意:一开始给定一棵有n个叶子节点的完全二叉树,叶子节点的取值就是1~n的一个排列,我们每次可以选择交换两个兄弟节点,问至少需要交换多少次才能使得叶子节点的排列为1~n。如果不能直接输出-1.
分析:首先我们要保证互为兄弟节点的两个叶子节点的值不能相差大于1,而且要保证奇数小于偶数。对于不满足这种情况的叶子节点我们直接输出-1即可。对于已经满足题意的树我们可以取一个子节点的值将其值的一半赋给其父亲节点,然后重复上面操作直至只剩一个节点即可,过程中统计答案。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=2e5+10;
int main()
{
int T;
cin>>T;
while(T--)
{
vector<int>p[41];
int n;
scanf("%d",&n);
int t;
for(int i=1;i<=n;i++)
scanf("%d",&t),p[1].push_back(t);
int d=1;
long long ans=0;
bool flag=true;
int tn=n;
while(tn!=1)
{
for(int i=0;i<p[d].size();i+=2)
{
if((p[d][i]-1)/2!=(p[d][i+1]-1)/2)
{
flag=false;
break;
}
else if(p[d][i]>p[d][i+1]) ans++;
p[d+1].push_back((p[d][i]-1)/2+1);
}
d++;tn/=2;
if(!flag) break;
}
if(!flag) puts("-1");
else printf("%lld\n",ans);
}
return 0;
}
E. Sending a Sequence Over the Network
样例输入:
7
9
1 1 2 3 1 3 2 2 3
5
12 1 2 7 5
6
5 7 8 9 10 3
4
4 8 6 2
2
3 1
10
4 6 2 1 9 4 9 3 4 2
1
1
样例输出:
YES
YES
YES
NO
YES
YES
NO
题意:给定若干段区间,每段区间有若干个数,我们可以把区间的长度作为一个数值放在这个区间的左边或者右边合成一个块,最后把所有的块按顺序合在一起就组成了一个序列,现在给定一个序列,问是否可以经过上述方法得到。
分析:设f[i][0]=true/false代表以i作为区间长度向左扩展可行/不可行,f[i][1]=true/false代表以i作为终点可行/不可行。
接下来就是动态转移方程的推导:
对于第i个字符,我们先假设其作为区间长度且向左扩展,前提条件是i左边至少要有a[i]个数,而且要保证前i-1-a[i]个数是合法的,才能将这段区间作为一个合法区间出现,如果满足上述条件,那么f[i][1]也应该被置为true。下面枚举第i个位置作为区间长度向右进行扩展,那么前提条件是第i个位置后至少要有a[i]个元素,而且区间[1,i-1]必须是合法区间,这个时候我们就知道区间[1,i+a[i]]是合法的了。最后我们只需要判断f[n][1]是否为true就知道是否合法了。
细节见代码:
#include<iostream>
#include<algorithm>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<vector>
#include<queue>
using namespace std;
const int N=2e5+10;
bool f[N][2];
int a[N];
//f[i][0]=true/false代表以i作为区间长度向左扩展可行/不可行
//f[i][1]=true/false代表以i作为终点可行/不可行
int main()
{
int T;
cin>>T;
while(T--)
{
int n;
scanf("%d",&n);
f[0][1]=true;
for(int i=1;i<=n;i++)
scanf("%d",&a[i]),f[i][0]=f[i][1]=false;
for(int i=1;i<=n;i++)
{
if(i>a[i])
if(f[i-1-a[i]][1]) f[i][0]=f[i][1]=true;
if(f[i-1][1]&&(i+a[i]<=n)) f[i+a[i]][1]=true;
}
if(f[n][1]) puts("YES");
else puts("NO");
}
return 0;
}