Educational Codeforces Round 162 (Rated for Div. 2)
Preface
开学了没时间组队训练就抽空把之前欠下的CF补一补
这场当时玩《拔作岛》上头了忘记有比赛了,等想起来的时候已经快结束了就没现场打
赛后补题发现A~E都很简单,F的话一个地方没太想清看了题解才会
A. Moving Chips
签到,找一个极小的且包含了所有\(1\)的区间,这个区间中\(0\)的个数就是答案
#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=55;
int t,n,a[N];
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%d",&t);t;--t)
{
RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
int l=n,r=0; for (i=1;i<=n;++i) if (a[i]==1) l=min(l,i),r=max(r,i);
int ans=0; for (i=l;i<=r;++i) ans+=(a[i]==0);
printf("%d\n",ans);
}
return 0;
}
B. Monsters Attack!
签到,把怪物按照\(|x_i|\)排序后贪心先打近的即可
#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define int long long
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=300005;
int t,n,k,a[N],x[N]; pi p[N];
signed main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%lld",&t);t;--t)
{
RI i; for (scanf("%lld%lld",&n,&k),i=1;i<=n;++i) scanf("%lld",&a[i]);
for (i=1;i<=n;++i) scanf("%lld",&x[i]),p[i]=pi(abs(x[i]),a[i]);
int cur=0; bool flag=1; for (sort(p+1,p+n+1),i=1;i<=n;++i)
if ((cur+=p[i].se)>p[i].fi*k) { flag=0; break; }
puts(flag?"YES":"NO");
}
return 0;
}
C. Find B
感觉是个经典trick来着
对于某个长度为\(len\)的区间,由于填数的下限是\(1\),因此很容易想到把所有原来不是\(1\)的位置都填\(1\)
而原来是\(1\)的位置最少要填\(2\),如果有多出来的部分随便补给一个\(1\)上即可(如果没有\(1\)随便补给一个数也成立)
设区间内有\(cnt\)个\(1\),区间和为\(sum\),则该区间合法的充要条件为\(len-cnt+2\times cnt\le sum\)
#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define int long long
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=300005;
int t,n,q,x,l,r,pfx[N],cnt1[N];
signed main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%lld",&t);t;--t)
{
RI i; for (scanf("%lld%lld",&n,&q),i=1;i<=n;++i)
scanf("%lld",&x),pfx[i]=pfx[i-1]+x,cnt1[i]=cnt1[i-1]+(x==1);
for (i=1;i<=q;++i)
{
scanf("%lld%lld",&l,&r); int c1=cnt1[r]-cnt1[l-1];
if (l==r) { puts("NO"); continue; }
puts(r-l+1-c1+2LL*c1<=pfx[r]-pfx[l-1]?"YES":"NO");
}
}
return 0;
}
D. Slimes
唉又是一眼傻逼题
对于每个slime
,首先特判掉它被相邻的一步干掉的情况,否则考虑向左右两个方向分别二分答案
不难发现此时需要满足两个条件,一个是区间和要\(>a_i\);另一个是区间内所有数不能都相同
后者的话可以通过维护每个数向左/向右第一个与其不同的数来快速判断,总复杂度\(O(n\log n)\)
#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define int long long
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=300005,INF=1e18;
int t,n,a[N],pfx[N],nxt[N],ans1[N],ans2[N];
inline void solve(int* ans)
{
RI i; for (i=1;i<=n;++i) ans[i]=INF;
for (i=1;i<=n;++i) pfx[i]=pfx[i-1]+a[i];
for (nxt[n]=n+1,i=n-1;i>=1;--i)
if (a[i]==a[i+1]) nxt[i]=nxt[i+1]; else nxt[i]=i+1;
for (i=1;i<n;++i)
{
if (a[i+1]>a[i]) { ans[i]=1; continue; }
int l=1,r=n-i,mid,ret=INF;
while (l<=r)
{
mid=l+r>>1;
if (pfx[i+mid]-pfx[i]>a[i]&&nxt[i+1]<=i+mid) ret=mid,r=mid-1; else l=mid+1;
}
ans[i]=ret;
}
}
signed main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%lld",&t);t;--t)
{
RI i; for (scanf("%lld",&n),i=1;i<=n;++i) scanf("%lld",&a[i]);
solve(ans1); reverse(a+1,a+n+1); solve(ans2); reverse(ans2+1,ans2+n+1);
for (i=1;i<=n;++i) ans1[i]=min(ans1[i],ans2[i]),printf("%lld%c",ans1[i]==INF?-1:ans1[i]," \n"[i==n]);
}
return 0;
}
E. Count Paths
很典的一个题,感觉启发式合并做这类题算是个很经典的问题了
考虑把无根树转化为有根树,并且总是在一条路径的LCA处统计其贡献,不难发现路径有两种:
不妨称某个子树中某种颜色的“顶部节点”为满足在该子树内不存在另一个该颜色的点为其祖先的点,则有:
- 以当前点为LCA的路径,此时我们需要求出子树内该点颜色的“顶部节点”的数量
- 经过当前点为LCA的路径,此时所选的路径颜色应与该点颜色不同,方案数就是在子树中的“顶部节点”间配对的方案数
考虑直接拿map
来维护一个点子树内每种颜色对应的“顶部节点”的数量,子树间直接用启发式合并即可,同时过程中可以顺带统计上面两种情况的贡献
总复杂度\(O(n\log^2 n)\)
#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define int long long
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=200005;
int t,n,c[N],x,y,ans; vector <int> v[N]; map <int,int> rst[N];
inline void merge(CI ban,CI x,CI y)
{
if (rst[x].size()<rst[y].size()) swap(rst[x],rst[y]);
for (auto [col,cnt]:rst[y])
{
if (col!=ban) ans+=rst[x][col]*cnt;
rst[x][col]+=cnt;
}
rst[y].clear();
}
inline void DFS(CI now=1,CI fa=0)
{
for (auto to:v[now]) if (to!=fa) DFS(to,now),merge(c[now],now,to);
ans+=rst[now][c[now]]; rst[now][c[now]]=1;
}
signed main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
for (scanf("%lld",&t);t;--t)
{
RI i; for (scanf("%lld",&n),i=1;i<=n;++i)
scanf("%lld",&c[i]),rst[i].clear(),v[i].clear();
for (i=1;i<n;++i) scanf("%lld%lld",&x,&y),v[x].push_back(y),v[y].push_back(x);
ans=0; DFS(); printf("%lld\n",ans);
}
return 0;
}
F. Shrink-Reverse
刚开始没想清楚第二种操作的本质以为很不可做,后面瞄了眼题解马上就会了
由于这题在算答案的时候天然会去掉所有的前导\(0\),因此如果没有SHRINK-REVERSE
操作的话就是一个显而易见的贪心,每次把最靠左的\(1\)和最靠右的\(0\)交换即可
考虑SHRINK-REVERSE
操作的本质,其实就是把原串的所有前导\(0\)和后缀\(0\)都删除后,再将中间有效的部分reverse
不难发现由于SHRINK-REVERSE
不会改变中间有效部分的长度,而交换操作总会使有效部分长度减\(1\),因此我们至多只会进行一次SHRINK-REVERSE
操作
操作\(0\)次SHRINK-REVERSE
的情况很简单,我们现在考虑操作\(1\)次的情况,首先可以把原串reverse
一下
考虑对于某个区间\([l,r]\),如果我们钦定其为最后有效部分对应的区间,则需要满足以下两个条件:
- 该区间长度$\ge $序列中\(1\)的个数
- 区间外部的\(1\)的数量\(\le k-1\)个(因为要留出最后一次操作用于
SHRINK-REVERSE
)
不难发现我们可以大力枚举左端点,用尺取法找出满足上面要求的最靠左的右端点
考虑对于两个方案\([l_1,r_1],[l_2,r_2]\)该如何比较其优劣,显然若区间长度不等则选择长度较短的那个一定更优
否则当两个区间长度相等时,手玩以下会发现原来字典序更小的那个在把外部的\(1\)移进去后也一定更优,因此这种情况就是比较两个字串的字典序大小
这个问题就非常经典了,可以用后缀数组预处理LCP,或者直接拿二分+Hash来求LCP,总复杂度\(O(n\log n)\)
#include<cstdio>
#include<iostream>
#include<utility>
#include<vector>
#include<cstring>
#include<cmath>
#include<cstdlib>
#include<algorithm>
#include<queue>
#include<set>
#include<map>
#include<set>
#include<array>
#include<random>
#include<bitset>
#include<ctime>
#include<limits.h>
#include<assert.h>
#include<unordered_set>
#include<unordered_map>
#define RI register int
#define CI const int&
#define mp make_pair
#define fi first
#define se second
#define Tp template <typename T>
using namespace std;
typedef long long LL;
typedef long double LDB;
typedef unsigned long long u64;
typedef __int128 i128;
typedef pair <int,int> pi;
typedef vector <int> VI;
typedef array <int,3> tri;
const int N=500005,mod=1e9+7;
int n,k,cnt1,l=-1,r=-1; char s[N],t[N],tmp[N];
const int mod1=998244353,mod2=1e9+7;
struct Hasher
{
int x,y;
inline Hasher(CI X=0,CI Y=0)
{
x=X; y=Y;
}
inline LL get_val(void)
{
return ((1LL*x)<<31LL)|(1LL*y);
}
friend inline bool operator == (const Hasher& A,const Hasher& B)
{
return A.x==B.x&&A.y==B.y;
}
friend inline Hasher operator + (const Hasher& A,const Hasher& B)
{
return Hasher((A.x+B.x)%mod1,(A.y+B.y)%mod2);
}
friend inline Hasher operator - (const Hasher& A,const Hasher& B)
{
return Hasher((A.x-B.x+mod1)%mod1,(A.y-B.y+mod2)%mod2);
}
friend inline Hasher operator * (const Hasher& A,const Hasher& B)
{
return Hasher(1LL*A.x*B.x%mod1,1LL*A.y*B.y%mod2);
}
}h[N],pw[N];
const Hasher seed=Hasher(31,131);
inline Hasher get(CI l,CI r)
{
return h[r]-h[l-1]*pw[r-l+1];
}
inline bool cmp(CI l1,CI r1,CI l2,CI r2)
{
int l=0,r=r1-l1+1,mid,ret;
while (l<=r)
{
mid=l+r>>1;
if (get(l1,l1+mid-1)==get(l2,l2+mid-1)) ret=mid,l=mid+1; else r=mid-1;
}
if (ret==r1-l1+1) return 0;
return s[l1+ret]<s[l2+ret];
}
inline int calc(char *s,CI l,CI r)
{
int ret=0,cur=1; for (RI i=r;i>=l;--i,cur=2LL*cur%mod)
if (s[i]=='1') (ret+=cur)%=mod; return ret;
}
int main()
{
//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
RI i,j; for (scanf("%d%d%s",&n,&k,s+1),i=1;i<=n;++i) cnt1+=(s[i]=='1');
vector <int> c[2]; for (i=1;i<=n;++i) c[s[i]-'0'].push_back(i),tmp[i]=s[i];
for (reverse(c[1].begin(),c[1].end()),i=1;i<=k;++i)
{
if (c[0].empty()||c[1].empty()) break;
if (c[1].back()>c[0].back()) break;
swap(s[c[1].back()],s[c[0].back()]);
c[1].pop_back(); c[0].pop_back();
}
for (i=1;i<=n;++i) t[i]=s[i],s[i]=tmp[i];
for (pw[0]=Hasher(1,1),i=1;i<=n;++i) pw[i]=pw[i-1]*seed;
for (reverse(s+1,s+n+1),i=1;i<=n;++i) h[i]=h[i-1]*seed+Hasher(s[i],s[i]);
if (k==0) return printf("%d",calc(t,1,n)),0;
int out=0; for (i=cnt1+1;i<=n;++i) out+=(s[i]=='1');
for (i=1,j=cnt1;i+cnt1-1<=n;++i)
{
if (j-i+1<cnt1) out-=(s[++j]=='1');
while (j<=n&&out>k-1)
{
if (++j>n) break; out-=(s[j]=='1');
}
if (j>n) break;
if (l==-1||j-i+1<r-l+1||(j-i+1==r-l+1&&cmp(i,j,l,r))) l=i,r=j;
out+=(s[i]=='1');
}
if (l!=-1)
{
for (out=0,i=1;i<=n;++i) if (i<l||i>r) out+=(s[i]=='1');
for (i=r;i>=l;--i) if (s[i]=='0'&&out) s[i]='1',--out;
j=1; while (t[j]=='0') ++j; bool flag=0;
if (r-l+1!=n-j+1) flag=(r-l+1<n-j+1); else
{
for (i=l;i<=r&&j<=n;++i,++j)
if (s[i]!=t[j]) { flag=(s[i]<t[j]); break; }
}
printf("%d",flag?calc(s,l,r):calc(t,1,n));
} else printf("%d",calc(t,1,n));
return 0;
}
Postscript
最近开学后由于平时晚上寝室要断电,因此很多CF的场都没法现场打了,只好等到赛后去补题了