Codeforces Round 1286 简要题解

A. Garland

B. Numbers on Tree

C2. Madhouse (Hard version)

D. LCC

注意到如果存在相遇点对,第一次相遇的一定是相邻的点。
那么考虑枚举所有的相邻点对和它们的初始方向,可以得到 O ( n ) \mathcal O(n) O(n)组可能的第一次相遇时间。给它们排序后,只要对每一组算出恰好是该时间相遇的概率即可,差分一下变为给定 O ( n ) \mathcal O(n) O(n)个时刻 t i t_i ti,算出前 t i t_i ti时刻都没有相遇的概率。
对于一个固定的时刻 t t t计算的话,相当于对每个相邻点对 ( i , i + 1 ) (i,i+1) (i,i+1),给定它们之间可能的状态( i i i的方向与 i − 1 i-1 i1的方向间的关系),可以写成一个 2 ∗ 2 2\ast2 22的矩阵,那么从左到右做一个矩阵乘法即可求解。现在要对 O ( n ) \mathcal O(n) O(n)个时刻求,可以排序后对时刻从大到小扫描线,每次加入若干个相邻点对间可能的状态,也即会修改若干个矩阵,那么拿线段树维护矩阵乘法即可。
时间复杂度为 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

#include <bits/stdc++.h>
#define MOD 998244353
#define last last2

using namespace std;

typedef long long ll;

ll pow_mod(ll x,int k) {
  ll ans=1;
  while (k) {
  	if (k&1) ans=ans*x%MOD;
  	x=x*x%MOD;
  	k>>=1;
  }
  return ans;
}

struct Matrix {
  int num[2][2],n,m;
  Matrix() {}
  Matrix(int a,int b):n(a),m(b) {memset(num,0,sizeof(num));}
};

Matrix operator * (Matrix a,Matrix b) {
  Matrix c(a.n,b.m);
  for(int i=0;i<c.n;i++)
    for(int j=0;j<c.m;j++)
      for(int k=0;k<a.m;k++) 
	    c.num[i][j]=(c.num[i][j]+(ll)a.num[i][k]*b.num[k][j])%MOD;
  return c;
}

Matrix fir[100005];

namespace SGT {

Matrix mulv[400000];

void build(int l,int r,int o) {
  if (l==r) mulv[o]=fir[l];
  else {
  	int m=((l+r)>>1);
  	build(l,m,o*2);
  	build(m+1,r,o*2+1);
  	mulv[o]=mulv[o*2+1]*mulv[o*2];
  }
}

void update(int l,int r,int o,int p) {
  if (l==r) mulv[o]=fir[l];
  else {
  	int m=((l+r)>>1);
  	if (m>=p) update(l,m,o*2,p);
  	else update(m+1,r,o*2+1,p);
  	mulv[o]=mulv[o*2+1]*mulv[o*2];
  }
}

Matrix query() {
  return mulv[1];
}

}

const ll inv=pow_mod(100,MOD-2);

ll num[100005],pos[100005],val[100005];
bool f[100005][4];

void getMatrix(int x,int n) {
  fir[x]=Matrix(2,2);
  if (f[x][0]) fir[x].num[1][1]=(1LL-num[x]+MOD)%MOD;
  if (f[x][1]) fir[x].num[0][0]=num[x];
  if (f[x][2]) fir[x].num[1][0]=(1LL-num[x]+MOD)%MOD;
  if (f[x][3]) fir[x].num[0][1]=num[x];
}

ll query() {
  Matrix t=SGT::query();
  Matrix v(2,1);
  v.num[0][0]=num[1];
  v.num[1][0]=(1LL-num[1]+MOD)%MOD;
  v=t*v;
  return (v.num[0][0]+v.num[1][0])%MOD;
}

struct Frac {
  ll x,y;
  int kind,id;
  Frac() {}
  Frac(ll a,ll b,int c,int d):x(a),y(b),kind(c),id(d) {}
  bool operator < (const Frac & b) const {return x*b.y<y*b.x;}
};

