Codeforces Round #638
比赛链接 https://codeforces.com/contest/1348
比赛记录 https://blog.csdn.net/cheng__yu_/article/details/105395197
A. Phoenix and Balance(水)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;
int t,n;
int main()
{
cin>>t;
while(t--)
{
cin>>n;
ll ans1=0,ans2=0;
for(int i=1;i<=n/2-1;++i)
ans1+=(1<<i);
ans1+=(1<<n);
for(int i=n/2;i<=n-1;++i)
ans2+=(1<<i);
cout<<ans1-ans2<<"\n";
}
return 0;
}
B. Phoenix and Beauty(周期)
题意:如果一个数组所有长度为k的子数组的和相同,就称为是美丽数组。
给你一个长度为n的数组,里面的元素由[1,n]组成。你可以向数组任意位置填充[1,n]的数。请你输出填充完后的美丽数组。否则输出 -1
比赛体验:一开始想的是拿出 给定数组里面前k个元素,然后按照这k个元素填充。那么后面的数,一定要出现在前面的k个数中。
wa了一发后,想明白了,如果前k个数是这样的:4,4,4,4。那么绝对是不可能的。
思路:因此应该想到,这k个数字不变的,且向右一步步递推过去。要把整个数组扩展成美丽数组,那么数组里面的数就必须 被包含在 这k个数字中。
所以正确的思路是,子数组长度为k,最多只有k个不同的数字。如果不足k个数字,可以补充成k个数字。然后拿这k个数字作为一个循环来填充
- 大于k个不同数字,输出 -1
- 小于k个不同数字,补充为k个数字
- 等于k个不同数字,直接把这个循环输出n遍也行,不过我自己是根据这k个数字,对给定数组做补充
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=100+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;
int t,n,k;
int a[maxn];
int main()
{
cin>>t;
while(t--)
{
cin>>n>>k;
set<int> s;
for(int i=1;i<=n;++i)
cin>>a[i],s.insert(a[i]);
if(s.size()>k)
{
puts("-1");
continue;
}
else if(s.size()<k)
{
for(int i=1;i<=n;++i)
{
s.insert(i);
if(s.size()==k)
break;
}
}
vector<int> ans;
for(auto i : s)
ans.push_back(i);
for(int i=2;i<=n;++i)
{
int p=(int)ans.size()-k;
if(a[i]==ans[p])
ans.push_back(a[i]);
else
ans.push_back(ans[p]),--i;
}
cout<<(int)ans.size()<<"\n";
for(auto i : ans)
cout<<i<<" ";
cout<<"\n";
}
return 0;
}
C. Phoenix and Distribution(分类讨论)
题意:给定一个长度为n的字符串,分成k个分空的子集,使得字典序最大的子集最小。输出这个最大的子集
思路:
- 判断前k个是不是相等,如果相等,在判断后面的所有是不是相等。相等才能均分,不相等全加到a的后面就好了。
- 如果不相等,输出s[k],即第k大的字母
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;
int t,n,k;
string s;
int main()
{
cin>>t;
while(t--)
{
cin>>n>>k>>s;
sort(s.begin(),s.end());
s="0"+s;
string ans="";
if(s[1]==s[k])
{
ans=s[1];
if(s[k+1]==s[n])
{
int cnt=n/k+(n%k?1:0)-1;
for(int i=1;i<=cnt;++i)
ans+=s[k+1];
}
else
{
for(int i=k+1;i<=n;++i)
ans+=s[i];
}
}
else
ans=s[k];
cout<<ans<<"\n";
}
return 0;
}
D. Phoenix and Science(构造)
题意:原题意就不分析了。翻译过来的题意是:
a
1
+
a
2
+
a
i
+
⋯
+
a
k
=
n
a_1+a_2+a_i+\dots+a_k=n
a1+a2+ai+⋯+ak=n
其中,
a
i
−
1
<
=
a
i
<
=
2
a
i
−
1
a_{i-1}<=a_i<=2a_{i-1}
ai−1<=ai<=2ai−1,使得
k
k
k 值最小,最后输出
a
a
a 的差分数组。
思路:
- 最强的构造方法:直接构造 1+2+4+8+16+x=n。最后将这些数排个序,然后输出差分数组
- 第二种构造方法:按常规思维递增构造,维护一个ans数组,每次加入数组的数是 m i n ( n / 2 , a n s . b a c k ( ) ∗ 2 ) min(n/2,ans.back()*2) min(n/2,ans.back()∗2),这样贪心可以构造一个递增的数列
1、直接构造 1+2+4+8+16+x=n。最后将这些数排个序,然后输出差分数组
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;
int t,n;
int main()
{
cin>>t;
while(t--)
{
cin>>n;
vector<int> ans;
for(int i=1;i<=n;i*=2)
{
ans.push_back(i);
n-=i;
}
if(n>0)
{
ans.push_back(n);
sort(ans.begin(),ans.end());
}
int n=(int)ans.size()-1;
cout<<n<<"\n";
for(int i=1;i<=n;++i)
cout<<ans[i]-ans[i-1]<<" ";
cout<<"\n";
}
return 0;
}
2、每次加入数组的数是 m i n ( n / 2 , a n s . b a c k ( ) ∗ 2 ) min(n/2,ans.back()∗2) min(n/2,ans.back()∗2)
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;
int t,n;
int main()
{
cin>>t;
while(t--)
{
cin>>n;
vector<int> ans;
ans.push_back(1);
n--;
while(1)
{
if(2*ans.back()>=n)
{
ans.push_back(n);
break;
}
ans.push_back(min(n/2,ans.back()*2));
n-=ans.back();
}
int n=(int)ans.size()-1;
cout<<n<<"\n";
for(int i=1;i<=n;++i)
cout<<ans[i]-ans[i-1]<<" ";
cout<<"\n";
}
return 0;
}
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=1e5+5,maxm=1e5+5;
const int mod=1e9+7,inf=0x7f7f7f7f;
int t,n;
int main()
{
cin>>t;
while(t--)
{
cin>>n;
vector<int> ans;
ans.push_back(1);
int cur=1;
n--;
while(cur*4<=n)
{
cur*=2;
n-=cur;
ans.push_back(cur);
}
if(cur*2>=n)
ans.push_back(n);
else
ans.push_back(n/2),ans.push_back(n-n/2);
int cnt=ans.size()-1;
cout<<cnt<<"\n";
for(int i=1;i<=cnt;++i)
cout<<ans[i]-ans[i-1]<<" ";
cout<<"\n";
}
return 0;
}
E. Phoenix and Berries(DP)
题意:有 n 棵果树,每棵树上有
a
i
a_i
ai 个红莓和
b
i
b_i
bi 个蓝莓。有容量为 k 的篮子,不同树同颜色的可以放一起,同一棵树的红莓和蓝莓可以放一起。问最多能装满几个篮子
(
1
≤
n
,
k
≤
500
,
0
≤
a
i
,
b
i
≤
1
0
9
)
(1\le n,k \le 500 ,0 \le a_i,b_i\le 10^9)
(1≤n,k≤500,0≤ai,bi≤109)
思路:
状态表示:
d
p
[
i
]
[
j
]
dp[i][j]
dp[i][j]表示前
i
i
i 颗树剩下
j
j
j 个红莓的方案的集合
属性:凑成最多的篮子数
状态转移:因为是模
k
k
k 意义下的,所以从
d
p
[
i
−
1
]
[
j
]
dp[i-1][j]
dp[i−1][j] 来转移会更方便。
- 1、取不同的树相同颜色: d p [ i ] [ ( j + a i ) % k ] = d p [ i − 1 ] [ j ] + ( j + a i ) / k + ( s u m [ i − 1 ] − k × d p [ i − 1 ] [ j ] − j + b i ) / k dp[ i] [(j+a_i)\%k]=dp[i-1][j]+(j+a_i)/k+(sum[i-1]-k\times dp[i-1][j]-j+b_i)/k dp[i][(j+ai)%k]=dp[i−1][j]+(j+ai)/k+(sum[i−1]−k×dp[i−1][j]−j+bi)/k
- 2、取同一棵树不同颜色:
d
p
[
i
]
[
(
j
+
a
i
−
s
)
%
k
]
=
d
p
[
i
−
1
]
[
j
]
+
(
j
+
a
i
−
s
)
/
k
+
(
s
u
m
[
i
−
1
]
−
k
×
d
p
[
i
−
1
]
[
j
]
−
j
+
b
i
−
(
k
−
s
)
)
/
k
+
1
dp[i][(j+a_i-s)\%k]=dp[i-1][j]+(j+a_i-s)/k+(sum[i-1]-k\times dp[i-1][j]-j+b_i-(k-s))/k+1
dp[i][(j+ai−s)%k]=dp[i−1][j]+(j+ai−s)/k+(sum[i−1]−k×dp[i−1][j]−j+bi−(k−s))/k+1,一棵树上只需要取一组组成一篮就可以,其他的可以同色组,因为一颗树上取 k 的倍数的红莓和蓝莓,最后模 k 之后,相当于只有一组是 红莓和蓝莓相结合的
枚举取 s s s 个红莓,至少可以取 m a x ( 1 , k − b [ i ] ) max(1,k-b[i]) max(1,k−b[i]) ,至多可以取 m i n ( k − 1 , a [ i ] ) min(k-1,a[i]) min(k−1,a[i])
#include <bits/stdc++.h>
#define ll long long
using namespace std;
const int maxn=500+5,maxm=1e5+5;
const int mod=998244353,inf=0x7f7f7f7f;
int n,k;
ll dp[maxn][maxn];
int a[maxn],b[maxn];
ll sum[maxn];
int main()
{
memset(dp,-1,sizeof(dp));
scanf("%d%d",&n,&k);
for(int i=1;i<=n;++i)
{
scanf("%d%d",&a[i],&b[i]);
sum[i]=sum[i-1]+a[i]+b[i];
}
dp[0][0]=0;
for(int i=1;i<=n;++i)
{
for(int j=0;j<k;++j)
{
if(dp[i-1][j]==-1)
continue;
int l=max(1,k-b[i]),r=min(k-1,a[i]);
for(int s=l;s<=r;++s)
{
int x=j+a[i]-s;
int y=sum[i-1]-k*dp[i-1][j]-j+b[i]-k+s;
dp[i][x%k]=max(dp[i][x%k],dp[i-1][j]+1+x/k+y/k);
}
int x=j+a[i];
int y=sum[i-1]-k*dp[i-1][j]-j+b[i];
dp[i][x%k]=max(dp[i][x%k],dp[i-1][j]+x/k+y/k);
}
}
ll ans=0;
for(int j=0;j<k;++j)
ans=max(ans,dp[n][j]);
printf("%lld\n",ans);
return 0;
}
F. Phoenix and Memory(贪心 + 线段树)
链接https://codeforces.com/contest/1348/problem/F
题意:给定每个人的区间,还原出每个人的原来的位置(排列)。如果答案唯一,输出YES,打印一组排列。否则输出NO,并打印2组排列
思路:先按右区间从小到大排列,贪心还原出一个排列。然后判断是否可以有其他排列。
- 对于第 i 个人和第 j 个人,如果存在 l j ≤ p i < p j ≤ r i l_j\le p_i<p_j\le r_i lj≤pi<pj≤ri,那么这两个人是可交换的
- 只需要在 [ p i + 1 , r i ] [ p_i+1,r_i ] [pi+1,ri]范围内找到一个最小的 l j l_j lj,判断 p i p_i pi 和 l j l_j lj 的大小即可
- 因此需要用线段树维护 l j l_j lj 和 j j j 两个值。按贪心出来的排列更新,维护 l j l_j lj 的最小值
#include <bits/stdc++.h>
#define ll long long
#define pii pair<int,int>
#define ls (rt<<1)
#define rs ((rt<<1)|1)
using namespace std;
const int maxn=2e5+5,maxm=1e5+5;
const int mod=998244353,inf=0x7f7f7f7f;
int n;
struct People
{
int l,r,id;
bool operator<(const People & b) const
{
return r<b.r;
}
}p[maxn];
pair<int,int> ST[maxn<<2];
pair<int,int> pp[maxn];
set<int> s;
int pos[maxn];
void print()
{
for(int i=1;i<=n;++i)
cout<<pos[i]<<" ";
puts("");
}
void Push_up(int rt)
{
ST[rt]=min(ST[ls],ST[rs]);
}
void Build(int rt,int L,int R)
{
if(L==R)
{
ST[rt]=pp[L];
return;
}
int mid=(L+R)>>1;
Build(ls,L,mid);
Build(rs,mid+1,R);
Push_up(rt);
}
pii Query(int rt,int l,int r,int L,int R)
{
if(l<=L&&R<=r)
return ST[rt];
int mid=(L+R)>>1;
pii ans1={inf,0},ans2={inf,0};
if(l<=mid)
ans1=Query(ls,l,r,L,mid);
if(r>mid)
ans2=Query(rs,l,r,mid+1,R);
return min(ans1,ans2);
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;++i)
{
scanf("%d%d",&p[i].l,&p[i].r);
p[i].id=i;
s.insert(i);
}
sort(p+1,p+1+n);
for(int i=1;i<=n;++i)
{
auto it=s.lower_bound(p[i].l);
pos[p[i].id]=*it;
pp[*it]={p[i].l,p[i].id};
s.erase(it);
}
Build(1,1,n);
for(int i=1;i<=n;++i)
{
int pi=pos[p[i].id],Ri=p[i].r;
if(pi+1>Ri)
continue;
auto ans=Query(1,pi+1,Ri,1,n);
if(ans.first<=pi)
{
puts("NO");
print();
swap(pos[p[i].id],pos[ans.second]);
print();
return 0;
}
}
puts("YES");
print();
return 0;
}