• 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;
    }
    
  • 相关阅读:
    mysql 获取字符串的长度
    mysql 字符类以及重复元字符
    mysql 字段拼接
    mysql 去除字符串中的空格
    mysql 正则表达式
    mysql 选出前五个元素
    mysql regexp 表达式
    mysql 选择所有同学名字
    mysql 获取数学成绩最高以及最低的同学
    mysql 获取单个科目的平均分
  • 原文地址:https://www.cnblogs.com/czyty114/p/12792956.html
Copyright © 2020-2023  润新知