Frac a[400005];

int main() {
  int n;
  scanf("%d",&n);
  for(int i=1;i<=n;i++) {
  	scanf("%lld%lld%lld",&pos[i],&val[i],&num[i]);
  	num[i]=num[i]*inv%MOD;
  }
  if (n==1) {
  	puts("0");
  	return 0;
  }
  int cnt=0;
  for(int i=2;i<=n;i++) {
  	if (val[i-1]<val[i]) a[++cnt]=Frac(pos[i]-pos[i-1],val[i]-val[i-1],0,i);
	else f[i][0]=1;
	if (val[i]<val[i-1]) a[++cnt]=Frac(pos[i]-pos[i-1],val[i-1]-val[i],1,i);
	else f[i][1]=1;
	a[++cnt]=Frac(pos[i]-pos[i-1],val[i]+val[i-1],2,i);
  	f[i][3]=1;
  	getMatrix(i,n);
  }
  sort(a+1,a+cnt+1);
  SGT::build(2,n,1);
  ll ans=0,last=query();
  for(int i=cnt;i>0;i--) {
  	f[a[i].id][a[i].kind]=1;
  	getMatrix(a[i].id,n);
  	SGT::update(2,n,1,a[i].id);
  	ll v=query();
  	ans=(ans+(v-last+MOD)*a[i].x%MOD*pow_mod(a[i].y,MOD-2))%MOD;
  	last=v;
  }
  printf("%lld\n",ans);
  return 0;
}

E. Fedya the Potter Strikes Back

考虑每次加入 c i c_i ci w i w_i wi,求出所有右端点为 i i i的区间的贡献。
注意到对于一个固定的左端点 l l l,满足它是suspiciousness的右端点 r r r形成一个 ≥ l \geq l l的前缀区间。那么我们可以维护所有当前suspiciousness的区间的左端点的集合,支持几种操作:插入一个元素,删除一个元素,对于集合中元素的权值取 min ⁡ \min min,查询集合中元素和。显然,这可以通过维护权值的单调栈,及用树状数组维护支持查询区间元素来完成。这样,我们的关键问题在于每次删去所有当前集合中,满足 [ l , i ] [l,i] [l,i]suspiciousness的左端点 l l l
S [ 1 , i ] S[1,i] S[1,i]的最长border长度为 f a i l i fail_i faili,这可以用KMP算法求出。那么每次集合中应保留的元素即为 1 , i − f a i l i + 1 , i − f a i l f a i l i + 1 , ⋯ 1,i-fail_i+1,i-fail_{fail_i}+1,\cdots 1,ifaili+1,ifailfaili+1,。考虑求出所有上次存在,这次不存在的 l l l。根据KMP的过程,我们要不断跳 i − 1 i-1 i1 f a i l fail fail链直至重新匹配上,那么跳过的 l e n len len对应的 i − l e n i-len ilen就应该被删掉,且接下来要删去的长度即为 f a i l i fail_i faili要删去的长度。可以发现删去的长度也形成了一个树形结构,那么每次直接暴力跳到根即可得到所有应当被删去的长度。
时间复杂度为 O ( n log ⁡ n ) \mathcal O(n\log n) O(nlogn)

#include <bits/stdc++.h>
#define lowbit(x) (x&-x)
#define FR first
#define SE second

using namespace std;

typedef long long ll;
typedef __int128 lll;
typedef pair<int,int> pr;

namespace BIT {

int sumv[600005];

void add(int x,int n,int num) {
  for(;x<=n;x+=lowbit(x)) sumv[x]+=num;
}

int sum(int x) {
  int s=0;
  for(;x;x-=lowbit(x)) s+=sumv[x];
  return s;
}

int sum(int l,int r) {
  return sum(r)-sum(l-1);
}

}

char str[600005];

int fail[600005],ed[600005]; 
int fa[600005],len[600005],cur[600005];
int num[600005],st[600005];

