博客园 首页 私信博主 显示目录 隐藏目录 管理 动画

10.14 牛客提高集训营5


2018.10.14 牛客提高集训营5

比赛链接

A 同余方程(思路 位运算)

题目链接

  首先容斥一下,\(Ans=(r_1,r_2)-(r_1,l_2-1)-(l_1-1,r_2)+(l_1-1,l_2-1)\)\((x,y)\)表示\(l_1=l_2=0,\ r_1=x,\ r_2=y\)时的答案。
  因为是异或,我们按位考虑。
  对于\((r_1,r_2)\),考虑这种特殊情况:\(r_1=2^n-1,\ r_2=2^m-1\)(假设\(n\leq m\))。
  从\([0,2^n)\)中任取一个\(x\)\([0,2^m)\)中任取一个\(y\),那么\(x^{\wedge}y\)还在\([0,2^n)\)内。
  对于任意一个\(z\in[0,2^n)\),它的前\(n-m\)位是由\(x\)确定的,后\(m\)位由\(x,y\)共同确定。那么对于\(x\)的后\(m\)位的\(2^m\)种取值,\(y\)都有唯一的值使得\(x^{\wedge}y=z\)。所以我们算一下\([0,2^n)\)中有多少个\(P\)的倍数,再乘上\(2^m\)就行了。
  考虑更一般的情况。如果枚举\(r\)的某一位\(1\)\(0\),那么后面所有位可以随意确定。比如\(r=(1011)_2\),可以将它拆成\(0???,100?,1010\)三个区间(区间左端点为\(0\)),这样就对应三个形如\(a\times2^n+[0,2^n)\)的区间(\(a\)就是枚举的\(1\)之前的前缀)。当然\(1011\)本身不会计算到,让\(r+1\)就行了。
  这样我们就把任意区间拆成了\(O(\log n)\)个前缀固定,后面随便放的区间。
  考虑对于\(r_1,r_2\),设拆出的区间分别是\(a_1\times2^n+[0,2^n)\)\(a_2\times2^m+[0,2^m)\),且\(n\geq m\)。那么从这两个区间中任取两个数异或,结果的后\(n\)位可以任意定,剩余高位由\(a_1\times2^n\ ^{\wedge}\ a_2\times2^m\)确定。异或的结果是一个区间,区间中任意数同样有\(2^m\)种选择得到。算一下区间中有多少个数整除\(P\)就行了。(因为有个\([0,2^n)\)的数,所以区间下界的后\(n\)位为全\(0\),上界就全\(1\),其余高位由\(a_1\times2^n\ ^{\wedge}\ a_2\times2^m\)决定)。

#include <cstdio>
#include <cctype>
#include <algorithm>
#define gc() getchar()
#define BIT 59
#define mod 998244353
typedef long long LL;

inline LL read()
{
	LL now=0;register char c=gc();
	for(;!isdigit(c);c=gc());
	for(;isdigit(c);now=now*10+c-'0',c=gc());
	return now;
}
//LL Calc(LL a,LL b,LL m)//两种写法 
//{
//	LL ans=0;
//	for(int i=0; i<=BIT; ++i)
//		if(a>>i&1)
//			for(int j=0; j<=BIT; ++j)
//				if(b>>j&1)
//				{
//					int mx=std::max(i,j),mn=std::min(i,j);
//					LL L=((a^1ll<<i)^(b^1ll<<j))&~((1ll<<mx)-1),R=L+(1ll<<mx)-1,tot=(R/m-L/m+(L%m==0))%mod;//0
//					(ans+=(1ll<<mn)%mod*tot)%=mod;
//				}
//	return ans;
//}
LL Calc(LL a,LL b,LL m)
{
	LL ans=0;
	for(LL i=a; i; i&=i-1)
		for(LL j=b; j; j&=j-1)
		{
			LL mx=std::max(i&-i,j&-j),mn=std::min(i&-i,j&-j);
			LL L=((i&i-1)^(j&j-1))&~(mx-1),R=L+mx-1,tot=(R/m-L/m+(L%m==0))%mod;
			(ans+=mn%mod*tot)%=mod;
		}
	return ans;
}

int main()
{
	LL l1=read(),r1=read(),l2=read(),r2=read(),m=read();
	printf("%lld\n",(Calc(r1+1,r2+1,m)+mod-Calc(r1+1,l2,m)+mod-Calc(l1,r2+1,m)+Calc(l1,l2,m))%mod);
	return 0;
}

B 旅游(Kruskal 贪心)

题目链接

假设让每条边经过\(a_i\)次,那么我们要最小化\(\sum_{i=1}^m 2^i*a_i\)
怎么判断当前\(a_i\)的选择方案是否合法呢?也就是判是否存在欧拉回路。那么令\(dgr_i\)\(i\)点度数,那么若满足\(\forall i,dgr_i\%2=0\),方案合法。
好了以上是废话。我们先求最小生成树。
因为树边权值都小于非树边,且\(2^k>\sum_{i=0}^{k-1}2^i\),所以如果要走一条权值大的非树边两次,完全可以多绕一圈树边代替,且同样只影响路径端点的两个点的度数奇偶性。
我们还可以发现,\(1\leq a_i\leq2\)。对生成树DFS一遍,根据点的度数确定一下\(x\)需要走到\(fa[x]\)的边多少次就行了。
\(1\)节点没有到父节点的边,但因为所有点度数和为偶数,所有它的度数也一定是偶数。

