NOI Online #2 赛后题解

color

题意

(;)
给定(p_1,p_2),要求(p_1)的倍数格子填红色,(p_2)的倍数格子填蓝色,既是(p_1)又是(p_2)倍数的格子颜色任选。求是否存在一种填法,满足忽略掉无色格子后不存在个连续(k)相同颜色的格子
(;)
比较水的一道题。
(gcd(p_1,p_2))和贪心乱搞一通,用裴蜀定理证明,没了。
但是千万千万记得要特判(k=1)
时间复杂度:(O(T;log;p))

code

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
#define LL long long
LL a, b, k, T;
LL gcd(LL a,LL b)
{
	if(b == 0) return a;
	return gcd(b , a % b);
}
int main()
{
	cin>>T;
	while(T--)
	{
		scanf("%lld%lld%lld",&a,&b,&k);
		if(k == 1)
		{
			puts("No");
			continue;
		}
		if(a > b)swap(a,b);
		LL g = gcd(a,b);
		LL t = ((b - g) % a == 0) ? 0 : 1;
		if((b - g) / a + t < k)puts("Yes");
		else puts("No");
	}
	return 0;
} 

(;)
(;)

sequence

(;)

题意

(;)
给一个长度为(n)的序列(A_1,A_2,cdots,A_n.;)定义(f(l,r))为[l,r]中不同的数字个数.求
(sum_{l=1}^n sum_{r=l}^n f(l,r)^2)
(;)

暴力

(;)
直接枚举(l,r),然后在(r)不断加(1)的同时,更新此时区间中数字种类个数
时间复杂度:(O(n^2))
期望得分:(50pts)
(;)
(;)

正解

(;)
直接求平方似乎不太好操作
(ecause f(l,r)^2=f(l,r)(f(l,r)-1)+f(l,r))
不妨设(F(n)=sum_{i=1}^n f(i,n);,;g(n)=sum_{i=1}^n f(i,n)(f(i,n)-1))
然后我们只需分别求(sum_{i=1}^n F(n),g(n))即可
关键是怎么求?
(;)
(;)
现在给出一个思路:求(F(n)-F(n-1))(g(n)-g(n-1))
(F(n))中的每个区间只比(F(n-1))中的区间多了(a_n)
所以我们预处理出(last_n=j),(j<n)(a_j=a_n)((j)是满足这个条件中最靠后的)
因此只有([last_n+1,n])(l)端点的区间才会多出(a_r)这个新的数字,产生(1)的贡献,总共多产生了(n-last_n)的贡献
( herefore F(n)-F(n-1)=n-last_n)
而这个玩意相当于把([last_n+1,n])这段区间执行(+1)的操作

(;)
而求(g(n)-g(n-1))(F)同理。
([last_n+1,n])(l)端点的区间,多产生(1)的贡献。
因此:这些区间的(f(l,r)(f(l,r)-1)=>f(l,r)(f(l,r)+1))
(ecause f(l,r)(f(l,r)+1)-f(l,r)(f(l,r)-1)=2 imes f(l,r))
所以总共会增长:(2 imes sum_{i=last_n+1}^n f(i,r))
而:(sum_{i=last_n+1}^n f(i,r))实际是一个区间求和的操作

(;)
区间执行(+1),区间求和你想到了什么?
没错,就是线段树。
因此在枚举(r)(右端点)的过程中用线段树维护区间修改与和。
注意在修改与求和的顺序。
时间复杂度:(O(n;log;n))
期望得分:(100pts)

code

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>

using namespace std;

const int N = 1000010, mod = 1000000007;
#define LL long long 
#define F(i,a,b) for(int i=a;i<=b;i++)
#define ls(x) x<<1
#define rs(x) x<<1|1
int n, a[N], b[N], ap[N], last[N];
LL tree[N << 2], tag[N << 2], f[N], g[N], res1, res2;