void output(lll x) {
  if (!x) putchar('0');
  char str[30];
  int cnt=0;
  for(;x;x/=10) str[++cnt]=x%10;
  for(;cnt;cnt--) putchar(str[cnt]+'0');
}

int main() {
  int n;
  scanf("%d",&n);
  int top=0,sz=0;
  ll s=0;
  lll ans=0;
  for(int i=1;i<=n;i++) {
  	scanf("%s%d",str+i,&num[i]);
  	str[i]='a'+(str[i]-'a'+ans)%26;
  	num[i]^=(ans&((1<<30)-1));
  	int cnt=0,x=fail[i-1];
  	while (x&&str[x+1]!=str[i]) {
  		cur[++cnt]=x;
  		x=fail[x];
	  }
	if (i>1&&str[x+1]==str[i]) x++;
	fail[i]=x;
	ed[i]=ed[x];
	for(int j=cnt;j>0;j--) {
		len[++sz]=cur[j];
		fa[sz]=ed[i];
		ed[i]=sz;
	}
	for(int j=ed[i];j;j=fa[j]) {
		int u=i-len[j],t=st[lower_bound(st+1,st+top+1,u)-st];
		BIT::add(u,n,-1);
		s-=num[t];
	}
    while (top&&num[st[top]]>=num[i]) {
    	s-=(ll)(num[st[top]]-num[i])*BIT::sum(st[top-1]+1,st[top]);
    	top--;
	}
	st[++top]=i;
	if (str[i]==str[1]) {
		BIT::add(i,n,1);
		s+=num[i];
	}
	ans+=s;
	output(ans);
	printf("\n");
  }
  return 0;
} 

F. Harry The Potter

