Atcoder Beginner Contest 179 题解&&花絮

文章目录

  • Solution
      • A
      • B
      • C
      • D
      • E
      • F
  • 花絮
  • Code
      • A
      • B
      • C
      • D
      • E
      • F

酣畅淋漓的一波 A K AK AK……

累死我了,第二天 7 : 00 7:00 7:00才醒。

Solution

A

直接模拟即可。

B

使用一个计数器 c n t cnt cnt,如果当前的 a i = b i a_i=b_i ai=bi,那么 c n t cnt cnt的值加一;否则归 0 0 0。若某个时刻 c n t cnt cnt的值不小于 3 3 3,说明满足要求,否则不满足要求。

C

考虑如果我们已经得到了 C C C,那么满足要求的 ( A , B ) (A,B) (A,B)对数就是 N − C N-C NC的约数个数。

所以,我们预处理出每个数的约数个数。即,我们枚举 i ( 1 ≤ i ≤ n ) i(1≤i≤n) i(1in),对于所有是 i i i倍数的数均加 1 1 1,从而在 O ( n ln ⁡ n ) O(n \ln n) O(nlnn)的代价下求出了每个数的约数个数。

然后,枚举 C C C并累加满足要求的 ( A , B ) (A,B) (A,B)对即可。

时间复杂度 O ( n ln ⁡ n ) O(n \ln n) O(nlnn)

D

怎么有人跟我说是矩阵优化 d p dp dp

首先,如果每个区间的长度均为 1 1 1,我们该如何做呢?

这是一个显然的 d p dp dp,时间复杂度 O ( n k ) O(nk) O(nk)

但是,每个区间的长度不一定是 1 1 1,如果我们把这些可能很长的区间拆成一个一个长度为 1 1 1的小块的话,时间复杂度原地爆炸成 O ( n 2 ) O(n^2) O(n2)

观察 d p dp dp转移式,可以发现,在区间修改后我们的 d p dp dp式子在转移时需要区间求和。即,假设对于一个给定的区间 [ l , r ] [l,r] [l,r],目前走到了 i ( i > l ) i(i>l) i(i>l),那么它可以从区间 [ m a x ( i − r , 1 ) , i − l ] [max(i-r,1),i-l] [max(ir,1),il]转移过来,显然这是一个区间求和。

所以我们想到了用前缀和优化。一边转移,一边维护前缀和,即可将时间复杂度优化到 O ( n k ) O(nk) O(nk)

E

太套路了吧

通过观察,不难发现,这个序列是有周期的(纯循环或混循环),且周期的长度不会超过 m m m,周期之前的长度也不会超过 m m m

所以,我们开桶,记录下这个值上一次出现在哪个位置。比如,对于 n = 1 0 10 , x = 2 , m = 7 n=10^{10}, x=2, m=7 n=1010,x=2,m=7的情况,序列是 2 , 4 , 2 , 4 … … 2,4,2,4…… 2,4,2,4如果我们扫描到 2 2 2的时候,查桶时会发现 2 2 2已经出现过,此时循环节为 24 24 24

有了循环节之后就很好做啦。

时间复杂度 O ( m ) O(m) O(m)

F

怎么又考毒瘤的线段树啊

考虑维护两个序列 A , B A,B A,B:

①每一行的白格子最早出现在哪个位置。
②每一列的白格子最早出现在哪个位置。

考虑修改的时候,我们需要改变哪些值。

①修改了列( 1 1 1操作): 设 n o w now now B k B_k Bk。把序列 A A A 1 1 1 n o w − 1 now-1 now1的数对 k k k m i n min min,然后 B k = 1 B_k=1 Bk=1;

②修改了行( 2 2 2操作): 设 n o w now now A k A_k Ak。把序列 B B B 1 1 1 n o w − 1 now-1 now1的数对 k k k m i n min min,然后 A k = 1 A_k=1 Ak=1

现在,我们把这题转换为了两种操作:

①区间对一个值取 m i n min min
②单点查询。

显然,可以用两棵可爱的线段树维护。

但是,这里线段树维护的是区间的标记,并没有维护别的东西。即,假设我们要对 [ 3 , 7 ] [3,7] [3,7]这个区间对 4 4 4 m i n min min,且 [ 3 , 7 ] [3,7] [3,7]分成了 [ 3 , 4 ] , [ 5 , 6 ] , [ 7 ] [3,4],[5,6],[7] [3,4],[5,6],[7]这三个区间,那么这三个区间的标记全部对 4 4 4 m i n min min(初始时所有标记均为无穷大)。

那么单点查询呢?假设我们要查询某个序列第 k k k个位置的值。我们可以用根节点往下走,拐来拐去,记录下所经过的节点的 t a g tag tag的最小值 m i n v minv minv,并实时 p u s h d o w n pushdown pushdown。那么,最终查询的值就是 m i n ( m i n v , a k ) min(minv,a_k) min(minv,ak)

