2024 ICPC香港站 H.Mah-jong

这题的过题数竟然能算成金牌题,雀实很有思维量,这是浙大很爱出的一类麻将题,曾经在CCPC2023桂林站里也出现过,那道题是防AK题,也是那场里需要用到少见的斯坦纳树😱

现场赛的时候看到这道题目第一感觉是扫描线,结合了一下过题人数更加确定了,实际上根据这个权值大小是感觉可以卡常的一道题,我来讲解一下~:

给你一个序列,问其中包含多少种麻将子序列,即这个子序列中包含Chow结构和Pong结构,Chow结构是{x,x+1,x+2},Pong结构是{x,x,x},那么第一感觉解决所有子序列的计数问题我们想到的就是扫描线或者双指针这两种解法。

但是这道题目基本可以排除扫描线,扫什么玩意呢?

因此只能从这个麻将的本质入手,我们很容易发现对于三个一样的“Chow”结构,也就是{x,x+1,x+2}{x,x+1,x+2}{x,x+1,x+2}可以变换成三个不一样的“Pong”结构,也就是{x,x,x}{x+1,x+1,x+1}{x+2,x+2,x+2}

所以根据这样的性质,我们可以知道,一个麻将序列中,可视化的Chow结构相同的x不会超过2个,然后所有的Chow的x取值只会在[1,6]之间,那么关于所有不同的麻将序列中的Chow的种类不超过3^6个。

所以我们枚举Chow的序列一种729个,然后我们使用双指针寻找出大序列中包含某个种类的子序列的个数,容易推出解决这样一个问题是可以进行双指针算法的,这里直接给出题解的原话:

那么直接上代码了:

#include<bits/stdc++.h>
#define Alex std::ios::sync_with_stdio(false),std::cin.tie(0),std::cout.tie(0);
#define int long long
#define double long double
#define L(i,a,b) for(int i = a;i <= b;i++)
#define R(i,a,b) for(int i = a;i >= b;i--)

const int QAQ = 0;
const int mod = 1e9 + 7;
const int P = 998244353;
const double eps = 1e-10;
const double pi = std::acos(-1.0);

//using namespace std;

inline void Fre()
{
	int liujiahui = 142857;
	freopen("circles.in","r",stdin);
	freopen("circles.out","w",stdout);
}

inline int qpow(int x,int y)
{
	int res = 1;
	while(y)
	{
		if(y & 1) res = (res * x) % mod;
		x = (x * x) % mod;
		y >>= 1;
	}
	return res;
}

inline int Inv(int x)
{
	return qpow(x,mod - 2);
}

inline int Sign(double x)
{
	if(-eps < x && x < eps) return 0;
	return x < 0 ? -1 : 1;
}

int n,a[200005],tong[200005][10];
int f[500005],g[500005],h[500005];

signed main()
{
//	Fre();
	Alex;
	int _;
	_ = 1;
	std::cin>>_;
	while(_--)
	{
		std::cin>>n;
		for(int i = 1;i <= n;i++) std::cin>>a[i];
		for(int i = 0;i <= n;i++)
		for(int j = 1;j <= 8;j++) tong[i][j] = 0;
		for(int i = 1;i <= n;i++)
		{
			for(int j = 1;j <= 8;j++) tong[i][j] = tong[i - 1][j];
			tong[i][a[i]] = tong[i - 1][a[i]] + 1;
		}
		for(int i = 0;i <= n;i++) f[i] = 0;
		for(int i = 1;i <= n;i++)
		{
			int s = 0;
			for(int j = 1;j <= 8;j++) s = s * 3 + tong[i][j] % 3;
			f[i] = s;
		}
		int ans = 0;
		for(int bit = 0;bit < 729;bit++)
		{
			int g[10] = {0,0,0,0,0,0,0,0,0,0};
			int t = bit;
			for(int i = 1;i <= 6;i++)
			{
				int x = t % 3;
				t = t / 3;
				g[i] = g[i] + x;
				g[i + 1] = g[i + 1] + x;
				g[i + 2] = g[i + 2] + x;
			}
			for(int i = 0;i <= n;i++) h[f[i]]++;
			int l = 0;
			for(int i = 1;i <= n;i++)
			{
				while(l <= i) h[f[l++]]--;
				int s = 0;
				for(int j = 1;j <= 8;j++)
				{
					while(l <= n && tong[l][j] < tong[i - 1][j] + g[j])
					{
						h[f[l++]]--;
					}
					s = s * 3 + (tong[i - 1][j] + g[j]) % 3;
				}
				if(l > n) break;
				ans = ans + h[s];
			}
		}
		std::cout<<ans<<'\n';
	}
	return QAQ;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值