Codeforces Round #848 (Div. 2)(持续更新)

Preface

这场打的可以说发挥了水平但没有完全发挥的说

ABC都是一眼一遍过,结果D是我最怕的期望题,大概推了个DP的式子但感觉成环了初始条件又不够(flag)就弃了

本来当时看E只有十几个人过的,抱着随便看看的心态看了眼题面发现是个套路题

结果30minRush完代码后一直连着T了好多发,直到比赛结束前两分钟才卡常卡过去

嘛不过不管怎么说应该不会掉分的说(最近两场的分怎么被CF给吃掉了,还没更新上去就难绷)


A. Flip Flop Sum

SB题,直接按题意枚举即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,a[N],pfx[N],suf[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]);
		for (pfx[0]=0,i=1;i<=n;++i) pfx[i]=pfx[i-1]+a[i];
		for (suf[n+1]=0,i=n;i>=1;--i) suf[i]=suf[i+1]+a[i];
		int ans=-1e9; for (i=1;i<n;++i) ans=max(ans,pfx[i-1]+suf[i+2]-a[i]-a[i+1]);
		printf("%d\n",ans);
	}
	return 0;
}

B. The Forbidden Permutation

SB题,话说这题题面写的很难懂赛后被好多人喷来着

不难发现由于只要有一个位置不合法即可,我们先特判掉答案为\(0\)的情况

然后考虑直接枚举每个位置,分别考虑破坏不等式两边所需要的最小步数即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005,INF=1e9;
int t,n,m,d,p[N],pos[N],a[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%d",&n,&m,&d),i=1;i<=n;++i)
		scanf("%d",&p[i]),pos[p[i]]=i;
		for (i=1;i<=m;++i) scanf("%d",&a[i]);
		bool flag=0; int ans=INF; for (i=1;i<m;++i)
		{
			int x=pos[a[i]],y=pos[a[i+1]];
			if (y<x||y>x+d) { flag=1; break; }
			ans=min(ans,y-x); if (n-1>d) ans=min(ans,d+1-(y-x));			
		}
		if (flag) puts("0"); else printf("%d\n",ans);
	}
	return 0;
}

C. Flexible String

爆搜题

不难发现关于答案的计算方式,我们只要考虑找出每段连续的两个串对应位置相同的区间长度即可

然后注意到题目中反复提到的字符集大小小于等于\(10\)的限制,可以直接爆搜改了哪些字符然后check即可

复杂度\(O(2^{10}\times n)\),实际情况前面是\(C_{10}^k\)只会更小

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=100005;
int t,n,k,tot,rst[26]; char a[N],b[N]; bool vis[N],cs[15]; 
vector <int> pos[15]; long long ans;
inline long long C2(CI x)
{
	return 1LL*x*(x+1)/2LL;
}
inline void DFS(CI now=1,CI num=0)
{
	if (num>k) return; if (now==tot+1)
	{
		RI i; for (i=1;i<=n;++i) vis[i]=a[i]==b[i];
		for (i=1;i<=tot;++i) if (cs[i])
		for (int x:pos[i]) vis[x]=1;
		long long ret=0; int lst=0;
		for (i=1;i<=n;++i) if (!vis[i]) ret+=C2(i-1-lst),lst=i;
		ret+=C2(n-lst); ans=max(ans,ret); return;
	}
	cs[now]=1; DFS(now+1,num+1); cs[now]=0; DFS(now+1,num);
}
int main()	
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (tot=0,i=0;i<26;++i) rst[i]=0;
		for (i=0;i<15;++i) pos[i].clear();
		for (scanf("%d%d%s%s",&n,&k,a+1,b+1),i=1;i<=n;++i)
		{
			if (!rst[a[i]-'a']) rst[a[i]-'a']=++tot;
			pos[rst[a[i]-'a']].push_back(i);
		}
		ans=0; DFS(); printf("%lld\n",ans);
	}
	return 0;
}

D. Flexible String Revisit

DP题,方程很好写但是求解需要技巧