//107ms	16476KB
#include <cstdio>
#include <cctype>
#include <algorithm>
//#define gc() getchar()
#define MAXIN 300000
#define gc() (SS==TT&&(TT=(SS=IN)+fread(IN,1,MAXIN,stdin),SS==TT)?EOF:*SS++)
#define mod 998244353
typedef long long LL;
const int N=5e5+5;

int pw[N],Enum,H[N],to[N<<1],nxt[N<<1],len[N<<1],fa[N],dgr[N];
LL Ans;
char IN[MAXIN],*SS=IN,*TT=IN;

inline int read()
{
	int now=0;register char c=gc();
	for(;!isdigit(c);c=gc());
	for(;isdigit(c);now=now*10+c-'0',c=gc());
	return now;
}
inline void AE(int u,int v,int w)
{
	to[++Enum]=v, nxt[Enum]=H[u], H[u]=Enum, len[Enum]=w;
	to[++Enum]=u, nxt[Enum]=H[v], H[v]=Enum, len[Enum]=w;
}
int Find(int x)
{
	return x==fa[x]?x:fa[x]=Find(fa[x]);
}
void DFS(int x,int fa)
{
	for(int i=H[x],v; i; i=nxt[i])
		if((v=to[i])!=fa)
		{
			DFS(v,x);
			if(dgr[v]) dgr[x]^=1,Ans+=pw[len[i]];//异或比++快好多啊(比较n大)
		}
}

int main()
{
	int n=read(),m=read();
	for(int i=1; i<=n; ++i) fa[i]=i;
	pw[0]=1;
	for(int i=1; i<=m; ++i) pw[i]=pw[i-1]<<1,pw[i]>=mod&&(pw[i]-=mod);
	Ans=0;
	for(int i=1,u,v,r1,r2; i<=m; ++i)
	{
		dgr[u=read()]^=1, dgr[v=read()]^=1, Ans+=pw[i];
		if((r1=Find(u))==(r2=Find(v))) continue;
		fa[r1]=r2, AE(u,v,i);
	}
	DFS(1,1);
	printf("%lld\n",Ans%mod);

	return 0;
}

C 串串(思路 组合)

题目链接

  首先串\(T\)\(\binom{c+d}{c}\)种可能。
  先生成串\(S\)再判断\(T\)\(S\)的子序列很麻烦。我们可以往\(T\)中加入\(a-c\)\(0\)\(b-d\)\(1\)来得到\(S\)。那么所求即为有多少种加入\(0/1\)的方案由\(T\)得到\(S\)
  显然多余的\(0/1\)是哪都可以加的。但是直接这么算会算重。比如000加一个0,有\(4\)个位置可选,但实际方案数只有1种。
考虑比如0001要加入一个0,只要钦定0只能放在那个1前面,就不会算重了。再比如00011001,每个1前(且只在\(1\)前)可以放若干个0,方案都是不同的。
  所以实际上\(0\)可以放的位置只有\(d\)个(即\(1\)的个数)(当然一个位置可以放多个\(0\))。
  另外\(T\)的末尾是可以任意放\(0/1\)的。那我们枚举\(T\)的末尾放多少个\(0/1\),方案数可以用组合数算。同样其余的\(0/1\)只能放在前面确定的一些位置,方案数同样可以用组合数\(O(1)\)计算(就是\(n\)个球放入\(m\)个盒子,允许盒子为空,即将\(n\)个球分成\(m\)组)。
  设在前面放\(x\)\(0\)\(y\)\(1\),则方案数为:$$\binom{x+d-1}{d-1}\times\binom{y+c-1}{c-1}\times\binom{a-c-x+b-d-y}{a-c-x}$$

  因为有\(c-1,d-1\),所以要特判\(c,d\)\(0\)的情况。而且这样算只与\(0/1\)数量有关,最后可以直接乘\(\binom{c+d}{c}\)

#include <cstdio>
#include <cctype>
#include <cstring>
#include <algorithm>
#define gc() getchar()
#define mod 1000000007
typedef long long LL;
const int N=4005;

int fac[N],ifac[N];

inline int read()
{
	int now=0;register char c=gc();
	for(;!isdigit(c);c=gc());
	for(;isdigit(c);now=now*10+c-'0',c=gc());
	return now;
}
inline int FP(int x,int k)
{
	int t=1;
	for(; k; k>>=1,x=1ll*x*x%mod)
		if(k&1) t=1ll*t*x%mod;
	return t;
}
#define C(n,m) (1ll*fac[n]*ifac[m]%mod*ifac[(n)-(m)]%mod)

int main()
{
	const int lim=4000;
	fac[0]=fac[1]=1;
	for(int i=2; i<=lim; ++i) fac[i]=1ll*fac[i-1]*i%mod;
	ifac[lim]=FP(fac[lim],mod-2);
	for(int i=lim; i; --i) ifac[i-1]=1ll*ifac[i]*i%mod;

	int a=read(),b=read(),c=read(),d=read(); a-=c,b-=d;
	LL ans=0;
	if(!c)
		for(int x=0; x<=a; ++x)
			ans+=1ll*C(x+d-1,d-1)%mod*C(a-x+b,a-x)%mod;
	else if(!d)
		for(int y=0; y<=b; ++y)
			ans+=1ll*C(y+c-1,c-1)%mod*C(a+b-y,a)%mod;
	else
		for(int x=0; x<=a; ++x)
			for(int y=0; y<=b; ++y)
				ans+=1ll*C(x+d-1,d-1)*C(y+c-1,c-1)%mod*C(a-x+b-y,a-x)%mod;
	printf("%lld\n",ans%mod*C(c+d,c)%mod);

	return 0;
}
posted @ 2018-10-16 07:48  SovietPower  阅读(118)  评论(0编辑  收藏  举报