Codeforces Round 872 (Div. 2)

Preface

怎么感觉越来越菜了现在,拿个小号打Div2都被血虐

C题一个Corner Case没写好直接爆炸,本来10min秒切的愣是搞到最后用对拍才看出来

本来D2和E都很有思路的说,结果都来不及写了苦路西

不过换着想还好是小号,不然直接又俯冲蓝名了


A. LuoTianyi and the Palindrome String

SB题,如果所有字符都相同则答案为\(-1\),否则肯定存在删去某个字符使得整个串不是回文的,答案为\(|s|-1\)

#include<cstdio>
#include<iostream>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=55;
int t,n; char s[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; bool flag=0; for (scanf("%s",s+1),n=strlen(s+1),i=2;i<=n;++i)
		if (s[i]!=s[1]) flag=1; printf("%d\n",flag?n-1:-1);
	}
	return 0;
}

B. LuoTianyi and the Table

SB题,首先不妨设\(n>m\)

考虑如果给\((1,1)\)填上全局的最大值\(mx\),然后\((2,1)\)填上全局最小值\(mi\)\((1,2)\)填上次小值\(smi\),这样总贡献就是

\[(n-1)\times(m-1)\times (mx-mi)+(n-1)\times (mx-mi)+(m-1)\times (mx-smi) \]

当然也有可能分别填的是\(mi,mx,smx\),这样最后一项就要和\((m-1)\times (smx-mi)\)\(\max\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,m,len,b[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d%d",&n,&m),len=n*m,i=1;i<=len;++i) scanf("%d",&b[i]);
		sort(b+1,b+len+1); long long ans=1LL*(n-1)*(m-1)*(b[len]-b[1]); if (n<m) swap(n,m);
		printf("%lld\n",ans+1LL*(n-1)*(b[len]-b[1])+1LL*(m-1)*max(b[len]-b[2],b[len-1]-b[1]));
	}
	return 0;
}

C. LuoTianyi and the Show

一眼题,首先不难发现最终所有\(x_i>0\)的人都落座一定不会让答案更劣(注意不是指一开始就让他们坐下来,而是最终可以有位置坐,不过当然指的是去重之后的)

再考虑怎么插空加入\(-1,-2\)的人,显然在缺少一个基准的时候我们是不能很好地安排这两类人的

因此我们枚举第一个人坐的位置\(pos\),然后统计出左边右边分别还有多少个空位,显然这些空位都可以用\(-1,-2\)的人插空坐下去,答案就很好算了

最后也要特判下第一个坐下的是\(-1,-2\)的人的情况,但是我前面写的时候没特判好导致输出答案比\(m\)还大真是脑瘫

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,m,cnt,pos[N],num_A,num_B,x;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d%d",&n,&m),num_A=num_B=cnt=0,i=1;i<=n;++i)
		if (scanf("%d",&x),x==-1) ++num_A; else if (x==-2) ++num_B; else pos[++cnt]=x;
		if (!cnt) { printf("%d\n",min(m,max(num_A,num_B))); continue; }
		sort(pos+1,pos+cnt+1); cnt=unique(pos+1,pos+cnt+1)-pos-1;
		int ans=0; for (i=1;i<=cnt;++i)
		{
			int L=pos[i]-1-(i-1),R=m-pos[i]-(cnt-i);
			ans=max(ans,cnt+min(L,num_A)+min(R,num_B));
		}
		if (num_A&&pos[cnt]!=m) ans=max(ans,1+cnt+min(m-1-cnt,num_A-1));
		if (num_B&&pos[1]!=1) ans=max(ans,1+cnt+min(m-1-cnt,num_B-1));
		printf("%d\n",ans);
	}
	return 0;
}

D1. LuoTianyi and the Floating Islands (Easy Version)

这个D就分开来写吧,毕竟我比赛的时候就花了20min堪堪写了D1,后面都没细想滚回去调C了

首先\(k=1\)的情况很trivial,\(k=2\)的情况就是树上所有路径长度和

不过我比赛的时候脑抽了没想到就是统计边的长度和然后加\(1\),导致后面想正解就不那么容易

做法就是枚举每个点然后考虑各部分经过它的路径数,这个太trivial了就不细讲了

然后\(k=3\)的情况我们稍作讨论发现:

  • 当三个点在一条路径上时,good点一定在中间的那个点处
  • 当三个点不在一条路径上时,good点在三点的公共LCA处,因为此时往任意一个子树方向走或者往上走都一定会让答案变差

不论哪种情况good点都只有一个,因此此时答案亦为\(1\)

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod=1e9+7;
int n,k,x,y,ans,sz[N]; vector <int> v[N];
inline int C2(CI x)
{
	return 1LL*x*(x-1)/2LL%mod;
}
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void DFS(CI now=1,CI fa=0)
{
	sz[now]=1; int ret=C2(n-1); for (int to:v[now])
	if (to!=fa) DFS(to,now),sz[now]+=sz[to],(ret+=mod-C2(sz[to]))%=mod;
	(ret+=mod-C2(n-sz[now]))%=mod; (ans+=ret+n-1)%=mod;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d%d",&n,&k),i=1;i<n;++i)
	scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	if (k==1||k==3) return puts("1"),0;
	return DFS(),printf("%d",1LL*ans*quick_pow(C2(n))%mod),0;
}