考虑在两个进行了 2 2 2操作间的下标 ( i , j ) (i,j) (i,j)间连一条边,那么对于每个弱连通块 S S S,至少需要进行 ∣ S ∣ − 1 |S|-1 S1次操作才能全变为 0 0 0。那么我们会选择尽量多的不交子集,使得每个子集 S S S可以用 ∣ S ∣ − 1 |S|-1 S1次操作全变为 0 0 0,若最多能选出 k k k个,答案即为 n − k n-k nk
考虑先预处理出每个集合 S S S能否用 ∣ S ∣ − 1 |S|-1 S1次操作全变为0。对于 ∣ S ∣ = 1 |S|=1 S=1的情况,显然当且仅当里面的元素为 0 0 0。考虑 ∣ S ∣ > 1 |S|>1 S>1的情况(只考虑其中不包含 0 0 0的,这显然不会变劣),注意到如果可行的话操作间形成了一棵树。对树按深度黑白染色的话可以得到两个非空子集 S 0 S_0 S0 S 1 S_1 S1,这里 a b s ( ∑ T ∈ S 0 T − ∑ T ∈ S 1 T ) < ∣ S ∣ abs(\sum_{T\in S_0}T-\sum_{T\in S_1}T)<|S| abs(TS0TTS1T)<S,且要求 ∑ T ∈ S T \sum_{T\in S}T TST ∣ S ∣ |S| S奇偶性相异。
事实上,对于一个集合 S S S(不包含 0 0 0),它能用 ∣ S ∣ − 1 |S|-1 S1次操作全变为 0 0 0的充要条件即为 ∑ T ∈ S T \sum_{T\in S}T TST ∣ S ∣ |S| S奇偶性相异,且能够划分为两个非空子集 S 0 ′ S_0' S0 S 1 ′ S_1' S1,使得 a b s ( ∑ T ∈ S 0 T − ∑ T ∈ S 1 T ) < ∣ S ∣ abs(\sum_{T\in S_0}T-\sum_{T\in S_1}T)<|S| abs(TS0TTS1T)<S。充分性可以构造得出:考虑先给 S 0 ′ S_0' S0 S 1 ′ S_1' S1分配 2 2 2操作的额外的 − 1 -1 1,使得分配完后两边总和相等(显然可以完成),不妨设 ∣ S 0 ′ ∣ ≤ ∣ S 1 ′ ∣ |S_0'|\leq |S_1'| S0S1,若 ∣ S 0 ′ ∣ = 1 |S_0'|=1 S0=1显然可行,否则取出一个 x ∈ S 0 ′ x\in S_0' xS0 y ∈ S 1 ′ y\in S_1' yS1,进行一次 2 2 2操作,将 y y y操作为 0 0 0,至于 − 1 -1 1的方向取决于哪边还剩余 − 1 -1 1,操作完后 S 1 ′ S_1' S1中删去了一个元素,可以递归构造。
这样,我们可以对于每个 S S S,枚举它的所有非空子集 T T T判定,这样是 O ( 3 n ) \mathcal O(3^n) O(3n)的,不太能通过。更优秀的做法是取一个块大小 B ≥ ⌈ n 2 ⌉ B\geq \lceil \frac{n}{2}\rceil B2n,对于前 B B B个数和 n − B n-B nB个数内部的所有不交集合对 ( p , q ) (p,q) (p,q) p ∩ q = ∅ p \cap q=\empty pq=),按 ∑ T ∈ p T − ∑ T ∈ q q \sum_{T\in p}T-\sum_{T\in q}q TpTTqq排序,代表 p p p q q q中的元素分到两侧。注意到这里的排序是对子集排序,可以类似倍增的思想,每次加入一个元素,维护当前的排序结果,这样按这个元素是否取及取的话分到哪一侧可以得到三个单调不降队列,对三个队列归并即可线性排序,复杂度为 O ( 3 B ) \mathcal O(3^B) O(3B)
考虑对个数为 n − B n-B nB的一侧从小到大暴力枚举所有 ( p , q ) (p,q) (p,q)的对,在 B B B的一侧双指针,对每个 p ∪ q p\cup q pq维护当前权值和不超过 n − B n-B nB一侧当前枚举的和的最大总和(这样可以尽可能接近),这样每扫一个元素直接枚举 B B B一侧的所有可能的 p ∪ q p\cup q pq(共 2 B 2^B 2B种)更新即可,还需要反过来再扫描一次处理 B B B一侧和超过 n − B n-B nB一侧的情况,这样复杂度为 O ( 2 B ⋅ 3 n − B ) \mathcal O(2^B\cdot 3^{n-B}) O(2B3nB)。注意这里要求左边选的 ( p 1 , q 1 ) (p_1,q_1) (p1,q1)与右边选的 ( p 2 , q 2 ) (p_2,q_2) (p2,q2)满足 p 1 ∪ q 2 ≠ ∅ p_1\cup q_2\neq \empty p1q2= q 1 ∪ p 2 ≠ ∅ q_1\cup p_2 \neq \empty q1p2=,我的实现是在上面做的过程中 B B B的一侧只用 p ≠ ∅ p\neq \empty p= q ≠ ∅ q\neq \empty q=的对 ( p , q ) (p,q) (p,q)更新,对于至少一个为空的暴力枚举 n − B n-B nB一侧的算,这样复杂度不变。
这样对每个集合判定的总复杂度为 O ( 3 B + 2 B ⋅ 3 n − B ) \mathcal O(3^B+2^B\cdot 3^{n-B}) O(3B+2B3nB),简单分析一下可以知道当 B = log ⁡ 4 3 ⋅ n B=\log_4{3}\cdot n B=log43n时取到最优复杂度 O ( ( 3 log ⁡ 4 3 ) n ) \mathcal O((3^{\log_{4}{3}})^n) O((3log43)n),这比官方题解中的 O ( ( 1 + 2 ) n ) \mathcal O((1+\sqrt 2)^n) O((1+2 )n)更加优秀。
然后问题还剩下求出最多能选出多少不相交的,可以用 ∣ S ∣ − 1 |S|-1 S1操作全部变为 0 0 0的集合 S S S。考虑一个集合幂级数 A A A,其中 [ x S ] A [x^S]A [xS]A当且仅当 S S S非空且可以用 ∣ S ∣ − 1 |S|-1 S1操作全部变为 0 0 0时为 1 1 1,其他时刻为 0 0 0。那么能选出至少 k k k个当且仅当 A k ≠ 0 A^k\neq 0 Ak=0(这里乘法定义为子集不交并卷积)。直接暴力枚举 k k k的话是 O ( 2 n ⋅ n 3 ) \mathcal O(2^n\cdot n^3) O(2nn3)的,用倍增后二分的思想可以优化到 O ( 2 n ⋅ n 2 log ⁡ n ) \mathcal O(2^n\cdot n^2\log n) O(2nn2logn)。实际上虽然这部分渐进复杂度较低,但却是主要的时间瓶颈。
时间复杂度为 O ( ( 3 log ⁡ 4 3 ) n + 2 n ⋅ n 2 log ⁡ n ) \mathcal O((3^{\log_{4}{3}})^n+2^n\cdot n^2\log n) O((3log43)n+2nn2logn)

