2024.8.18 模拟赛 22

模拟赛

T1 先崩,然后电脑又崩。

题面都在这里了

T1 2-Coloring

原题3100,张口放T1(这是原话)

看起来像 dp,推了两个小时大力分讨,最后式子比我命还长。刚推出来就发现假了

正解差不多人类智慧吧,也可能只是小 trick。

对于整张图,考虑最终染色的“形状”。(下面这个样子)

图片来自 题解 CF1503E 2-Coloring,讲得真的很好。

同一颜色最多会产生两个连通块,如果中间恰好交于一点一共就有四个连通块。

如果我们想对这个图的“形状”进行计数,那么就要找到能够代表图的特征点以便不重不漏,并且应该可以计数。

对于这道题来说,有一个关键就是每一列不能全放、每一行不能不放,在“形状”上表示中间有且只有一列可以将两侧黄色连通块分开

所以我们可以枚举这个临界,然后计算当临界列为 \(i\) 时的方案。

这就将黄色连通块分开了。黄色块会与第 \(i\) 列有两段相交的区间 \((l_1,r_1)\)\((l_2,r_2)\),我们将 \(r1,l2\) 这对点作为我们计数的标准。

样整个图就变成了四个部分,每个部分都很规整的将两种颜色分成两半。

画图条件有限。

因此我们分别算每个块的方案数就好了!最后把四个块的乘起来。

那么怎么单独求方案数呢?有一种很巧妙的方法就是转化为路径问题。由于要满足选的 \(A,B\) 点是 \(r_1,l_2\) 的位置,所以实际计算时就是算顶点到上面标出的四个点的路径方案数。

这时我们只考虑了黄色被分割的情况,而蓝色被分割其实就是 \(n,m\) 交换一下。

这样先枚举 \(i\),再枚举 \(A,B\) 的复杂度显然是 \(n^3\) 的,如何优化?

假如 \(A\) 点在上方,那么 \(B\) 点其实就是在 \([1,A]\) 的所有情况的和,可以前缀和维护,注意 \(A=B\) 的时候特殊处理。

然后终于做完了。

code
#include<bits/stdc++.h>
using namespace std;
#define LL long long
const int N = 4999,mod = 998244353;
int n,m;
LL f[N],inv[N],ans;