所以,我们在 O ( n log ⁡ n ) O(n\log n) O(nlogn)的时间复杂度内维护了这两个数组。


就在此时,你可能会发现,样例 3 3 3过不去了。

但是我们维护得都是对的呀?

答案是啥呢?

貌似这两个数组并不能得到最终的答案……

好吧,这题我也不会做

首先,我们回来想一想,我们认为

∑ i = 1 n m a x ( a i − 2 , 0 ) + ∑ i = 1 n m a x ( b i − 2 , 0 ) \sum_{i=1}^n max(a_i-2,0)+\sum_{i=1}^n max(b_i-2,0) i=1nmax(ai2,0)+i=1nmax(bi2,0)

就是答案。

但是,可以发现,如果一个黑格子的左边以及上面都没有白格子,那么它就会被算两遍。

我们现在要减去这个被重复算的值。如何计算呢?假设所有行修改的 x x x的最小值为 h m hm hm,所有列修改的 x x x的最小值 l m lm lm,那么这个值就是 m a x ( h m − 2 , 0 ) × m a x ( l m − 2 , 0 ) max(hm-2,0)×max(lm-2,0) max(hm2,0)×max(lm2,0)

显然, h m , l m hm,lm hm,lm容易求出,最终减去这个值即可。

最后,注意:

①如果 n o w = 1 now=1 now=1,放弃修改,否则会出现死循环;
②最大值代表的 i n f inf inf开大;
③线段树开 8 8 8倍;
④别忘了答案的表达式中对 0 0 0 m a x max max的那些部分。

总时间复杂度为 O ( n log ⁡ n ) O(n \log n) O(nlogn)


花絮

不知道自己在干什么,这题赛事竟然刚了 70 m i n 70min 70min才在距离比赛 18 m i n 18min 18min的时候刚出来。

线段树怎么还是不熟啊

A K AK AK之后貌似敲了一个巨佬的桌子,这里对他说对不起,影响了大家的做题体验。

另外,以后敢说几十个秋葵的人,我就把你变成秋葵。

Code

A

#include 
#define int long long
using namespace std;
 
string s;
 
signed main()
{
     
	cin>>s;
	int n=s.size()-1;
	if (s[n]=='s')  cout<<s<<"es"<<endl;
	else cout<<s<<"s"<<endl;
	
	return 0;
}

B

#include 
#define int long long
using namespace std;
 
int n,len=0;
int a[1000005],b[1000005],c[1000005];
 
signed main()
{
     
	cin>>n;
	for (int i=1;i<=n;i++)  cin>>a[i]>>b[i];
	for (int i=1;i<=n;i++)
	{
     
		if (a[i]==b[i])
		{
     
			len++;
			if (len>=3)  return cout<<"Yes"<<endl,0;
		}
		else len=0;
	}
	cout<<"No"<<endl; 
	return 0;
}

C

#include 
#define int long long
using namespace std;
 
int n;
int cnt[1000005];
 
signed main()
{
     
	cin>>n;
	for (int i=1;i<=n;i++)
	{
     
		for (int j=i;j<=n;j+=i)  cnt[j]++;
	}
	int ans=0;
	for (int i=1;i<=n;i++)  ans+=cnt[n-i];
	cout<<ans<<endl;
	
	return 0;
}

D

#include 
#define int long long
using namespace std;
const int mod=998244353;
 
int n,k;
int l[1005],r[1005],dp[1000005],pre[2000005];
 
inline int query(int ll,int rr)
{
     
	return ((pre[rr]-pre[ll-1])%mod+mod)%mod;
}
 
signed main()
{
     
	cin>>n>>k;
	for (int i=1;i<=k;i++)  cin>>l[i]>>r[i];
	
	dp[1]=1;
	for (int i=1;i<=n;i++)
	{
     
		for (int j=1;j<=k;j++)
		{
     
			if (l[j]>=i)  continue;
			int nowr=min(r[j],i-1ll);
			dp[i]=(dp[i]+max(query(i-nowr,i-l[j]),0ll))%mod;
		}
		pre[i]=(pre[i-1]+dp[i])%mod;
	}
	cout<<dp[n]%mod<<endl;
	
	return 0;
}

E

#include 
#define int long long
using namespace std;
 
int n,x,m,l,r;
int a[1000005],pre[1000005];
 
map<int,int> ma;
 
