SDOI2019 R2D1 题解

题目排序不是我做题的顺序也不是试题顺序。

染色

subtask 具有提示意义,考虑按 subtask 来分析。
1、只考虑每一列至多定下一种颜色,且首尾列都定下了
(dp_{i,c}) 表示做到第 (i) 个有颜色的列,该列另一个位置填 (c) 的方案数。
可以暴力转移,枚举上一列填了啥子,
这两列的关系有如下几种:

aa
bb

ab
ba

aa bc
bc aa

ab ca
ca ab

ab
cd

每一种颜色组合,中间的填色方案数,取决于 (i-j),那么我们可以考虑手玩一个 dp 预处理这个系数。

复杂度 (O(nc^2)) 得分:0。

注意到对于每个颜色,本质不同的转移是 (O(1)) 的,用前缀和可以优化,这样一来复杂度可以做到 (O(nc))。得分:76。

考虑如果有一列有两种颜色怎么做:直接当成直接一种颜色,抹去另一行不合法颜色位置的值即可。得分:88。

考虑前后有一段空列怎么做:只跟空列数目有关,普及组dp预处理系数即可。得分:96。

参考代码(96分)

/*
g[i][0/1/2/3/4]

aa
bb

ab
ba

aa bc
bc aa

ab ca
ca ab

ab
cd
*/
#include<stdio.h>
#include<vector>
#include<stdlib.h>
#include<algorithm>
using namespace std;

typedef long long ll;
const int N=100005,P=1e9+9;

inline int add(int a,int b){return (a+=b)>=P?a-P:a;}
inline int dec(int a,int b){return (a-=b)<0?a+P:a;}
inline int mul(int a,int b){static ll r;r=(ll)a*b;return r>=P?r%P:r;}

inline void Inc(int &a,int b){(a+=b)>=P&&(a-=P);}
inline void Dec(int &a,int b){(a-=b)<0&&(a+=P);}
inline void Mul(int &a,int b){a=mul(a,b);}

int n,c,f[N],g[N][5],a[N][2],dp[2][N],s[2],ans;

void init0()
{
	int G[5][5]={
		{0,1,0,2*(c-2),mul(c-2,c-3)},
		{1,0,2*(c-2),0,mul(c-2,c-3)},
		{0,1,c-2,c-3+c-2,add(mul(c-3,c-4),c-3)},
		{1,0,c-3+c-2,c-2,add(mul(c-3,c-4),c-3)},
		{1,1,2*(c-3),2*(c-3),add(mul(c-4,c-4),c-3)}
	}; // 各种状态间的转移系数
	g[1][1]=g[1][3]=g[1][4]=1;
	for(int i=2; i<=n; i++)
		for(int j=0; j<5; ++j)
			for(int k=0; k<5; ++k)
				Inc(g[i][j],mul(g[i-1][k],G[j][k]));
	f[0]=1; // 首尾空列的系数
	for(int i=1; i<=n; i++)
		f[i]=mul(f[i-1],add(mul(c-2,c-2),c-1));
}

struct node {
	int p,a,b;
}; vector<node> v;

void init1()
{
	// naive
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i][0]);
	for(int i=1; i<=n; i++)
		scanf("%d",&a[i][1]);
	for(int i=1; i<=n; i++)
		if(a[i][0]||a[i][1])
		{
			v.push_back((node){i,a[i][0],a[i][1]});
			if(a[i][0]==a[i][1])
			{
				puts("0");
				exit(0);
			}
		}
}