void init(int NM)
{
	f[0]=inv[0]=inv[1]=1;
	for(int i=2;i<=NM;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	for(int i=1;i<=NM;i++) f[i]=f[i-1]*i%mod,inv[i]=inv[i-1]*inv[i]%mod;
}
LL way(int x,int y) {return f[x+y]*inv[x]%mod*inv[y]%mod;}
int main()
{
	freopen("magic.in","r",stdin);
	freopen("magic.out","w",stdout);
	scanf("%d%d",&n,&m);
	init(n+m);
	for(int i=1;i<=m-1;i++)
	{
		LL sum=0;
		for(int j=1;j<=n-1;j++)
		{
			sum=(sum+way(i,j-1)*way(i-1,n-j))%mod;
			ans=(ans+sum*way(m-i-1,j)%mod*way(m-i,n-j-1))%mod;
		}
	}
	swap(n,m);
	for(int i=1;i<=m-1;i++)
	{
		LL sum=0;
		for(int j=1;j<=n-1;j++)
		{
			ans=(ans+sum*way(m-i-1,j)%mod*way(m-i,n-j-1))%mod;
			sum=(sum+way(i,j-1)*way(i-1,n-j))%mod;
		}
	}
	printf("%lld\n",(ans<<1ll)%mod);
}

T2 连通块

需要用到树的直径的小性质:

  • 距离树内一点 \(u\) 最远的点一定为树的直径的端点。

  • 两棵树合并后树的直径就是两棵树之前的直径端点间的最大距离(四个点里选两个)。

也就是树的直径可以合并。

对于原题中的删边操作,我们可以转化为逆向的加边操作。这样用并查集维护连通块,并且树的直径可以直接合并。就很简单了。

(第一次有关树据结构的题能自己打出来还一遍过了)

code
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
int n,m,d[N][2],ans[N];
int head[N],tot;
struct E {int u,v;} e[N<<1],ed[N];
inline void add(int u,int v) {e[++tot]={head[u],v}; head[u]=tot;}

struct Q {int tp,x;} q[N<<1];
bool vs[N];
namespace LCA
{
	int dfn[N],cnt,st[N][21],dep[N],lg[N];
	void dfs(int u,int f)
	{
		dfn[u]=++cnt; st[cnt][0]=f; dep[u]=dep[f]+1;
		for(int i=head[u];i;i=e[i].u) 
		{
			int v=e[i].v; if(v==f) continue;
			dfs(v,u);
		}
	}
	inline int get(int x,int y) {return dfn[x]<dfn[y]?x:y;}
	void init() 
	{
		for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
		for(int i=1;i<=20;i++)
			for(int j=1;j+(1<<i)-1<=n;j++) 
				st[j][i]=get(st[j][i-1],st[j+(1<<i-1)][i-1]);
	}
	int lca(int x,int y) 
	{
		if(x==y) return x;
		if((x=dfn[x])>(y=dfn[y])) swap(x,y);
		int k=__lg(y-x); x++;
		return get(st[x][k],st[y-(1<<k)+1][k]);
	}
	inline int dis(int x,int y) {return dep[x]+dep[y]-(dep[lca(x,y)]<<1);}
}; using namespace LCA;
namespace DSU
{
	int fa[N];
	void init() {for(int i=1;i<=n;i++) fa[i]=i,d[i][0]=d[i][1]=i;}
	int find(int x) {return x==fa[x]?x:fa[x]=find(fa[x]);}
	void merge(int x,int y)
	{
		int fx=find(x),fy=find(y);
		int t1=d[fx][0],t2=d[fx][1],tmp=dis(t1,t2),t3;
		for(int i=0;i<=1;i++) for(int j=0;j<=1;j++) 
		{
			t3=dis(d[fx][i],d[fy][j]);
			if(t3>tmp) t1=d[fx][i],t2=d[fy][j],tmp=t3;
		}
		t3=dis(d[fy][1],d[fy][0]); if(t3>tmp) t1=d[fy][0],t2=d[fy][1],tmp=t3;
		fa[fy]=fx; d[fx][0]=t1; d[fx][1]=t2;
	}
	int get(int x)
	{
		int fx=find(x);
		return max(dis(x,d[fx][0]),dis(x,d[fx][1]));
	}
};

int main()
{
	freopen("block.in","r",stdin);
	freopen("block.out","w",stdout);
	scanf("%d%d",&n,&m);
	for(int i=1;i<n;i++)
	{
		int x,y; scanf("%d%d",&x,&y);
		add(x,y); add(y,x); ed[i]={x,y};
	}
	dfs(1,0); init(); DSU::init();
	for(int i=1;i<=m;i++)
	{
		int c,x; scanf("%d%d",&c,&x);
		q[i]={c,x}; if(c==1) vs[x]=1;
	}
	for(int i=1;i<n;i++) if(!vs[i]) DSU::merge(ed[i].u,ed[i].v);
	int cnt=0;
	for(int i=m;i>=1;i--)
	{
		if(q[i].tp==1) DSU::merge(ed[q[i].x].u,ed[q[i].x].v);
		else ans[++cnt]=DSU::get(q[i].x);
	}
	for(int i=cnt;i>=1;i--) printf("%d\n",ans[i]);
	return 0;
}

T3 军队

这其实才是数据结构。。。

题面挺抽象的,但题意奇怪但简单。

我们可以用扫描线处理矩形加,因为只需要维护每一行的信息,所以其实还挺简单的。

重点在于维护什么信息?

我们想要知道每一行中 \(\ge k\) 的个数,并且 \(k \le 30\),首先想到维护 \(\lt k\) 的个数,并从 \(k\) 上操作。

其实小于 \(k\) 的数值只会有 \(k\) 个,我们可以维护\(k\)的值和数量,\(\lt k\) 的个数一定已经被包含在里面了。

线段树合并时类似归并,挺暴力的。查询只会查整行,所以暴力遍历前 \(k\) 小的值就行了。

每一行记录两种“性别”中较小的那个(因为要配对)。最后回答时贪心,\(lower_bound\) 找一下多少行能取 \(\lfloor \frac{y}{2} \rfloor\)。剩下的顶着取就行了。

这里其实就是 \(\sum a \times (y-a)\),维护一下 \(a\)\(a^2\) 的前缀和(后缀和也可以呢)。(\(a\) 就是那个)。

(一开始用 vector,写法简洁又亲民,\(\mathbb{T}\) 飞了,被迫换数组。)

code
#include<bits/stdc++.h>
#define scan __builtin_scanf
#define print __builtin_printf
using namespace std;
#define fi first
#define se second
#define P pair<int,int>
#define LL long long
const int N = 3e5+5;
int n,m,c,K,Q,s[N];
LL ans,s1[N],s2[N];
struct M {int l,r,v;}; vector<M> v[N];

namespace SEG
{
	struct T {int l,r,lz,cnt=0,kthf[11],kths[11];} tr[N<<2];
	inline void pushup(int k)
	{
		tr[k].cnt=0;
		int sz1=tr[k<<1].cnt,sz2=tr[k<<1|1].cnt;
		int l=1,r=1;
		while(l<=sz1&&r<=sz2&&tr[k].cnt<K)
		{
			if(tr[k<<1].kthf[l]<tr[k<<1|1].kthf[r]) tr[k].kthf[++tr[k].cnt]=tr[k<<1].kthf[l],tr[k].kths[tr[k].cnt]=tr[k<<1].kths[l++];
			else if(tr[k<<1].kthf[l]>tr[k<<1|1].kthf[r]) tr[k].kthf[++tr[k].cnt]=tr[k<<1|1].kthf[r],tr[k].kths[tr[k].cnt]=tr[k<<1|1].kths[r++];
			else tr[k].kthf[++tr[k].cnt]=tr[k<<1].kthf[l],tr[k].kths[tr[k].cnt]=tr[k<<1].kths[l]+tr[k<<1|1].kths[r],l++,r++;
		}
		while(l<=sz1&&tr[k].cnt<K) tr[k].kthf[++tr[k].cnt]=tr[k<<1].kthf[l],tr[k].kths[tr[k].cnt]=tr[k<<1].kths[l++];
		while(r<=sz2&&tr[k].cnt<K) tr[k].kthf[++tr[k].cnt]=tr[k<<1|1].kthf[r],tr[k].kths[tr[k].cnt]=tr[k<<1|1].kths[r++];
	}
	inline void pushdown(int k)
	{
		if(tr[k].lz)
		{
			int lz=tr[k].lz; tr[k].lz=0;
			for(int i=1;i<=tr[k<<1].cnt;i++) tr[k<<1].kthf[i]+=lz;
			for(int i=1;i<=tr[k<<1|1].cnt;i++) tr[k<<1|1].kthf[i]+=lz;
			tr[k<<1].lz+=lz; tr[k<<1|1].lz+=lz;
		}
	}
	inline void bui(int k,int l,int r)
	{
		tr[k].l=l; tr[k].r=r;
		if(l==r) return (tr[k].kthf[++tr[k].cnt]=0,tr[k].kths[tr[k].cnt]=1),void(0);
		int mid=l+r>>1;
		bui(k<<1,l,mid); bui(k<<1|1,mid+1,r);
		pushup(k);
	}
	inline void mdf(int k,int l,int r,int v)
	{
		if(tr[k].l>=l&&tr[k].r<=r) 
		{
			for(int i=1;i<=tr[k].cnt;i++) tr[k].kthf[i]+=v;
			tr[k].lz+=v; return;
		}
		pushdown(k);
		int mid=tr[k].l+tr[k].r>>1;
		if(l<=mid) mdf(k<<1,l,r,v);
		if(r>mid) mdf(k<<1|1,l,r,v);
		pushup(k);
	}
}; using namespace SEG;

int main()
{
	freopen("ar.in","r",stdin);
	freopen("army.out","w",stdout);
	scan("%d%d%d%d%d",&n,&m,&c,&K,&Q);
	for(int i=1;i<=c;i++)
	{
		int x1,x2,y1,y2; scan("%d%d%d%d",&x1,&y1,&x2,&y2);
		v[x1].push_back({y1,y2,1}); v[x2+1].push_back({y1,y2,-1});
	}
	bui(1,1,m);
	for(int i=1;i<=n;i++)
	{
		int res=0;
		for(M j:v[i]) mdf(1,j.l,j.r,j.v);
		for(int i=1;i<=tr[1].cnt;i++)
		{
			if(tr[1].kthf[i]>=K) break;
			res+=tr[1].kths[i];
		}	
		s[i]=min(res,m-res);
	}
	sort(s+1,s+1+n);
	for(int i=n;i>=1;i--) s1[i]=s1[i+1]-s[i]*s[i],s2[i]=s2[i+1]+s[i];
	for(int i=1;i<=Q;i++)
	{
		int x,y; scan("%d%d",&x,&y);
		int p=lower_bound(s+1,s+1+n,y/2)-s;
		if(n-p+1>=x) print("%lld\n",1ll*x*(y/2)*(y-(y/2)));
		else print("%lld\n",1ll*(n-p+1)*(y/2)*(y-(y/2))+s1[n-x+1]-s1[p]+1ll*y*(s2[n-x+1]-s2[p]));
	}
	return 0;
}

T4 棋盘

咕咕咕。。。

posted @ 2024-09-23 20:18  ppllxx_9G  阅读(12)  评论(0编辑  收藏  举报