CodeForces 1324 - Codeforces Round #627 (Div. 3)
第一次遇到这么水的div. 3。。。
本来想抢D一血的,结果失败了。。然后干脆就\(45\mathrm{min}\)AK了。。。
CodeForces比赛页面传送门
A - Yet Another Tetris Problem
洛谷题目页面传送门 & CodeForces题目页面传送门
给出\(n\)个数,第\(i\)个数为\(a_i\)。每次操作可以选择一个\(i\in[1,n]\)并令\(a_i=a_i+2\),或令\(\forall i\in[1,n],a_i=a_i-1\)。问能否经过若干次操作,将\(a\)数组清零。本题多测。
结论显然,能将\(a\)数组清零当且仅当所有数奇偶性相同。
证明:若所有数奇偶性相同,显然可以通过若干次第\(1\)种操作把它们变成两两相等,再通过若干次第\(2\)种操作把它们都变成\(0\);若存在\(2\)个数\(a_i,a_j\)奇偶性不相同,则若对\(a_i,a_j\)做第\(1\)种操作,那么它们奇偶性不变,若做第\(2\)种操作,它们奇偶性互换,所以它们奇偶性永远不可能相同,自然也无法都变成\(0\)。得证。
下面是AC代码:
#include<bits/stdc++.h>
using namespace std;
void mian(){
int n;
cin>>n;
int x;
cin>>x;
x&=1;//a[1]的奇偶性
n--;
bool ok=true;
while(n--){
int y;
cin>>y;
ok&=x==(y&1);//奇偶性要全部相同
}
puts(ok?"YES":"NO");
}
int main(){
int testnum;
cin>>testnum;
while(testnum--)mian();
return 0;
}
B - Yet Another Palindrome Problem
洛谷题目页面传送门 & CodeForces题目页面传送门
给出\(n\)个数,第\(i\)个数为\(a_i\)。问是否存在长度至少为\(3\)的回文子序列。本题多测。
\(n\in[3,5000],\sum n\leq5000\)。
显然,任何一个回文子序列的两端相等,又因为要求长度至少为\(3\),所以两端的距离至少为\(2\)。于是存在\(2\)个距离至少为\(2\)且相等的数是存在长度至少为\(3\)的回文子序列的必要条件。若存在\(2\)个距离至少为\(2\)且相等的数,那么随便选择它们之间的任意一个数即可构成长度为\(3\)的回文子序列。于是存在\(2\)个距离至少为\(2\)且相等的数是存在长度至少为\(3\)的回文子序列的充分条件。综上,存在\(2\)个距离至少为\(2\)且相等的数是存在长度至少为\(3\)的回文子序列的充要条件。
直接暴力枚举数对,时间复杂度\(\mathrm O\!\left(\sum n^2\right)\)。
下面是AC代码:
#include<bits/stdc++.h>
using namespace std;
const int N=5000;
int n;
int a[N+1];
void mian(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<=n;i++)for(int j=i+2/*距离至少为2*/;j<=n;j++)if(a[i]==a[j])/*相等*/{puts("YES");return;}
puts("NO");
}
int main(){
int testnum;
cin>>testnum;
while(testnum--)mian();
return 0;
}
C - Frog Jumps
洛谷题目页面传送门 & CodeForces题目页面传送门
有\(n+2\)个格子,编号\(0\sim n+1\)。\(\forall i\in[1,n]\),格子\(i\)上有一个字母\(a_i\in\{\texttt L,\texttt R\}\),分别表示在格子\(i\)上只能往左、右跳。青蛙一开始站在格子\(0\),\(a_0=\texttt R\)。求一个最小的\(d\),表示青蛙一次能跳\(1\sim d\)格且不能出格,使得青蛙能到达格子\(n+1\)。保证总有符合要求的\(d\)。本题多测。
首先,不难发现对于任意一个\(d\),能到达的格子组成前缀。证明:反证法。若不组成前缀,即存在\(i\in[1,n]\)使得\(i\)不能到达且\(i+1\)能到达。此时必存在一个\(x\in[0,i)\)使得\(a_x=\texttt R\)且\(x\)能到达\([i+1,n+1]\)中任意一个格子\(y\)。根据青蛙跳的规则,\([x+1,y]\)内任意一个格子都能从\(x\)到达,又\(x<i,y>i\Rightarrow i\in[x+1,y]\),与\(i\)不能到达矛盾。得证。
接下来,又双叒叕不难发现往左跳的格子不起丝毫作用,即任意一个能到达的格子都可以只往右跳来到达。证明:数学归纳法。设对于某一个\(d\),能到达的格子组成的前缀为\([0,r](r\geq0)\)。
-
格子\(0\)显然满足可以只往右跳来到达;
-
\(\forall x\in[1,r]\),假设\(\forall i\in[0,x)\),格子\(i\)都满足可以只往右跳来到达。对于\(x\)的某一种到达方式,设最后一步是由\(y\)跳过来的,分\(2\)种情况:
- \(y<x\)。此时最后一步一定是往右跳。再加上\(\forall i\in[0,x)\),格子\(i\)都满足可以只往右跳来到达,可以得出格子\(x\)满足可以只往右跳来到达;
- \(y>x\)。此时在\(y\)的到达方式中,必有一步是从\(z\in[1,x)\)跳到\(xx\in[x,n+1]\)。根据青蛙跳的规则,\([z+1,xx]\)内任意一个格子都能从\(z\)到达,又\(x>z,x\leq xx\Rightarrow x\in[z+1,xx]\),所以\(x\)能被\(z\)往右跳到达。再加上\(z\in[1,x)\subsetneq[0,x)\)且\(\forall i\in[0,x)\),格子\(i\)都满足可以只往右跳来到达,可以得出格子\(x\)满足可以只往右跳来到达。
综上,若\(\forall i\in[0,x)\),格子\(i\)都满足可以只往右跳来到达,那么格子\(x\)满足可以只往右跳来到达。
综上,得证。
于是我们可以把所有\(a_i=\texttt R\)的\(i\)有序地抽出来组成序列\(pos\),特殊地,\(pos_{|pos|}=n+1\)。显然,\(\forall i\in[1,|pos|),pos_i\to pos_{i+1}\),这种跳法最省\(d\)。要满足每次跳,起点和终点的距离都在\(d\)以内,所以答案是\(d=\max\limits_{i=1}^{|pos|-1}\{pos_{i+1}-pos_i\}\)。
下面是AC代码:
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=200000;
int n;
char a[N+5];
void mian(){
cin>>a+1;
n=strlen(a+1);
vector<int> pos;
pos.pb(0);
for(int i=1;i<=n;i++)if(a[i]=='R')pos.pb(i);
pos.pb(n+1);
int ans=0;
for(int i=0;i+1<pos.size();i++)ans=max(ans,pos[i+1]-pos[i]);//取最大距离
cout<<ans<<"\n";
}
int main(){
int testnum;
cin>>testnum;
while(testnum--)mian();
return 0;
}
D - Pair of Topics
洛谷题目页面传送门 & CodeForces题目页面传送门
给定\(2\)个长度为\(n\)的数组\(a,b\),求有多少个有序对\((i,j)\)满足\(i<j\)且\(a_i+a_j>b_i+b_j\)。
\(n\in\left[2,2\times10^5\right],a_i,b_i\in\left[1,10^9\right]\)。
\(a_i+a_j>b_i+b_j\Leftrightarrow a_i-b_i>b_j-a_j\),这样左边仅关于\(i\),右边仅关于\(j\)。于是我们把\(\forall i\in[1,n],a_i-b_i,b_i-a_i\)这\(2n\)个数一起离散化咯,然后类似BIT求逆序对那样建一个值域BIT,从后往前扫描,每扫到一个数\(i\),给答案贡献值域区间\((-\infty,a_i-b_i)\)上的区间计数的结果,再将\(b_i-a_i\)插入BIT。时间复杂度\(\mathrm O(n\log n)\)。
答案会爆int
,记得开long long
。
下面是AC代码:
#include<bits/stdc++.h>
using namespace std;
#define int long long//答案爆int
#define pb push_back
int lowbit(int x){return x&-x;}
const int N=200000;
int n;
int a[N+1];
int b[N+1];
vector<int> nums;
void discrete(){//离散化
sort(nums.begin(),nums.end());
nums.resize(unique(nums.begin(),nums.end())-nums.begin());
for(int i=1;i<=n;i++)
b[i]=lower_bound(nums.begin(),nums.end(),-a[i])-nums.begin()+1,
a[i]=lower_bound(nums.begin(),nums.end(),a[i])-nums.begin()+1;
}
struct bitree{//BIT
int sum[2*N+1];
void init(){memset(sum,0,sizeof(sum));}
void add(int x){//添加x
while(x<=nums.size())sum[x]++,x+=lowbit(x);
}
int Sum(int x){//前缀计数
int res=0;
while(x)res+=sum[x],x-=lowbit(x);
return res;
}
}bit;
signed main(){
cin>>n;
for(int i=1;i<=n;i++)scanf("%lld",a+i);
for(int i=1;i<=n;i++){
int x;
cin>>x;
a[i]-=x;
nums.pb(a[i]);//a[i]-b[i]
nums.pb(-a[i]);//b[i]-a[i]
}
discrete();
int ans=0;
bit.init();
for(int i=n;i;i--){
ans+=bit.Sum(a[i]-1);//贡献答案
bit.add(b[i]);//添加
}
cout<<ans;
return 0;
}
E - Sleeping Schedule
洛谷题目页面传送门 & CodeForces题目页面传送门
在Vova的世界里,一天\(h\mathrm h\)。Vova会睡\(n\)次觉,每次睡刚好\(1\)天。第\(i\)次会在第\(i-1\)次睡觉醒来后\((a_i-1)\mathrm h\)或\(a_i\mathrm h\)后入睡,特殊地,第\(0\)次睡觉在第\(0\mathrm h\)醒来。设一次睡觉是在入睡当天的第\(x\mathrm h\)入睡,若\(x\in[l,r]\),则称此次睡觉是好的。问最多能有多少次好的睡觉。
\(n\in[1,2000],h\in[3,2000],0\leq l\leq r<h,a_i\in[1,h)\)。
可以说是基础DP。
设\(dp_{i,j}\)表示考虑到第\(i\)次睡觉,第\(i\)次睡觉在当天第\(j\mathrm h\)醒来时最多的好的睡觉次数。边界为\(dp_{i,j}=\begin{cases}0&j=0\\- \infty&j\neq0\end{cases}\)(\(j\neq0\)时状态不合法),目标为\(\max\limits_{i=0}^{h-1}\{dp_{n,i}\}\),状态转移方程为\(dp_{i,j}=\max\!\left(dp_{i-1,(j-(a_i-1))\bmod h},dp_{i-1,(j-a_i)\bmod h}\right)+[j\in[l,r]]\)(选择在上一次睡觉醒来后\((a_i-1)\mathrm h\)还是\(a_i\mathrm h\)后入睡)。时间复杂度\(\mathrm O(nh)\)。
下面是AC代码:
#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
const int N=2000,H=2000;
int dp[N+1][H];
int n,h,l,r;
int a[N+1];
bool in(int x){return l<=x&&x<=r;}//[x in [l,r]]
int main(){
cin>>n>>h>>l>>r;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<h;i++)dp[0][i]=-inf;//不合法状态
for(int i=1;i<=n;i++)for(int j=0;j<h;j++)//DP
dp[i][j]=max(dp[i-1][(j-a[i]+1+h)%h],dp[i-1][(j-a[i]+h)%h])+in(j);//状态转移方程
cout<<*max_element(dp[n],dp[n]+h);//目标
return 0;
}
F - Maximum White Subtree
洛谷题目页面传送门 & CodeForces题目页面传送门
给定一棵树\(T=(V,E),|V|=n,|E|=n-1\),节点\(i\)有一个权值\(a_i\in\{0,1\}\),分别表示是白点、黑点。\(\forall i\in[1,n]\),找一个树上连通子图,设此图内白、黑点各有\(cnt1,cnt2\)个,要求最大化\(cnt1-cnt2\)。输出最大值。
\(n\in\left[1,2\times10^5\right]\)。
先吐槽一句:为什么我总共就打过\(4\)场div. 3,其中\(3\)场的F都是树形DP???/yiw
非常显然的树形DP+二次扫描与换根。
首先,如果当前要求的这个点\(x\)是树根的话,那一切都好办了。设\(dp_i\)表示在以\(x\)为整棵树的根的情况下,在以\(i\)为根的子树内选连通子图,必须包含\(i\)时\(cnt1-cnt2\)的最大值。目标是\(dp_x\),状态转移方程是\(dp_i=\sum\limits_{j\in son_i}\max(0,dp_j)+\begin{cases}-1&a_i=0\\1&a_i=1\end{cases}\)(累加以每个儿子为根的子树能给\(dp_i\)带来的贡献的最大值,如果为负就不选)。时间复杂度\(\mathrm O(n)\)。
然而题目要求对于所有点。不妨先令\(1\)为根求出所有点的DP值,再一遍二次扫描与换根求出所有点的答案。时间复杂度\(\mathrm O(n)\)。
总时间复杂度\(\mathrm O(n)+\mathrm O(n)=\mathrm O(n)\)。
下面是AC代码:
#include<bits/stdc++.h>
using namespace std;
#define pb push_back
const int N=200000;
int n;
bool a[N+1];//点权
vector<int> nei[N+1];
int dp[N+1];
void dfs(int x=1,int fa=0){//求出以1为根时所有点的DP值
dp[x]=a[x]?1:-1;
for(int i=0;i<nei[x].size();i++){
int y=nei[x][i];
if(y==fa)continue;
dfs(y,x);
dp[x]+=max(0,dp[y]);
}
}
int ans[N+1];
void dfs0(int x=1,int fa=0){//二次扫描与换根
ans[x]=dp[x];//记录答案
for(int i=0;i<nei[x].size();i++){
int y=nei[x][i];
if(y==fa)continue;
dp[x]-=max(0,dp[y]);
dp[y]+=max(0,dp[x]);
dfs0(y,x);
dp[y]-=max(0,dp[x]);
dp[x]+=max(0,dp[y]);
}
}
int main(){
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
for(int i=1;i<n;i++){
int x,y;
cin>>x>>y;
nei[x].pb(y);nei[y].pb(x);
}
dfs();
dfs0();
for(int i=1;i<=n;i++)cout<<ans[i]<<" ";
return 0;
}