inline void push_up(int root)
{
	tree[root] = (tree[ls(root)] + tree[rs(root)]) % mod; 
}
void build (int root, int l, int r)
{
	if(l==r)
	{
		return;
	}
	int mid = (l + r) >> 1;
	build(ls(root), l, mid);
	build(rs(root), mid+1, r);
	push_up(root);
}
inline void change(int root, int l, int r, LL k)
{
	tag[root] = (tag[root] + k) % mod;
	tree[root] = (tree[root] + (r-l+1) * k % mod) % mod;
}
void push_down(int root, int l, int r)
{
	if(tag[root])
	{
		int mid = (l + r) >> 1;
		change(ls(root), l, mid, tag[root]);
		change(rs(root), mid+1, r, tag[root]);
	}
	tag[root] = 0;
}
void Update(int root, int L, int R, int l, int r, LL k)
{
	if(l<=L&&R<=r)
	{
		change(root,L,R,k);
		return;
	}
	push_down(root,L,R);
	int mid = (L + R) >> 1;
	if(l <= mid) Update(ls(root), L, mid, l, r, k);
	if(r > mid) Update(rs(root), mid+1, R, l, r, k);
	push_up(root);
}
LL Query(int root, int L, int R, int l, int r)
{
	LL res = 0;
	if(l<=L&&R<=r)
	{
		return tree[root];
	}
	push_down(root,L,R);
	int mid = (L + R) >> 1;
	if(l <= mid) res = (res + Query(ls(root), L, mid, l, r)) % mod;
	if(r > mid) res = (res + Query(rs(root), mid+1, R, l, r)) % mod;
	return res;
}
int main()
{
	scanf("%d",&n);
	F(i,1,n) 
	{
		scanf("%d",&a[i]);
		b[i]=a[i];
	}
	sort(b+1,b+n+1);
	int m = unique(b+1,b+n+1) - b - 1;
	F(i,1,n)
	{
		a[i] = lower_bound(b+1,b+m+1,a[i]) - b;
	}
	F(i,1,n)
	{
		last[i]=ap[a[i]];
		ap[a[i]]=i;
	}
	build(1,1,n);
	F(r,1,n)
	{
		f[r] = (f[r-1] + r - last[r]) % mod;
		res1 = (res1 + f[r]) % mod;
		g[r] = (g[r-1] + 2 * Query(1,1,n,last[r]+1,r)) % mod;
		res2 = (res2 + g[r]) % mod;
		Update(1,1,n,last[r]+1,r,1);
	}
	printf("%lld",(res1 + res2) % mod);
	return 0;
}

(;)
(;)

match

(;)

题意

小 A 和小 B 正在玩一个游戏:有一棵包含 (n) 个点的有根树(点从 (1-n) 编号),它的根是(1) 号点,初始时两人各拥有 (m) 个点。游戏的每个回合两人都需要选出一个自己拥有且之前未被选过的点,若对手的点在自己的点的子树内,则该回合自己获胜;若自己的点在对方的点的子树内,该回合自己失败;其他情况视为平局。游戏共进行$ m$ 回合。

作为旁观者的你只想知道,在他们随机选点的情况下,第一次非平局回合出现时的回合数的期望值。

为了计算这个期望,你决定对于(k=0,1,2,cdots,m),计算出非平局回合数为(k)的情况数。两种情况不同当且仅当存在一个小 A 拥有的点 (x),小 B 在(x) 被小 A 选择的那个回合所选择的点不同。

由于情况总数可能很大,你只需要输出答案对(998244353)取模后的结果
(;)
(;)

暴力

(;)
枚举所有的可能性,对每种情况处理一下统计一下非平局的个数。
时间复杂度:(O(m!))
期望得分:(20pts)
(;)
(;)

正解

(;)
(;)

考虑树上DP

(;)
(f_{u,i})表示以(u)为根的子树中,有(i)对互为祖先与后代的关系的方案数。
状态转移?其实与各种树上DP的套路类似,考虑(u)与它的若干个子树(v)的关系
根据乘法原理,有:(f_{u,j+k}=f_{u,j} imes f_{v,k})
但注意:这里的转移不能直接拿(f)数组进行,而应先用一个(tmp)数组按上面的方程进行转移,再将其赋给(f)数组。
原因是我们要保证转移方程中的(f_{u,j})一定是(v)这个子树之前的方案数,并没算(v)这棵子树的贡献。而若直接用(f)数组进行转移,可能导致这里(f_{u,j})是加上了(v)这棵子树的结果。其实与01背包的倒序循环类似
(;)

算u的贡献

