Codeforces Round #636 Div. 3
比赛链接 https://codeforces.com/contest/1343
比赛记录 https://blog.csdn.net/cheng__yu_/article/details/105395197
A. Candies(水)
题意:给定一个
n
n
n,找一个
x
x
x满足
x
(
1
+
2
+
4
+
⋯
+
2
k
−
1
)
=
n
(
k
>
1
)
x(1+2+4+\dots +2^{k-1})=n (k>1)
x(1+2+4+⋯+2k−1)=n(k>1)
解题思路:
(
1
+
2
+
4
+
⋯
+
2
k
−
1
)
(1+2+4+\dots +2^{k-1})
(1+2+4+⋯+2k−1),这个式子可以化简为
2
k
−
1
2^{k}-1
2k−1
k从2开始枚举,看
2
k
−
1
2^{k}-1
2k−1 能不能被
n
n
n 整除。
总结:
- ( 1 + 2 + 4 + ⋯ + 2 k − 1 ) = 2 k − 1 (1+2+4+\dots +2^{k-1})=2^{k}-1 (1+2+4+⋯+2k−1)=2k−1
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,mod=1e9+7;
const int inf=0x3f3f3f3f;
int t;
ll n;
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%lld",&n);
int cnt=1;
ll ans=0;
while(1)
{
cnt++;
ll tmp=(1<<cnt)-1;
if(n%tmp==0)
{
ans=n/tmp;
break;
}
}
printf("%lld\n",ans);
}
return 0;
}
B. Balanced Array(超级水)
题意:给定一个偶数n,构造一个长度为n的数组,左右边长度同为
n
2
\frac n2
2n
左边全为偶数,右边全为奇数。左右两边的和相等
解题思路:
左边从2、4、6、8开始
右边从1,、3、5、7开始,最后一个元素在加上
n
2
\frac n2
2n就好了
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5,mod=1e9+7;
const int inf=0x3f3f3f3f;
int t,n;
int a[maxn];
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
if(n%4!=0||(n&1))
{
puts("NO");
continue;
}
int now=2;
for(int i=1;i<=n/2;++i)
a[i]=now,now+=2;
now=1;
for(int i=n/2+1;i<=n;++i)
a[i]=now,now+=2;
a[n]+=n/2;
puts("YES");
for(int i=1;i<=n;++i)
printf("%d%c",a[i],i==n?'\n':' ');
}
return 0;
}
C. Alternating Subsequence(实现)
题意:给定一个正负交错的序列,求一个最长的一正一负交错的子序列的最大和。
解题思路:
原序列根据正负分成很多子段。每个子段取一个最大的数累加。
实现:用last标记一段的开始,遇到 异号 就统计一下这一段的最大值
异号判断:
a
[
i
]
∗
a
[
j
]
<
0
a[i]*a[j]<0
a[i]∗a[j]<0
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;
int t,n,a[maxn];
int main()
{
cin>>t;
while(t--)
{
cin>>n;
for(int i=1;i<=n;++i)
cin>>a[i];
int last=1;
a[n+1]=0;
ll ans=0;
for(int i=2;i<=n+1;++i)
{
if(1ll*a[i]*a[i-1]<=0)
ans+=*max_element(a+last,a+i),last=i;
}
cout<<ans<<"\n";
}
return 0;
}
D. Constant Palindrome Sum(差分数组)
题意:给定一个n个元素的数组a,每个元素不大于k。现在可以将a[i]替换成[1,k],问最少需要替换多少次才能使得
a
i
+
a
n
−
i
+
1
=
x
a_i+a_{n-i+1}=x
ai+an−i+1=x 对
i
i
i 属于
[
1
,
n
2
]
[1,\frac n2]
[1,2n]都成立
解题思路:
用差分数组来做。x是
[
2
,
2
k
]
[2,2k]
[2,2k] 上的某个值。对于一组a、b来说,改变它们的值,使它们的和在[2,2k]上是有规律的(设a<b)。
区间 | 改变的次数 |
---|---|
[ 2 , a ] [2,a] [2,a] | 2 |
[ a + 1 , a + b − 1 ] [a+1,a+b-1] [a+1,a+b−1] | 1 |
[ a + b ] [a+b] [a+b] | 0 |
[ a + b + 1 , b + k ] [a+b+1,b+k] [a+b+1,b+k] | 1 |
[ b + k + 1 , 2 k ] [b+k+1,2k] [b+k+1,2k] | 2 |
- 把这n/2对元素,对 [ 2 , 2 k ] [2,2k] [2,2k] 区间的更新操作累积在一个数组上,作前缀和后,数组上最小的数,就是我们所需的最少操作次数
- 简单的说,在数组上做区间更新,相当于在差分数组上做标记。最后累积前缀和统计答案。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5,mod=1e9+7;
const int inf=0x3f3f3f3f;
int t,n,k;
int a[maxn],d[maxn<<1];
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d %d",&n,&k);
for(int i=1;i<=n;++i)
scanf("%d",&a[i]);
memset(d,0,sizeof(d));
for(int i=1;i<=n/2;++i)
{
int x=min(a[i],a[n-i+1]);
int y=max(a[i],a[n-i+1]);
d[2]+=2;
d[x+1]-=2;
d[x+1]++;
d[x+y]--;
d[x+y+1]++;
d[y+k+1]--;
d[y+k+1]+=2;
d[2*k+1]-=2;
}
int cur=0,ans=1e9;
for(int i=2;i<=2*k;++i)
cur+=d[i],ans=min(ans,cur);
printf("%d\n",ans);
}
return 0;
}
E. Weights Distributing(BFS)
题意:给定一颗树、三个点a、b、c,还有一个边权数组。现有人从a 走到b,再从b走到c。让你分配边权,问怎么分配可以使得走过的路径最短,输出这个最短的路径。
思路:从 a -> b 在从 b -> c,可能会有一段公共的路径。找到这个点 x,计算出距离。给它们分配前最小的边权。
实现:分别以a、b、c作为源点搜出深度。枚举每个点作为 x 点计算距离,取最小值
总结
- 其实,能够画出来的图也不多吧。一种是没有公共路径,直接从 a -> b,再从 b -> c。另外一种就是 a->x 、x->b、b->x、x->c。
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=2e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;
int t,n,m,a,b,c;
int head[maxn],cnt;
ll p[maxn];
int depth1[maxn],depth2[maxn],depth3[maxn];
struct Edge
{
int nxt,to;
}edges[maxn<<1];
void add(int u,int v)
{
edges[++cnt].to=v;
edges[cnt].nxt=head[u];
head[u]=cnt;
}
void bfs(int s,int depth[])
{
depth[s]=0;
queue<int> q;
q.push(s);
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];i!=-1;i=edges[i].nxt)
{
int v=edges[i].to;
if(depth[v]==inf)
{
depth[v]=depth[u]+1;
q.push(v);
}
}
}
}
int main()
{
scanf("%d",&t);
while(t--)
{
cnt=-1;
scanf("%d%d%d%d%d",&n,&m,&a,&b,&c);
for(int i=1;i<=n;++i)
depth1[i]=depth2[i]=depth3[i]=inf,head[i]=-1;
for(int i=1;i<=m;++i)
scanf("%lld",&p[i]);
sort(p+1,p+1+m);
for(int i=1;i<=m;++i)
p[i]+=p[i-1];
for(int i=1;i<=m;++i)
{
int u,v;
scanf("%d%d",&u,&v);
add(u,v);
add(v,u);
}
bfs(a,depth1);
bfs(b,depth2);
bfs(c,depth3);
ll ans=9e18;
for(int i=1;i<=n;++i)
{
int d1=depth1[i],d2=depth2[i],d3=depth3[i];
//cout<<"d1 "<<d1<<" d2 "<<d2<<" d3 "<<d3<<"\n";
if(d1+d2+d3>m)
continue;
ans=min(ans,p[d1+d2+d3]+p[d2]);
}
printf("%lld\n",ans);
}
return 0;
}
F. Restore the Permutation by Sorted Segments
题意:从
[
2
,
n
]
[2,n]
[2,n] 开始给定排列 n 的前缀的后缀,后缀长度至少为 2 。让你还原出原来的排列
思路:可以直接枚举起点,后续状态都是可以确定的。每次确定了一个数的时候,需要将这个数从所有拥有这个数的集合中删除,并将这些集合标记为访问,因为接下来找到的数也必须在这些已访问且非空的集合中出现,这样才能保证它们是连续的
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=200+10,mod=1e9+7;
int tt,n,k,x;
set<int> s[maxn],t[maxn];
int visit[maxn],ans[maxn];
bool check(int a)
{
memset(visit,0,sizeof(visit));
for(int i=1;i<=n-1;++i)
{
if(s[i].count(a))
{
s[i].erase(a);
visit[i]=1;
}
}
ans[1]=a;
for(int i=2;i<=n;++i)
{
bool f=0;
for(int j=1;j<=n-1;++j)
{
if(s[j].size()==1&&visit[j])
{
f=1;
ans[i]=*s[j].begin();
break;
}
}
if(!f) return false;
for(int j=1;j<=n-1;++j)
{
if(s[j].empty()) continue;
if(visit[j])
{
if(!s[j].count(ans[i]))
return false;
s[j].erase(ans[i]);
}
else if(s[j].count(ans[i]))
{
s[j].erase(ans[i]);
visit[j]=1;
}
}
}
return true;
}
int main()
{
cin>>tt;
while(tt--)
{
cin>>n;
for(int i=1;i<=n-1;++i) t[i].clear();
for(int i=1;i<=n-1;++i)
{
cin>>k;
for(int j=1;j<=k;++j)
{
cin>>x;
t[i].insert(x);
}
}
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n-1;++j)
s[j]=t[j];
if(check(i))
{
for(int j=1;j<=n;++j)
printf("%d%c",ans[j],j==n?'\n':' ');
break;
}
}
}
return 0;
}