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的场都没法现场打了,只好等到赛后去补题了

posted @ 2024-02-26 20:58  空気力学の詩  阅读(110)  评论(0编辑  收藏  举报