不难发现我们只关心两个串不同的位置的个数时多少,因此直接设\(f_{i}\)表示有\(i\)个不同位置的期望步数

转移方程显然有:\(f_i=f_{i+1}\times\frac{n-i}{n}+f_{i-1}\times \frac{i}{n}+1\)

即我们对当前状态进行操作,有\(\frac{i}{n}\)的概率选中一个不同的位,这样还要操作\(f_{i-1}\)次,反之则有\(\frac{n-i}{n}\)的概率还要操作\(f_{i+1}\)

然后这个DP看似是成环的,当时我只发现了\(f_0=0\)这一个终止条件,但没有注意到\(f_n=f_{n-1}+1\)这个特殊情况

有了这个边界之后我们就可以考虑解方程了,因为那个转移的式子可以写成:

\[f_{i-1}=f_{i}\times\frac{n-i+1}{n}+f_{i-2}\times \frac{i-1}{n}+1\\ \Leftrightarrow f_i=(f_{i-1}-1-f_{i-2}\times \frac{i-1}{n})\times \frac{n}{n-i+1} \]

这是一个关于前两项的递推式,而根据我们的两个已知条件式可以解出来的

我们不妨设\(f_1\)是已知的初始条件,最后我们可以把每个\(f_i\)都用一个二元组\((x,y)\)来表示,其中\(f_i=x\times f_1+y\)

这样我们在得到了\(f_n,f_{n-1}\)的表示后可以反解出\(f_1\)的值,从而推出其中的任意一项了

不得不说这个trick之前没有见过,算是长见识了的说

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1000005,mod=998244353;
inline int sum(CI x,CI y)
{
	return x+y>=mod?x+y-mod:x+y;
}
inline int sub(CI x,CI y)
{
	return x-y<0?x-y+mod:x-y;
}
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;
}
struct Data // x*f[1]+y
{
	int x,y;
	inline Data(CI X=0,CI Y=0)
	{
		x=X; y=Y;
	}
	friend inline Data operator + (const Data& A,const Data& B)
	{
		return Data(sum(A.x,B.x),sum(A.y,B.y));
	}
	friend inline Data operator - (const Data& A,const Data& B)
	{
		return Data(sub(A.x,B.x),sub(A.y,B.y));
	}
	friend inline Data operator * (const Data& A,const Data& B)
	{
		return Data(1LL*A.x*B.x%mod,1LL*A.y*B.y%mod);
	}
}f[N]; int t,n,invn,dif; char a[N],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%s%s",&n,a+1,b+1),dif=0,i=1;i<=n;++i) dif+=a[i]!=b[i];
		for (f[0]=Data(0,0),f[1]=Data(1,0),invn=quick_pow(n),i=2;i<=n;++i)
		{
			int tmp=1LL*(i-1)*invn%mod,div=quick_pow(sub(1,tmp));
			f[i]=(f[i-1]-Data(0,1)-(f[i-2]*Data(tmp,tmp)))*Data(div,div);
		}
		Data tmp=f[n]-f[n-1]; int f1=1LL*sub(1,tmp.y)*quick_pow(tmp.x)%mod;
		printf("%d\n",sum(1LL*f[dif].x*f1%mod,f[dif].y));
	}
	return 0;
}

E. The Tree Has Fallen!

套路题

一堆数里选一些出来求最大的异或和,这个一眼线性基没跑了

换根操作的话一眼先定根然后再分类讨论,顺着这个思路这题就很简单了

首先假定\(1\)为根,然后我们考虑在这个有根树上把根换成\(r\)对于\(v\)的影响:

  • \(r\)不在\(v\)的子树中时,不难发现此时我们要找答案的数集就还是\(v\)的子树
  • \(r\)\(v\)的子树中时,手玩一下我们发现,设\(w\)\(v\)的直接儿子,并且满足\(r\)\(w\)的子树中,则此时我们要找答案的数集就是所有数的集合减去\(w\)的子树

因此我们把数的DFS序求出来,这样就变成了区间询问的题目了,由于线性基支持合并,因此直接上线段树即可

