洛谷七月月赛(Div.2)题解&&游记

Day 0

哇?一群金蓝勾大佬出的月赛!赶紧报名+宣传,做好了切(zi)题(bi)的准备!

Day 1

早上去上课了,中途出来打了一场 C Y E C C CYECC CYECC复赛;自我感觉 C Y E C C CYECC CYECC要滚粗了……

回家匆忙地去吃了午饭,然后跑到房间里去干了两道状压 d p dp dp的绿题,其中一题后来用贪心没过,真的是太菜了……本蒟蒻接着上洛谷求助,又没人回答(鄙视蒟蒻

这时是 13 : 50 13:50 13:50。本蒟蒻收拾了一下桌面,开始准备比赛……押了个题:

①T1: 简单的数学题,但是会挖坑;
②T2: 有一定思维含量的数学题,不会有坑;
③T3: 一道不错的 d p dp dp题,需要优化;
④T4: 一道卡常的数据结构题。

随着洛谷倒计时的结束,进入比赛界面。为了利用好加载界面的时间,本蒟蒻开了两个比赛界面,即在加载 A A A题时看 B B B题,以此类推。

果断开题!

A

看到这题名字就想吐……

滚粗的预感(逃


Solution

貌似这题也不难。分类讨论(防溢出)即可:

①当 n = 1 n=1 n=1时,输出 n n n
②当 n ≥ 40 n≥40 n40时,输出 1 1 1
③否则暴力枚举 x x x的值,用快速幂优化乘方

最差情况时间复杂度为 O ( n ) O(\sqrt n) O(n )。( m = 2 m=2 m=2)

Code

#include 
#define int long long
using namespace std;

int n,m;

int quick_power(int a,int b)
{
     
	int res=1;
	for (;b;b=b>>1,a=(a*a))
	{
     
		if (b&1)  res=(res*a);
	}
	return res;
}

signed main()
{
     
	cin>>n>>m;
	if (m==1)  cout<<n<<endl;
	else if (m>=40)  cout<<1<<endl;
	else
	{
     
		int cnt=0;
		for (int i=1;i<=n;i++)
		{
     
			if (quick_power(i,m)<=n&&quick_power(i,m)!=-1)  cnt++;
			else break;
		}
		cout<<cnt<<endl;
	}
	return 0;
}

B

本蒟蒻的数学能力算是个蒟蒻,而别的只能算个菜鸡,所以本蒟蒻相对来说就擅长点数学啦……于是就开了B题。

看了一半题,接着又开了D题;

D题看懂后,又开了C题;

于是,把 B , C , D B,C,D B,C,D全部看懂后,开始想 B B B题显然的思路。

Solution

假设一个等腰三角形三条边的长度为 a , a , b a,a,b a,a,b,简记 b b b为底。

分别考虑每条边为底,然后累加满足要求情况即可。设这条边的长度为 x x x,长度为 i i i的木棍的数量为 v i v_i vi m a x l e n = m a x maxlen=max maxlen=max{ a i a_i ai},则其满足要求的情况数为 ∑ j = ⌊ n 2 ⌋ + 1 m a x l e n C v j 2 \sum_{j=\lfloor \frac n 2 \rfloor+1}^{maxlen} C_{v_j}^2 j=2n+1maxlenCvj2

可以发现,此时每个等边三角形都被算了三次,所以答案还要减去 ( 3 − 1 ) ∑ i = 1 m a x l e n C v i 3 (3-1) \sum_{i=1}^{maxlen} C_{v_i}^3 (31)i=1maxlenCvi3

注意随时取模即可。由于有 n ≤ 2 × 1 0 5 n≤2×10^5 n2×105的限制,并不需要逆元。

万万没想到,由于溢出与 R E RE RE的问题,导致提交了 20 20 20发才过…… 辛亏心态还好,否则就直接滚上床睡觉颓废了

Code

#include 
#define int long long
#define rg register
using namespace std;
const int mod=998244353;

int n,ans=0,maxlen=200000;
int a[1000005],v[1000005],pre[1000005];

inline int C(int k)
{
     
	return ((k*k-k)/2)%mod;
}

inline int CC(int k)
{
     
	rg int now=(k*k-k)/2;
	return (((now%mod)*(k-2))/3)%mod;
}

inline int read()
{
     
	int s=0,w=1;
	char ch=getchar();
	
	while (ch<'0'||ch>'9')
	{
     
		if (ch=='-')  w=-w;
		ch=getchar();
	}
	while (ch>='0'&&ch<='9')
	{
     
		s=(s<<1)+(s<<3)+(ch^'0');
		ch=getchar();
	}
	return s*w;
}

signed main()
{
     
	cin>>n;
	for (rg int i=1;i<=n;++i)  a[i]=read(),v[a[i]]++;
	for (rg int i=1;i<=maxlen;++i)  pre[i]=pre[i-1]+C(v[i]);
	for (rg int i=1;i<=maxlen;++i)
	{
     
		rg int p=(i/2)+1;
		ans=(ans+((pre[maxlen]-pre[p-1]-C(v[i]))*v[i]))%mod;
	}
	for (rg int i=1;i<=maxlen;++i)
	{
     
		ans=(ans+CC(v[i]))%mod;
	}
	cout<<ans%mod<<endl;
	
	return 0;
}

C

果断开C题。

话不多说,顺利先骗到 11 11 11分,然后考虑正解。令人惊讶的是,我骗完 S u b t a s k 3 Subtask 3 Subtask3,以为只暂时拿到 6 6 6分,结果 S u b t a s k 4 Subtask 4 Subtask4跟着 S u b t a s k 3 Subtask 3 Subtask3也过了……

Solution

显然,我们可以将每列捆绑起来看。首先考虑无解的情况。定义"移动"为,一列在移到正确的位置中需要被翻转的次数。

①原来这两个数在同一列,现在不在同一列了
②原来这两个数在同一列,经过奇数次移动,这两个数的上下顺序仍然相同;
③原来这两个数在同一列,经过偶数次移动,这两个数的上下顺序变化了。

int m[2*maxlen+5][3];
for (rg int i=1;i<=2;i++)
{
     
	for (rg int j=1;j<=n;j++)  a[i][j]=read();
}
for (rg int i=1;i<=2;i++)
{
     
	for (rg int j=1;j<=n;j++)
	{
     
		b[i][j]=read();
		m[b[i][j]][1]=i,m[b[i][j]][2]=j;
	}
}
bool no_solution()
{
     
	for (int i=1;i<=n;i++)
	{
     
		int x=a[1][i],y=a[2][i];
		int xx=m[x][1],xy=m[x][2];
		int yx=m[y][1],yy=m[y][2];
		
		if (xy!=yy)  return true;
		if (i%2==xy%2&&yx!=xx+1)  return true;
		else if (i%2!=xy%2&&xx!=yx+1)  return true;
		
		v[i]=xy;
	}
	return false;
}
//判断无解的方法

既然已经保证有解,考虑最少翻转的次数。

我们可以将每列捆绑起来看,设第 i i i列它在 b b b数组中的位置为第 v i v_i vi列。此时,答案就是 v v v的逆序对个数!即,我们每次转换都会减少有且仅有一个逆序对,最终逆序对的数量为 0 0 0

用树状数组可以在 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)的时间复杂度内求逆序对数。

本题略微卡常,注意快速读入即可。总时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

当时比赛的时候脑子瓦特,第一次数组开小 R E RE RE,第二次用 m a p map map记录数字位置导致超时,第三次快读忘写导致卡常……

Code

#include 
#define int long long
#define rg register
using namespace std;
const int maxlen=1000000;

int n,ans=0;
int a[5][maxlen+5],b[5][maxlen+5],m[2*maxlen+5][3];
int v[maxlen+5],tree[maxlen+5];

inline int lowbit(int k)
{
     
	return k&(-k);
}

bool no_solution()
{
     
	for (int i=1;i<=n;i++)
	{
     
		int x=a[1][i],y=a[2][i];
		int xx=m[x][1],xy=m[x][2];
		int yx=m[y][1],yy=m[y][2];
		
		if (xy!=yy)  return true;
		if (i%2==xy%2&&yx!=xx+1)  return true;
		else if (i%2!=xy%2&&xx!=yx+1)  return true;
		
		v[i]=xy;
	}
	return false;
}

inline void change(int rt)
{
     
	while (rt<=n)
	{
     
		tree[rt]++;
		rt+=lowbit(rt);
	}
}

inline int query(int rt)
{
     
	int ans=0;
	while (rt>=1)
	{
     
		ans+=tree[rt];
		rt-=lowbit(rt);
	}
	return ans;
}

inline int read()
{
     
	int s=0,w=1;
	char ch=getchar();
	
	while (ch<'0'||ch>'9')
	{
     
		if (ch=='-')  w=-w;
		ch=getchar();
	}
	while (ch>='0'&&ch<='9')
	{
     
		s=(s<<1)+(s<<3)+(ch^'0');
		ch=getchar();
	}
	return s*w;
}

signed main()
{
     
	cin>>n;
	for (rg int i=1;i<=2;i++)
	{
     
		for (rg int j=1;j<=n;j++)  a[i][j]=read();
	}
	for (rg int i=1;i<=2;i++)
	{
     
		for (rg int j=1;j<=n;j++)
		{
     
			b[i][j]=read();
			m[b[i][j]][1]=i,m[b[i][j]][2]=j;
		}
	}
	if (no_solution())  return cout<<"dldsgay!!1"<<endl,0;
		
	for (int i=n;i>=1;i--)
	{
     
		ans+=query(v[i]-1);
		change(v[i]);
	}
	cout<<ans<<endl;
	
	return 0;
}

D

接着开了 D D D题。

什么东西啊?接着看了一眼排行榜,只有 i x 35 ix35 ix35大佬暂时拿到了 75 75 75分(后来他 A K AK AK了%%%)……难道这题不可做?

事实上,排行榜上 D D D题AC的人数很多。当时这题是抱着试一试的想法来搞的。

Before Solution

①考虑分块。

众所周知,分块的时间复杂度为 O ( n n ) O(n \sqrt n) O(nn )。直接放弃。

②考虑线段树。

什么鬼唉?线段树貌似用不起来,毕竟无法转换为区间修改,也显然不存在区间加法(即结合律)、

好吧,线段树暂时放弃吧。

此时万念俱灰地打了 S u b t a s k 1 Subtask 1 Subtask1的暴力,然后看到 S u b t a s k 2 Subtask 2 Subtask2显然可以用树状数组通过,就分段考虑,拿到了 50 50 50分。

看到 S u b t a s k 3 Subtask 3 Subtask3奇怪的样子,本蒟蒻继续想正解。

Solution

③考虑哈希。

我们如何判断两个区间本质相同呢?假设第一个区间的哈希值为 x x x,第二个区间的哈希值为 y y y,两个区间的长度均为 l e n len len,且第一个区间的最小值为 a a a,第二个区间的最小值为 b b b,那么总有 x + l e n ( b − a ) = y x+len(b-a)=y x+len(ba)=y

a , b , x , y a,b,x,y a,b,x,y均可以用线段树来维护(单点修改+区间查询),哈希值也可以用线段树来维护(注意取模),故总时间复杂度为 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n)