(;)
因为(u)能与它的子树内所有颜色不同的节点组成一对。所以设(cnt_{u,0/1})表示以(u)为根的0/1颜色的个数,设(color_u)表示(u)的颜色。
则与(u)不同颜色的节点有(cnt_{u,color_u ; xor ; 1})个。
因此我们可以得到转移方程:
(f_{u,i}=f_{u,i-1} imes (cnt_{u,color_u ; xor ; 1} - (i-1)))
(;)

统计答案

(;)
(Ans_i)表示整棵树中有(i)对的方案数。则(Ans_i=f_{1,i} imes (m-i)!)。等价于选出那(i)对后,剩下的随便组合。
但这样并不正确,剩下的点对也有可能组成祖先与后代的关系。
于是我们发现,这其实是一个类似容斥原理的东西。
对于每个有(i)对的方案,在统计的时候,由于剩下的点也有可能组成点对,所以其实可能有(i+1,i+2,cdots,m)个点对。
所以我们要将它们减去。
而对于(i+1)个点对的每一种方案,都会被算(C_{i+1}^i)次,相当于从这些中挑出(i)对。
如此类比可得:对于任意的(j(j>i))要减去(C_j^i imes Ans_j) 次。
于是通过这样的操作,即可保证不会算重。

时间复杂度

(;)
乍一看是(O(n^3))。但细细观察即可发现,我们在进行树上DP时,相当于是合并两颗子树中的点,只会在它们的LCA处作一次贡献。
所以是(O(n^2))
期望得分:(100pts)
(;)

code

#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include<algorithm>
using namespace std;
const int N = 5010, mod = 998244353;
int n, m, f[N][N], g[N], siz[N], cnt[N][2], fac[N], color[N], tmp[N];
int Invfac[N];
char str[N]; 
vector<int> G[N];
void Dfs(int u,int fa)
{
	f[u][0] = 1;
	for(int i=0;i<G[u].size();i++)
	{
		int v = G[u][i];
		if(v == fa) continue;
		Dfs(v,u);
		for(int j=0;j<=siz[u]+siz[v];j++)tmp[j] = 0;
		for(int j=0;j<=siz[u];j++)
		{
			for(int k=0;k<=siz[v];k++)
			{
				tmp[j + k] = (tmp[j + k] + 1LL * f[u][j] * f[v][k] % mod) % mod;
			}
		}
		for(int j=0;j<=siz[u]+siz[v];j++)f[u][j] = tmp[j];
		siz[u] += siz[v];
		cnt[u][0] += cnt[v][0]; cnt[u][1] += cnt[v][1];
	}  
	siz[u] ++;
	cnt[u][color[u]] ++;
	for(int j=siz[u];j;j--)
	{
		f[u][j] = (f[u][j] + 1LL * f[u][j - 1] * (cnt[u][color[u] ^ 1] - j + 1) % mod) % mod;
	}
}
int ksm(int a,int b)
{
	int res = 1;
	while(b)
	{
		if(b & 1) res = 1LL * res * a % mod;
		a = 1LL * a * a % mod;
		b >>= 1;
	}
	return res;
}
int C(int n,int m)
{
	return 1LL * fac[n] * Invfac[n - m] % mod * Invfac[m] % mod;
}
int main()
{
	cin >> n >> str + 1;
	m = n / 2;
	for(int i=1;i<=n;i++)
	{
		color[i] = (str[i] == '0') ? 0 : 1;
	}
	for(int i=1;i<n;i++)
	{
		int u, v;
		cin >> u >> v;
		G[u].push_back(v); G[v].push_back(u);
	}
	Dfs(1,0);
	fac[0] = 1;
	for(int i=1;i<=n;i++)
	{
		fac[i] = 1LL * fac[i - 1] * i % mod;
	}
	Invfac[0] = 1;
	for(int i=1;i<=n;i++)
	{
		Invfac[i] = 1LL * Invfac[i - 1] * ksm(i,mod - 2) % mod;
	}
	for(int i=0;i<=m;i++)
	{
		g[i] = 1LL * fac[m - i] * f[1][i] % mod;
	}
	for(int i=m;i>=0;i--)
	{
		for(int j=i+1;j<=m;j++)
		{
			g[i] = (g[i] - 1LL * C(j,i) * g[j] % mod + mod) % mod;
		}	
	}
	for(int i=0;i<=m;i++)printf("%d
",g[i]);
	return 0;
}
原文地址:https://www.cnblogs.com/czyty114/p/12792956.html