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;
}
很久以前的奇怪但现在依旧成立的签名
attack is our red sun $$\color{red}{\boxed{\color{red}{attack\ is\ our\ red\ sun}}}$$ ------------------------------------------------------------------------------------------------------------------------