D2. LuoTianyi and the Floating Islands (Hard Version)

有了D1的思路其实D2就呼之欲出了

我们考虑随机指定一个点,如果它某个子树(或子树外)有大于等于\(\frac{k}{2}\)个点时往那个方向走一定会让总距离变小

因此换句话说good点的子树内和子树外中关键点个数都得不大于\(\frac{k}{2}\)个点才行,所以我们就会发现\(k\)为奇数时答案一定为\(1\)

否则对于\(k\)为偶数的情况,我们发现此时若某个点为关键点,则其子树内关键点个数应该恰好等于\(\frac{k}{2}\)

如果good的点有多个的话,它们一定形成一条链,因为在上面移动的过程应该始终保持上面所讲的性质

然后如果路径是链的话就很容易了,我们考虑求出每条边被经过的期望次数,最后加上\(1\)就是点被选中的期望个数了

因此最后答案就是:

\[1+\sum_{x=1}^n C_{size_x}^\frac{k}{2}\times C_{n-size_x}^\frac{k}{2} \]

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,mod=1e9+7;
int n,k,x,y,ans,sz[N],fact[N],ifac[N]; vector <int> v[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
	for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
	RI i; for (fact[0]=i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
	for (ifac[n]=quick_pow(fact[n]),i=n-1;~i;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
}
inline int C(CI n,CI m)
{
	if (n<0||m<0||n<m) return 0;
	return 1LL*fact[n]*ifac[m]%mod*ifac[n-m]%mod;
}
inline void DFS(CI now=1,CI fa=0)
{
	sz[now]=1; for (int to:v[now])
	if (to!=fa) DFS(to,now),sz[now]+=sz[to];
	(ans+=1LL*C(sz[now],k/2)*C(n-sz[now],k/2)%mod)%=mod;
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d%d",&n,&k),i=1;i<n;++i)
	scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	if (k&1) return puts("1"),0; init(n); DFS();
	return printf("%d",(1+1LL*ans*quick_pow(C(n,k))%mod)%mod),0;
}

E. LuoTianyi and XOR-Tree

这题本来只有个朦胧的想法的,然后昨天后面比赛结束看到学长们在群里聊到这题的关键启发式合并求众数就会了

首先我们很容易有个暴力的想法,设\(f_{x,y}\)表示将\(x\)子树内所有叶子节点到根的路径的权值异或和变成\(y\)的最小代价

但是这个状态优化的空间很大,因为我们发现有意义的取值其实只有两种,如果\(f_{x,y_1}>f_{x,y_2}+1\),那我们可以直接把\(x\)的权值换成\(y_1\oplus y_2\),这样答案至多为\(f_{x,y_2}+1\)

因此换句话说,我们只要记录下\(f_x\)表示将\(x\)子树内所有叶子节点到根的路径的权值异或和变成相同的最小代价,然后用集合\(S_x\)表示能达成最小代价的\(y\)的集合

然后对于叶子节点\(u\)有初值\(f_u=0,S_u=pfx_u\),其中\(pfx_u\)表示从根到\(u\)路径上权值的异或和

有了这个之后其实转移就呼之欲出了,对于某个点\(u\),我们一定是找出它的子节点\(v\)的集合\(S_v\)中的众数来作为\(S_u\)的值

然后合并的过程显然直接启发式合并即可,最后可以暴力地遍历集合删去所有非众数的值,这样均摊的复杂度是没问题的

(Upt on 6/6:上面漏说了一点,只有当众数出现次数大于\(1\)的时候才去遍历删除,否则复杂度会假,代码中是注意了这一点的)

代码也是非常好写,总复杂度\(O(n\log^2 n)\)

#include<cstdio>
#include<iostream>
#include<vector>
#include<map>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int n,a[N],x,y,ans; vector <int> v[N]; map <int,int> rst[N];
inline void DFS(CI now=1,CI fa=0)
{
	a[now]^=a[fa]; if (now!=1&&v[now].size()==1) return (void)(++ans,rst[now][a[now]]=1);
	int mx=0; for (int to:v[now]) if (to!=fa)
	{
		DFS(to,now); if (rst[to].size()>rst[now].size()) swap(rst[to],rst[now]);
		for (auto [x,y]:rst[to]) mx=max(mx,rst[now][x]+=y);
	}
	if (mx>1)
	{
		ans=ans-mx+1; for (auto it=rst[now].begin();it!=rst[now].end();)
		if (it->second!=mx) it=rst[now].erase(it); else it->second=1,++it;
	}
}
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
	for (i=1;i<n;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
	if (DFS(),rst[1].count(0)) --ans; return printf("%d",ans),0;
}

Postscript

要期中考了但我还是一点没复习,忍不住不写题啊人菜瘾还大

再真不能写了,等期中考完再尽情补之前的CF和Atcoder和蓝桥杯吧,还有剩下的图论专题的神仙题qwq

posted @ 2023-05-09 17:20  空気力学の詩  阅读(129)  评论(0编辑  收藏  举报