#include <bits/stdc++.h>
#define FR first
#define SE second
#define inf 0x3f3f3f3f3f3f3f3fLL

using namespace std;

typedef long long ll;
typedef unsigned int ui;
typedef pair<int,int> pr;

ll num[25],sum[1<<20];
int bit[1<<20];
ui C[25][25];

void pre(int n) {
  for(int i=1;i<(1<<n);i++) {
    bit[i]=bit[i>>1]+(i&1);
    for(int j=1;j<=n;j++)
      if ((i>>(j-1))&1) sum[i]+=num[j];
  }
  for(int i=0;i<=n;i++) C[i][0]=1;
  for(int i=1;i<=n;i++)
    for(int j=1;j<=i;j++) C[i][j]=C[i-1][j-1]+C[i-1][j];
}

inline ll getsum(pr x) {
  return sum[x.FR]-sum[x.SE];
}

int getsub(int *p,pr *q,int n) {
  static pr q1[5000000],q2[5000000],q3[5000000];
  int sz=0;
  q[++sz]=pr(0,0);
  for(int i=1;i<=n;i++) {
  	int x=p[i],v=(1<<(x-1));
  	for(int j=1;j<=sz;j++) {
  		q1[j]=q[j];
  		q2[j]=pr(q[j].FR|v,q[j].SE);
  		q3[j]=pr(q[j].FR,q[j].SE|v);
	  }
	int r1=1,r2=1,r3=1,nsz=0;
	while (r1<=sz||r2<=sz||r3<=sz) {
	  nsz++;
	  if (r1<=sz&&(r2>sz||getsum(q1[r1])<getsum(q2[r2]))&&(r3>sz||getsum(q1[r1])<getsum(q3[r3])))
	    q[nsz]=q1[r1++];
	  else if (r2<=sz&&(r3>sz||getsum(q2[r2])<getsum(q3[r3])))
	    q[nsz]=q2[r2++];
	  else q[nsz]=q3[r3++];
    }
    sz=nsz;
  }
  return sz;
}

pr a[100000],b[5000000];
bool f[1<<20];

ll g[1<<20];