void work()
{
	int l=v.size();
	if(!l)
	{
		ans=mul(f[n-1],mul(c,c-1));
		return;
	}
	for(int k=1; k<=c; k++)
	{
		int a=v[0].a,b=v[0].b;
		if(a&&b&&(k!=b))
			continue;
		if(!a)a=k;
		if(!b)b=k;
		if(a==b)
			continue;
		dp[0][k]=f[v[0].p-1];
		Inc(s[0],dp[0][k]);
	}
	if(v[0].a==v[0].b)
		v[0].b=0;
	for(int i=1,cur=1; i<l; ++i,cur^=1)
	{
		s[cur]=0;
		for(int k=1; k<=c; k++)
		{
			dp[cur][k]=0;
			int ew=s[!cur];
			int a=v[i].a,b=v[i].b,c,p=v[i].p-v[i-1].p;
			if(a&&b&&(k!=b))
				continue;
			if(!a)a=k;
			if(!b)b=k;
			if(a==b)
				continue;
			if(v[i-1].a)
				c=v[i-1].a;
			else
				c=v[i-1].b,swap(a,b);
			if(a==c)
			{
				Inc(dp[cur][k],mul(dp[!cur][b],g[p][0]));
				Dec(ew,dp[!cur][b]);
				Inc(dp[cur][k],mul(ew,g[p][2]));
			}
			else if(b==c)
			{
				Inc(dp[cur][k],mul(dp[!cur][a],g[p][1]));
				Dec(ew,dp[!cur][a]);
				Inc(dp[cur][k],mul(ew,g[p][3]));
			}
			else
			{
				Inc(dp[cur][k],mul(dp[!cur][a],g[p][3]));
				Dec(ew,dp[!cur][a]);
				Inc(dp[cur][k],mul(dp[!cur][b],g[p][2]));
				Dec(ew,dp[!cur][b]);
				Inc(dp[cur][k],mul(ew,g[p][4]));
			}
			Inc(s[cur],dp[cur][k]);
		}
		if(v[i].a&&v[i].b)
			v[i].b=0;
		if(i==l-1)
			for(int k=1; k<=c; k++)
				Inc(ans,mul(dp[cur][k],f[n-v.back().p]));
	}
	
}

int main()
{
	scanf("%d%d",&n,&c);
	if(c>10000) return 0;
	init0();
	init1();
	work();
	printf("%d",ans);
	return 0;
}

dp 的过程和 PKUSC2019 d2t1 类似,可以直接写一个区间加乘线段树维护,时间复杂度 (O((n+c)logc))。得分:100。

世界地图

转化一下题意大概是这个:

有若干个 MST,各有 (O(n)) 个关键点。
要求支持把两个 MST 合并,保证只有关键点之间会添加 (O(n)) 条带权边,
要求能维护出 MST 的边权和,并且更新成一个新的大小 (O(n)) 的关键点集合(用于下一次合并)。
要求做到单次 (O(nlog n)) 左右的复杂度。

若关键点只有两个则只要考虑MST中 <u,v> 路径上最大的边即可(LCT 维护 MST 的思想)。
现在要维护任意两个关键点路径上的 max, 可以先在原 MST 上加上新边,再拉出新关键点的类似斯坦纳树状物。
我们考虑升序插入边,当一条边的两侧均有关键点时,他必定将成为某两个关键点之间路径上的 max,于是他被视为一条关键边。

struct MST {
	int n;
	vector<Edge> v;
	ll sum; // 非关键边的边权和
	ll getans()
	{
		ll t = sum;
		for(auto &e : v) t += e.w;
		return t;
	}
	void debug()
	{
		printf("n = %d
", n);
		for(auto &e : v)
			printf("<%d, %d> = %d
", e.u, e.v, e.w);
		printf("sum = %lld
", sum);
	}
}Pre[M], Suf[M], Mo;

void Merge(const MST& a, const MST& b, int *ew)
{
	int nn = a.n + b.n;
	ll sum = a.sum + b.sum;
	vector<Edge> v(a.v);
	for(auto &e : b.v)
		v.push_back(Edge(e.u + a.n, e.v + a.n, e.w));
	for(int j = 1; j <= n; j ++)
		v.push_back(Edge(a.n - n + j, a.n + j, ew[j]));
	sort(v.begin(), v.end(), [&](const Edge&a, const Edge&b) {
		return a.w < b.w;
	});
	// Points
	// [1, n] cup [nn - n + 1, nn]
	for(int i = 1; i <= nn; ++i) f[i] = i;
	Mo.v.clear();
	for(auto &e : v)
	{
		int u = find(e.u), v = find(e.v);
		if(u == v)
			continue;
		sum += e.w;
		int cu = u <= n || u > nn - n;
		int cv = v <= n || v > nn - n;
		if(cu && cv) {
			f[u] = v; sum -= e.w;
			if(u > nn - n) u = u - nn + n + n;
			if(v > nn - n) v = v - nn + n + n;
			Mo.v.push_back(Edge(u, v, e.w));
		}
		else if(cu)
			f[v] = u;
		else if(cv)
			f[u] = v;
		else
			f[u] = v;
	}
	Mo.n = n + n; Mo.sum = sum;
}

简单查询

恐怕这个题不太能做,博主水平有限请见谅。

原文地址:https://www.cnblogs.com/bestwyj/p/12465668.html