杭电多校第一场7月20日补题记录

H Maximal submatrix

题意:找到最大的连续子矩形,使得每一列都非单调递减。

解法:考虑转化为 01 矩阵。如果当前位比上一位大或等于,则记 1 1 1,反之为 0 0 0。则原题转化为找到最大的全 1 1 1 矩阵。

考虑使用悬线法。定义以下几个变量: l e f t i , j left_{i,j} lefti,j表示以 ( i , j ) (i,j) (i,j)为端点向左延申的最左边界(不遇到障碍), r i g h t i , j right_{i,j} righti,j ( i , j ) (i,j) (i,j) 向右延申的最右边界。 h e i g h t i , j height_{i,j} heighti,j 表示向上延申的最大高度。我们很容易发现一个简单的递推式:

l e f t i , j { l e f t i − 1 , j + 1 , 当前节点是合法节点 0 , 当前节点不合法 , r i g h t i , j { l e f t i + 1 , j + 1 , 当前节点是合法节点 0 , 当前节点不合法 , h e i g h t i , j { h e i g h t i , j − 1 + 1 , 当前节点是合法节点 0 , 当前节点不合法 left_{i,j} \begin{cases} left_{i-1,j}+1, & \text{当前节点是合法节点} \\ 0, & \text{当前节点不合法} \end{cases}, right_{i,j} \begin{cases} left_{i+1,j}+1, & \text{当前节点是合法节点} \\ 0, & \text{当前节点不合法} \end{cases}, height_{i,j} \begin{cases} height_{i,j-1}+1, & \text{当前节点是合法节点} \\ 0, & \text{当前节点不合法} \end{cases} lefti,j{lefti1,j+1,0,当前节点是合法节点当前节点不合法,righti,j{lefti+1,j+1,0,当前节点是合法节点当前节点不合法,heighti,j{heighti,j1+1,0,当前节点是合法节点当前节点不合法

在最后统计答案的时候,考虑以 ( i , j ) (i,j) (i,j) 为下端点的矩形。更新 l e f t i , j = max ⁡ { l e f t i , j , l e f t i − 1 , j } left_{i,j}=\max\{left_{i,j},left_{i-1,j}\} lefti,j=max{lefti,j,lefti1,j},更新 r i g h t i , j = min ⁡ { r i g h t i , j , r i g h t i + 1 , j } right_{i,j}=\min\{right_{i,j},right_{i+1,j}\} righti,j=min{righti,j,righti+1,j}。此时在 [ l e f t i , j , r i g h t i , j ] [left_{i,j},right_{i,j}] [lefti,j,righti,j] 上矩形始终合法。

模板见下:

    //赋初值
    for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
		{
			left[i][j] = right[i][j] = j;
			height[i][j] = 1;
		}
	//更新left
	for (int i = 1; i <= n; i++)
		for (int j = 2; j <= m; j++)
			if (check(i,j)) //判定是否合法
				left[i][j] = left[i][j - 1];
				
	for (int i = 1; i <= n; i++)
		for (int j = m - 1; j >=1 ; j--)
			if (check(i,j)) 
				right[i][j] = right[i][j + 1];
				
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= m; j++)
			if (i > 1 && check(i,j))
			{
				left[i][j] = max(left[i][j], left[i - 1][j]);
				right[i][j] = min(right[i][j], right[i - 1][j]);
				height[i][j] = height[i - 1][j] + 1;
			}

总体时间复杂度 O ( n m ) O(nm) O(nm)

但是此题有一个局限性在于:当前点为 0 0 0 并不代表着当前点不能取得,而是说这个点不能和上一个点一起取得。因而在处理的时候需要一些更特殊的判断。

同时此题卡空间,因而推荐使用单调栈的做法。考虑滚动 h e i g h t height height 数组,这样就可以满足题目要求的空间了。

#include <cstdio>
#include <algorithm>
using namespace std;
int height[2005], num[2005][2005];
bool vis[2005][2005];
int que[2005];
int main()
{
    int n, m, t;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n;i++)
            for (int j = 1; j <= m;j++)
            {
                scanf("%d", &num[i][j]);
                if(i>1 && num[i][j]>=num[i-1][j])
                    vis[i][j] = 1;
                else
                    vis[i][j] = 0;
            }
        int ans = 0;
        for (int i = 1; i <= n; i++)
        {
            int tot = 0;
            for (int j = 1; j <= m; j++)
                if(vis[i][j])
                    height[j]++;
                else
                    height[j] = 1;
            height[m + 1] = 0;//多压入一个这样可以处理完全部的栈中元素,防止遗漏解
            for (int j = 1; j <= m + 1; j++)
            {
                while(tot && height[que[tot]]>height[j])
                {
                    ans = max(ans, (j - que[tot - 1] - 1) * height[que[tot]]);
                    tot--;
                }
                que[++tot] = j;
            }
        }
        printf("%d\n", ans);
    }
    return 0;
}

J zoto

题意:给定 x O y xOy xOy 平面上的 n n n 个点 ( i , f ( i ) ) (i,f(i)) (i,f(i)),进行 m m m 次询问,每次给定一个矩形,问全部位于矩形内的点的不同纵坐标值有多少个。

解法:考虑一个简化的问题。给一个数组,初始所有位置全是 0 0 0,每次单点加一或减一,询问区间有多少个位置不为 0 0 0。若使用分块,则修改的复杂度为 O ( 1 ) O(1) O(1),查询的复杂度为 O ( n ) O(\sqrt{n}) O(n )

考虑离线化询问,使用莫队算法。注意到在 y y y 维度上就是我们说的这个问题,因而对 y y y 上对值域进行分块维护,在 x x x 轴上进行区间的移动。莫队单次移动区间复杂度为 O ( n ) O(\sqrt{n}) O(n ),查询复杂度为 O ( 1 ) O(1) O(1),因而整体复杂度为 O ( m ( ( n ) + ( N ) ) ) O(m(\sqrt(n)+\sqrt(N))) O(m(( n)+( N)))

#include <cstdio>
#include <algorithm>
#include <cmath>
using namespace std;
int block_x, block_y;
struct node
{
    int left;
    int right;
    int up;
    int down;
    int id;
};
struct node que[100005];
int f[100005];
int num[100005], sum[100005], ans[100005];
bool cmp(node a,node b)
{
    if(a.left/block_x==b.left/block_x)
    {
        if ((a.left/block_x)&1)
            return a.right < b.right;
        else
            return a.right > b.right;
    }
    else
        return a.left / block_x < b.left / block_x;
}
void add(int x)
{
    num[x]++;
    if(num[x]==1)
        sum[x / block_y]++;
}
void sub(int x)
{
    num[x]--;
    if(num[x]==0)
        sum[x / block_y]--;
}
int query(int x)
{
    int ans = 0;
    for (int i = 0; i < x / block_y;i++)
        ans += sum[i];
    for (int i = x / block_y * block_y; i <= x;i++)
        ans += (num[i] >= 1);
    return ans;
}
int main()
{
    int t, n, m;
    block_y = sqrt(100000);
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d%d", &n, &m);
        for (int i = 1; i <= n;i++)
            scanf("%d", &f[i]);
        block_x = sqrt(n);
        for (int i = 1; i <= m; i++)
        {
            scanf("%d%d%d%d", &que[i].left, &que[i].down, &que[i].right, &que[i].up);
            que[i].id = i;
        }
        sort(que + 1, que + n + 1, cmp);
        int nowleft = que[1].left, nowright = que[1].right;
        for (int i = nowleft; i <= nowright;i++)
            add(f[i]);
        ans[que[1].id] = query(que[1].up) - query(que[1].down - 1);
        for (int i = 2; i <= m;i++)
        {
            while(que[i].left<nowleft)
                add(f[--nowleft]);
            while (que[i].right > nowright)
                add(f[++nowright]);
            while (que[i].left > nowleft)
                sub(f[nowleft++]);
            while (que[i].right < nowright)
                sub(f[nowright--]);
            ans[que[i].id] = query(que[i].up) - query(que[i].down - 1);
        }
        for (int i = 1; i <= m; i++)
            printf("%d\n", ans[i]);
    }
    return 0;
}

K Necklace of Beads

题意:现有一个包含 n n n 个珠子的圆环,要向这 n n n 个珠子填红、蓝、绿三色,相同颜色不能相邻,旋转等价,求绿色珠子不超过 k k k 个的方案总数。

解法:旋转等价考虑使用 Burnside 引理。根据公式,方案总数 L = 1 ∣ G ∣ ∑ i = 1 s D ( g j ) \displaystyle L=\frac{1}{|G|} \sum_{i=1}^{s} D(g_j) L=G1i=1sD(gj),其中 s s s 为置换群的大小,即 ∣ G ∣ |G| G D ( g j ) D(g_j) D(gj) 表示对于置换 g j g_j gj,有 D ( g j ) D(g_j) D(gj) 种方案使得经过这个置换后仍为原状。注意到此处的旋转等价表示此处仅有以下的 n n n 种置换:

g 1 = ( 1 2 3 ⋯ n − 1 n 1 2 3 ⋯ n − 1 n ) , g 2 = ( 1 2 3 ⋯ n − 1 n 2 3 4 ⋯ n 1 ) , g 3 = ( 1 2 3 ⋯ n − 1 n 3 4 5 ⋯ 1 2 ) , ⋯   , g n = ( 1 2 3 ⋯ n − 1 n n 1 2 ⋯ n − 2 n − 1 ) , g_1=\begin{pmatrix} 1 & 2 & 3 & \cdots & n-1 & n \\ 1 & 2 & 3 & \cdots & n-1 & n \\ \end{pmatrix}, g_2=\begin{pmatrix} 1 & 2 & 3 & \cdots & n-1 & n\\ 2 & 3 & 4 & \cdots & n & 1\\ \end{pmatrix}, g_3=\begin{pmatrix} 1 & 2 & 3 & \cdots & n-1 & n \\ 3 & 4 & 5 & \cdots & 1 & 2 \\ \end{pmatrix}, \cdots, g_n=\begin{pmatrix} 1 & 2 & 3 & \cdots & n-1 & n \\ n & 1 & 2 & \cdots & n-2 & n-1 \\ \end{pmatrix}, g1=(112233n1n1nn),g2=(122334n1nn1),g3=(132435n11n2),,gn=(1n2132n1n2nn1),

一般化的,可以将这种置换表示为:

g k = ( 1 2 3 ⋯ n − 1 n k k m o d    n + 1 ( k + 1 ) m o d    n + 1 ⋯ ( k + n − 2 ) m o d    n + 1 ( k + n − 1 ) m o d    n + 1 ) , g_k=\begin{pmatrix} 1 & 2 & 3 & \cdots & n-1 & n \\ k & k \mod n +1 & (k+1)\mod n+1 & \cdots & (k+n-2)\mod n+1 & (k+n-1)\mod n+1 \\ \end{pmatrix}, gk=(1k2kmodn+13(k+1)modn+1n1(k+n2)modn+1n(k+n1)modn+1),

容易证明, g k g_k gk 的等价类个数为 gcd ⁡ ( k − 1 , n ) \gcd(k-1,n) gcd(k1,n),因而只有循环节长度为等价类个数的涂色方案才在该方案下不变,即不动点。不妨令 f ( n , m ) f(n,m) f(n,m) 表示在长度为 n n n 的圆环上插入 m m m 个绿色珠子且相邻颜色不同的方案总数,那么对于置换 g i g_i gi,其方案数为 ∑ j = 0 ⌊ k l n ⌋ g ( l , j ) , l = gcd ⁡ ( i − 1 , n ) \displaystyle \sum_{j=0}^{\lfloor \frac{kl}{n} \rfloor} g(l,j),l=\gcd(i-1,n) j=0nklg(l,j),l=gcd(i1,n)。因而总的方案数为 ∑ i = 1 n ∑ j = 0 ⌊ k gcd ⁡ ( i − 1 , n ) n ⌋ g ( gcd ⁡ ( i − 1 , n ) , j ) \displaystyle \sum_{i=1}^{n} \sum_{j=0}^{\lfloor \frac{k\gcd(i-1,n)}{n} \rfloor} g(\gcd(i-1,n),j) i=1nj=0nkgcd(i1,n)g(gcd(i1,n),j)

考虑 g ( n , m ) g(n,m) g(n,m) 的求法。不妨枚举这 m m m 个绿色珠子的放法。由插板法,我们容易得到方案总数为 ( n − m m ) + ( n − m − 1 m ) \dbinom{n-m}{m}+\dbinom{n-m-1}{m} (mnm)+(mnm1),即绿色珠子是否出现在最后一位与否进行讨论。对于剩下的位置,满足条件的插入红蓝珠子的方案总数只有 2 2 2 种,因而这部分有 2 m 2^m 2m 种方法。因而 g ( n , m ) = 2 m ( ( n − m m ) + ( n − m − 1 m ) ) g(n,m)=2^m(\dbinom{n-m}{m}+\dbinom{n-m-1}{m}) g(n,m)=2m((mnm)+(mnm1))。注意 g ( n , 0 ) = 2 g(n,0)=2 g(n,0)=2

考虑化简这个式子。一个非常常见的手法是枚举其公约数 d d d 而非直接枚举 i i i。原式化简为 ∑ d ∣ n φ ( n d ) ∑ j = 0 ⌊ k d n ⌋ g ( d , j ) \displaystyle \sum_{d|n} \varphi(\frac{n}{d}) \sum_{j=0}^{\lfloor \frac{kd}{n} \rfloor} g(d,j) dnφ(dn)j=0nkdg(d,j)。令 f ( d ) = ∑ j = 0 ⌊ k d n ⌋ g ( d , j ) f(d)=\sum_{j=0}^{\lfloor \frac{kd}{n} \rfloor} g(d,j) f(d)=j=0nkdg(d,j),此处可以 O ( n ) O(n) O(n) 的计算。加入欧拉函数的意义是,公约数 d d d [ 1 , n ] [1,n] [1,n] 中会出现 φ ( n d ) \varphi(\frac{n}{d}) φ(dn) 次,因而 f ( d ) f(d) f(d) 会造成 φ ( n d ) \displaystyle \varphi(\frac{n}{d}) φ(dn) 次的贡献。

因而最后化简为 ∑ d ∣ n φ ( n d ) f ( d ) \displaystyle \sum_{d|n} \varphi(\frac{n}{d}) f(d) dnφ(dn)f(d)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值