void calc(int n,int m) {
  int cur[25];
  for(int i=1;i<=m;i++) cur[i]=i;
  int sz1=getsub(cur,a,m);
  int k=n-m;
  for(int i=1;i<=k;i++) cur[i]=m+i;
  int sz2=getsub(cur,b,k);
  for(int i=0;i<(1<<k);i++) g[i]=-inf;
  int rx=1;
  for(int i=1;i<=sz1;i++) {
  	ll v=getsum(a[i]);
  	int st=(a[i].FR^a[i].SE);
  	if (a[i].FR&&a[i].SE&&abs(v)<bit[st]) f[st]=1;
  	while (rx<=sz2&&getsum(b[rx])<=v) {
  		if (b[rx].FR&&b[rx].SE) g[(b[rx].FR^b[rx].SE)>>m]=getsum(b[rx]);
  		rx++;
	  }
	for(int j=0;j<(1<<k);j++)
	  if (v-g[j]<bit[st^(j<<m)]) f[st^(j<<m)]=1;
  }
  for(int i=0;i<(1<<k);i++) g[i]=inf;
  int lx=sz2;
  for(int i=sz1;i>0;i--) {
  	ll v=getsum(a[i]);
  	int st=(a[i].FR^a[i].SE);
  	while (lx&&getsum(b[lx])>=v) {
  		if (b[lx].FR&&b[lx].SE) g[(b[lx].FR^b[lx].SE)>>m]=getsum(b[lx]);
  		lx--;
	  }
	for(int j=0;j<(1<<k);j++)
	  if (g[j]-v<bit[st^(j<<m)]) f[st^(j<<m)]=1;
  }
  for(int i=1;i<=sz2;i++) {
  	ll v=getsum(b[i]);
  	int st=(b[i].FR^b[i].SE);
  	if (b[i].FR&&b[i].SE&&abs(v)<bit[st]) f[st]=1;
  	if (b[i].FR&&!b[i].SE) {
  		for(int j=1;j<=sz1;j++)
  		  if (a[j].FR&&abs(v-getsum(a[j]))<bit[st^a[j].FR^a[j].SE]) 
			f[st^a[j].FR^a[j].SE]=1; 
	  }
	if (!b[i].FR&&b[i].SE) {
		for(int j=1;j<=sz1;j++)
		  if (a[j].SE&&abs(v-getsum(a[j]))<bit[st^a[j].FR^a[j].SE])
		    f[st^a[j].FR^a[j].SE]=1;
	}
  }
  for(int i=1;i<=n;i++)
    if (!num[i]) f[1<<(i-1)]=1;
  for(int i=0;i<(1<<n);i++)
    if (!((sum[i]^bit[i])&1)) f[i]=0;
}

void fwt(ui *p,int len) {
  for(int i=1;i<len;i<<=1)
    for(int j=0;j<len;j++)
      if (j&i) p[j]+=p[j^i];
}

ui p[5][21][1<<20],q[21][1<<20];

int solve(int n) {
  for(int i=0;i<(1<<n);i++)
    if (f[i]) p[0][bit[i]][i]=1;
  for(int i=0;i<=n;i++) fwt(p[0][i],1<<n);
  for(int i=1;i<5;i++)
  	for(int j=0;j<(1<<n);j++)
  	  for(int k=0;k<=n;k++)
  	    for(int l=0;l+k<=n;l++) p[i][k+l][j]+=p[i-1][k][j]*p[i-1][l][j];
  for(int i=0;i<(1<<n);i++)
    for(int j=0;j<=n;j++) q[j][i]=C[bit[i]][j];
  int ans=0;
  for(int i=4;i>=0;i--) {
  	ui sum=0;
  	for(int j=0;j<(1<<n);j++) {
  		ui s=0;
  		for(int k=0;k<=n;k++) s+=q[k][j]*p[i][n-k][j];
  		sum+=((bit[j]&1)?-1:1)*s;
	  }
	if (sum) {
		ans^=(1<<i);
		for(int j=0;j<(1<<n);j++)
		  for(int k=n;k>=0;k--) {
		    for(int l=1;l+k<=n;l++) q[k+l][j]+=q[k][j]*p[i][l][j];
		    q[k][j]=0;
		  }
	}
  }
  return n-ans;
}

int main() {
  int n;
  scanf("%d",&n);
  for(int i=1;i<=n;i++) scanf("%lld",&num[i]);
  pre(n);
  int m=min(n>>1,6);
  calc(n,m);
  printf("%d\n",solve(n));
  return 0;
}
/*
4
2 3 10 9
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值