[WC2022-DMY]我被卷飞了(stars)

89 篇文章 0 订阅
23 篇文章 0 订阅

题目

题目背景
O n e I n D a r k \sf OneInDark OneInDark 经过不懈努力,终于考入了心仪的哈尔滨佛学院,他再也不用忍受 O U Y E \sf OUYE OUYE 的强大了!在这里打坐的每一天, O n e I n D a r k \sf OneInDark OneInDark 都会想起以前被 O U Y E \sf OUYE OUYE 吊打的日子……

题目描述
一个学生每天要上 k k k 节课。课程用一个 1 0 9 10^9 109 以内的正整数进行编号,则一个学生要上的课可以用一个有序 k k k 元组 ( p 1 , p 2 , … , p k ) (p_1,p_2,\dots,p_k) (p1,p2,,pk) 表示,其中 p i p_i pi 是今天的第 i i i 个时段所上的课的编号。

众所周知,一旦和 O U Y E \sf OUYE OUYE 在同一个时段上同一节课,就会被 O U Y E \sf OUYE OUYE 无情地 “卷” 飞。定义一群学生(学生的集合)为「报团取暖」,当且仅当 O U Y E \sf OUYE OUYE 存在一种选课方式,使得每名学生都会被 “卷” 飞至少一次。

现在给出 n n n 个学生的课程表。请问,有多少个子段(连续子序列)满足:该子段中的学生的集合是「抱团取暖」?

数据范围与提示
n ⩽ 1 0 5 n\leqslant 10^5 n105 k ⩽ 5 k\leqslant 5 k5

思路

求解计数问题,先想判定问题;或者将其理解为自动机也可以。

判定问题:可以考虑贪心——不知道该叫什么名——确定 O U Y E \sf OUYE OUYE 的每个时段的课程。遇到一个没被 “卷” 飞的学生,就枚举一个时段,让 O U Y E \sf OUYE OUYE 去上那节课把这个学生 “卷” 飞;否则就保持原状态。用 2 k 2^k 2k 记录状态。

计数问题:用 O ( k ! ) \mathcal O(k!) O(k!) 记录每次确定的时段。由单调性,可用 d p \tt dp dp 值存储能延伸到的最远位置。如何转移?

显然我们给当前点用了一个时段。后面一段学生,都不在乎这个时段;直到某个位置,是只能用这个时段去 “卷” 飞的(其他时段都遇不到 O U Y E \sf OUYE OUYE)。相当于把第一次使用这个时段的位置后移了;肯定还是对应 O ( k ! ) \mathcal O(k!) O(k!) 中的某个顺序。枚举它就能找到。

时间复杂度 O ( n k ⋅ k ! ) \mathcal O(nk\cdot k!) O(nkk!),看上去挺可怕的,但是能过,不管了。

代码

#include <cstdio>
#include <iostream>
#include <algorithm>
#include <cstring>
#include <cctype>
using namespace std;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
typedef long long llong;
inline int readint(){
	int a = 0, c = getchar(), f = 1;
	for(; !isdigit(c); c=getchar())
		if(c == '-') f = -f;
	for(; isdigit(c); c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}

const int MAXN = 325;
namespace Trie{
	int ch[MAXN*5][5], cntNode, val[MAXN*5];
	void insert(int x[],int n,int id){
		int o = 0;
		for(int i=0; i!=n; ++i){
			int &d = ch[o][x[i]];
			o = (d ? d : (d = ++ cntNode));
		}
		val[o] = id;
	}
	int query(int o,int x[],int n){
		for(int i=0; i!=n; ++i) o = ch[o][x[i]];
		return val[o];
	}
}

const int MAXK = 5;
int a[MAXN+1][MAXK], len[MAXN+1], tot;
void scan(int t,int used,const int &k){
	static int now[MAXK] = {};
	++ tot, Trie::insert(now,t,tot);
	memcpy(a[tot],now,t<<2), len[tot] = t;
	for(int i=0; i!=k; ++i) if((~used)>>i&1)
		now[t] = i, scan(t+1,used^(1<<i),k);
}

int dp[2][MAXN+1]; // the left boundary
int tmp[MAXK], v[100001][MAXK];
int main(){
	int n = readint(), k = readint();
	tot = -1, scan(0,0,k); // pre-compute
	long long ans = 0;
	for(int i=1,fr=0; i<=n; ++i,fr^=1){
		rep(j,0,k-1) v[i][j] = readint();
		int lb = dp[i&1][0] = i; // no "tip" to use
		for(int j=1; j<=tot; ++j){
			memcpy(tmp,a[j],len[j]<<2);
			const int &me = a[j][0]; // first tip
			int o = 0, l = 0; // walk on trie
			while(l != len[j]-1 && dp[fr][o] && v[dp[fr][o]][me] != v[i][me])
				tmp[l] = a[j][l+1], ++ l, o = Trie::ch[o][a[j][l]];
			if(!dp[fr][o]){ dp[i&1][j] = lb = 0; continue; } // no boundary
			if(v[dp[fr][o]][me] != v[i][me]){
				lb = min(lb,dp[fr][o]); // get min
				dp[i&1][j] = dp[fr][o]; continue; // run out
			}
			tmp[l] = me; o = Trie::query(o,tmp+l,len[j]-l);
			dp[i&1][j] = dp[fr][o]; lb = min(lb,dp[i&1][j]);
		}
		ans += i-lb; // (lb,i] is acceptable
	}
	printf("%lld\n",ans);
	return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值