正解?

于是,本蒟蒻看到了充裕的 2 2 2小时多的时间,开始打代码~同时想到,单单求出两个最小值貌似容易被卡,于是也可以再设 a a a为第一个区间的最大值, b b b为第二个区间的最大值,此时公式 x + l e n ( b − a ) = y x+len(b-a)=y x+len(ba)=y仍成立,可以进行二次判断。并且,哈希的模数不仅要设为 998244353 998244353 998244353,还要设为 1 0 9 + 7 10^9+7 109+7 1 0 9 + 9 10^9+9 109+9……

惨惨的是,对于一名刚刚学会线段树的小蒟蒻,代码真是 T M TM TM难调啊……

Code

没调出来……QAQ

事实上,上述思路为正解。


这一场洛谷月赛相对于之前的(Expecially MtOI Round)洛谷月赛还是简单很多的,比如之前 i x 35 ix35 ix35神犇竟然把一个数论+树论的难题放在了 D i v . 3 Div.3 Div.3 C C C题, z r m p a u l zrmpaul zrmpaul把一道容斥+数学+整除分块的难题又放在了 C C C题……

D D D题的思路是比较显然的,尽管我的做法仍然有可能被毒瘤的人 H a c k Hack Hack。以前 D D D题出过一次序列通项(特征根)+数学+卡常(线性筛还卡),还有一次出了树上的一个不显然的贪心(《重拳出击》),直接送本蒟蒻退役了两天。

综上所述,代码能力急需加强,不然 D D D题只能对着程序目瞪口呆——毕竟宏,中,微观结合才是 A C AC AC的法宝之一。

菜死啦~

你可能感兴趣的:(算法)