signed main()
{
     
	cin>>n>>x>>m;
	a[1]=x;
	pre[1]=x;
	ma[x]=1;
	for (int i=2;i<=10*m;i++)
	{
     
		a[i]=(a[i-1]*a[i-1])%m;
		if (ma[a[i]])
		{
     
			l=ma[a[i]];
			r=i-1;
			break;
		}
		ma[a[i]]=i;
		pre[i]=pre[i-1]+a[i];
	}
	int ans=0,tim,lef,tot=0,tot2=0;
	ans=pre[l-1];
	tim=(n-(l-1))/(r-l+1);
	lef=n-(l-1)-tim*(r-l+1);
	
	for (int i=l;i<=l+lef-1;i++)  tot+=a[i];
	for (int i=l;i<=r;i++)  tot2+=a[i];
	cout<<ans+tot2*tim+tot<<endl;
	
	return 0;
}

F

#include 
#define int long long
#define inf 200000000000000007
using namespace std;

int n,q,opt,k,ans=0,mina,minb;
int a[2000005],b[2000005];

struct segmentree_a
{
     
	int num;
	int tag;
}treea[2000005];

struct segmentree_b
{
     
	int num;
	int tag;
}treeb[2000005];

inline void f_a(int rt,int l,int r,int k)
{
     
	treea[rt].tag=min(treea[rt].tag,k);
}

inline void f_b(int rt,int l,int r,int k)
{
     
	treeb[rt].tag=min(treeb[rt].tag,k);
}

inline void pushdown_a(int rt,int l,int r)
{
     
	int mid=(l+r)>>1;
	f_a(2*rt,l,mid,treea[rt].tag);
	f_a(2*rt+1,mid+1,r,treea[rt].tag);
}

inline void pushdown_b(int rt,int l,int r)
{
     
	int mid=(l+r)>>1;
	f_b(2*rt,l,mid,treeb[rt].tag);
	f_b(2*rt+1,mid+1,r,treeb[rt].tag);
}

inline void change_a(int nl,int nr,int l,int r,int rt,int k)
{
     
	pushdown_a(rt,l,r);
	if (nl<=l&&r<=nr)
	{
     
		treea[rt].tag=min(treea[rt].tag,k);
		return;
	}
	int mid=(l+r)>>1;
	if (nl<=mid)  change_a(nl,nr,l,mid,2*rt,k);
	if (nr>mid)  change_a(nl,nr,mid+1,r,2*rt+1,k);
}

inline void change_b(int nl,int nr,int l,int r,int rt,int k)
{
     
	pushdown_b(rt,l,r);
	if (nl<=l&&r<=nr)
	{
     
		treeb[rt].tag=min(treeb[rt].tag,k);
		return;
	}
	int mid=(l+r)>>1;
	if (nl<=mid)  change_b(nl,nr,l,mid,2*rt,k);
	if (nr>mid)  change_b(nl,nr,mid+1,r,2*rt+1,k);
}

inline int query_a(int nl,int l,int r,int rt)
{
     
	pushdown_a(rt,l,r);
	
	int mid=(l+r)>>1,ans;
	if (l==r&&l==nl)  return min(a[l],treea[rt].tag);
	if (nl<=mid)  ans=query_a(nl,l,mid,2*rt);
	else ans=query_a(nl,mid+1,r,2*rt+1);
	return ans;
}

inline int query_b(int nl,int l,int r,int rt)
{
     
	pushdown_b(rt,l,r);
	
	int mid=(l+r)>>1,ans;
	if (l==r&&l==nl)  return min(b[l],treeb[rt].tag);
	if (nl<=mid)  ans=query_b(nl,l,mid,2*rt);
	else ans=query_b(nl,mid+1,r,2*rt+1);
	return ans;
}

signed main()
{
     
	cin>>n>>q;
	mina=minb=n;
	for (int i=1;i<=n;i++)  a[i]=n;
	for (int i=1;i<=n;i++)  b[i]=n;
	a[n]=b[n]=1;
	
	for (int i=1;i<=8*n;i++)  treea[i].tag=treeb[i].tag=inf;
	while (q--)
	{
     
		cin>>opt>>k;
		if (opt==1)
		{
     
			mina=min(mina,k);
			int now=query_b(k,1,n,1);
			if (now>1)  change_a(1,now-1,1,n,1,k);
			b[k]=1;
		}
		else
		{
     
			minb=min(minb,k);
			int now=query_a(k,1,n,1);
			if (now>1)  change_b(1,now-1,1,n,1,k);
			a[k]=1;
		}
	}
	for (int i=1;i<=n;i++)  a[i]=query_a(i,1,n,1);
	for (int i=1;i<=n;i++)  b[i]=query_b(i,1,n,1);
	for (int i=2;i<=n-1;i++)
	{
     
		int cnt=max(a[i]-2ll,0ll);
		ans+=cnt;
	}
	for (int i=2;i<=n-1;i++)
	{
     
		int cnt=max(b[i]-2ll,0ll);
		ans+=cnt;
	}
	int another=1;
	another*=max(0ll,mina-2);
	another*=max(0ll,minb-2);
	cout<<ans-another<<endl;
	
	return 0;
}

你可能感兴趣的:(比赛题解)