但是由于\(O(n\log^2 n)\)的复杂度且由于我各部分的封装实现,常数巨大,卡了好久的常数才跑过

后来比赛结束后躺在床上转念一想由于只有查询操作,且两个区间合并时有重复不会影响答案(因为重复的部分相当于插入了两次线性基,对答案没有影响),因此可以用ST表来写

不过由于我太懒的说既然卡过了就万事大吉了,就懒得再写了

#include<cstdio>
#include<iostream>
#include<vector>
#include<cstring>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005,P=30;
int t,n,a[N],x,y,q,dep[N],dfn[N],L[N],R[N],tot; vector <int> v[N];
struct LB
{
	int v[P];
	inline LB(void) { memset(v,0,sizeof(v)); }
	inline void clear(void) { memset(v,0,sizeof(v)); }
	inline void insert(int x)
	{
		for (RI i=P-1;~i;--i) if ((x>>i)&1)
		{
			if (!v[i]) return (void)(v[i]=x); x^=v[i];
		}
	}
	inline int query(int x=0)
	{
		for (RI i=P-1;~i;--i) if ((x^v[i])>x) x^=v[i]; return x;
	}
	friend inline LB operator + (const LB& A,const LB& B)
	{
		LB C; RI i; for (i=0;i<P;++i) C.v[i]=A.v[i];
		for (i=0;i<P;++i) if (B.v[i]) C.insert(B.v[i]); return C;
	}
};
class Segment_Tree
{
	private:
		LB tr[N<<2];
		#define TN CI now=1,CI l=1,CI r=n
		#define LS now<<1,l,mid
		#define RS now<<1|1,mid+1,r
		inline void pushup(CI now)
		{
			tr[now]=tr[now<<1]+tr[now<<1|1];
		}
	public:
		inline void build(TN)
		{
			if (l==r) return tr[now].clear(),tr[now].insert(a[dfn[l]]);
			int mid=l+r>>1; build(LS); build(RS); pushup(now);
		}
		inline LB query(CI beg,CI end,TN)
		{
			if (beg>end) return LB(); if (beg<=l&&r<=end) return tr[now]; int mid=l+r>>1;
			if (beg<=mid&&end<=mid) return query(beg,end,LS);
			if (end>mid&&beg>mid) return query(beg,end,RS);
			return query(beg,end,LS)+query(beg,end,RS);
		}
		#undef TN
		#undef LS
		#undef RS
}SEG;
class Tree_Solver
{
	private:
		int anc[N][20];
	public:
		inline void DFS(CI now=1,CI fa=0)
		{
			RI i; for (i=0;i<20;++i) anc[now][i]=0;
			dep[now]=dep[anc[now][0]=fa]+1; dfn[++tot]=now; L[now]=tot;
			for (i=0;i<19;++i) if (anc[now][i]) anc[now][i+1]=anc[anc[now][i]][i]; else break;
			for (int to:v[now]) if (to!=fa) DFS(to,now); R[now]=tot;
		}
		inline void jump(int& x,CI y)
		{
			for (RI i=20;~i;--i) if ((y>>i)&1) x=anc[x][i];
		}
}T;
int main()	
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&a[i]),v[i].clear();
		for (i=1;i<n;++i) scanf("%d%d",&x,&y),v[x].push_back(y),v[y].push_back(x);
		for (tot=0,T.DFS(),SEG.build(),scanf("%d",&q),i=1;i<=q;++i)
		{
			if (scanf("%d%d",&x,&y),x==y) printf("%d\n",SEG.query(1,n).query()); else
			{
				if (L[y]<=L[x]&&L[x]<=R[y])
				{
					T.jump(x,dep[x]-dep[y]-1);
					printf("%d\n",(SEG.query(1,L[x]-1)+SEG.query(R[x]+1,n)).query());
				} else printf("%d\n",SEG.query(L[y],R[y]).query());
			}
		}
	}
	return 0;
}
posted @ 2023-02-02 21:44  空気力学の詩  阅读(110)  评论(0编辑  收藏  举报