NOI2016~2021 Solution Set

口胡一张嘴。

28/36。

NOI2016

优秀的拆分

极限得分:95(100?)。

\(\text{AABB}\)\(\text{AB}\) 间计数,然后相当于求 \(f_i,g_i\) 分别表示以第 \(i\) 个字符结尾有多少个 \(\text{AA}\)\(g_i\) 类似。

枚举 \(|A|\),然后在 \(|A|,2|A|,\cdots\) 放置分割点,容易发现 \(\text{AA}\) 过两个分割点。然后查 LCP 或 LCS 后缀数组解决。

网格

极限得分:92(写挂了)。

答案显然小于等于 \(2\)

判断 \(-1,0\) 比较显然,然后现在是找整个图里面有没有割点。我们选取一些怪七八糟的关键点然后 Tarjan 就好了。主要是没有证明很麻烦,具体是如果该行有蛐蛐,那就正上下左右以及与之八联通的格子看成关键点,然后在选取所有的四个角的八联通格子当成关键点。

建图就 \(x,y\) 相同的建一块儿就好了。证明我不会,反正过掉了 =.=

循环之美

极限得分:100。

简单莫比乌斯反演。规律显然。主要是不想写式子,后面补吧。这个题在莫比乌斯反演一套里面算很简单的了。

算了写一发,理性猜测选出来的分数 \(\dfrac{i}{j}\) 要满足 \(j ⊥ k\)\(j ⊥ i\),证明显然(除了相当于小数点移位了)。那就化一波式子,中间那些杂七杂八的不要了,就是:

\[\sum_d \mu(d) \left\lfloor \dfrac{n}{d} \right\rfloor \sum_{j=1}^{m/d} [jd ⊥ k] \]

把里面的 \([jd ⊥ k]\) 拆成 \([j ⊥ k]\)\([d ⊥ k]\),并把只跟 \(d\) 有关的东西提出来。

那里面那坨东西非常好算只需要预处理 \(O(k)\) 项。已经是一个类似于整除分块的形式。把不能整除分块的东西提出来做这个。总之可以拆成一个可以递归的形式,然后上杜教筛求 \(\mu\) 的前缀和。

吐槽,这种题出出来不是给差点金哥哥送分的?

区间

极限得分:100。

大概是说因为只跟最大的和最小的有关,我们将区间长度从大到小排序,选出来的区间定然是一段。离散化后线段树维护区间的覆盖,双指针移动就好了。

国王饮水记

极限得分:85。

每次操作显然必带结点 \(1\),不然没有意义。

然后显然我们每次只能操作比当前更大的,可以得到一个 \(O(n^2kp)\) 的 DP。当然你可以暂时先不管精度,然后倒推,可以做到 \(O(n^2k)\),其中 \(k\) 的级别可以到 \(O(n)\)(不妙啊!这样就可以 85 了)。

这个 DP 不好优化,注意到每次选两个比较优,猜测选的大段个数和长度不会很大。那么先用上面不管精度最后倒推的方法,然后设个一个好的转移阈值(转移区间最大长度,之类的)就好了,总之能过。

旷野大计算

极限得分:40(吧?)。

我再也不抄题解了,对不起。这个题太牛逼了,找个时间好好做……

NOI2017

真是奇怪啊,为什么这场我是真的一个题也不会啊(划掉)

整数

极限得分:44(你妈啊,我是真不会啊)。

正负拆开只需要比较前 \(\bold{k}\) 位大小暴力加均摊复杂度正确,压位计算就好了。

话说得好听啊,但是我怎么觉得这个狗题这么难写??

蚯蚓排队

极限得分:100(?有点大并。)

直接大暴力就好了…… 时间复杂度是 \(O(mk^2)\) 的,哈希就好了。

读题是难点。

泳池

极限得分:25(15?)。

不会,不会就是不会,怎么学也不会。

先容斥掉,小于等于 \(K\) 的减去小于 \(K\) 的。不用大于是因为上限有点太高了。

【等待施工中……】

游戏

极限得分:100。

到达 NOI2017 最垃圾题,游戏!太垃圾啦游戏。哎呀这不 2-SAT 吗,还是枚举一下 x 是什么吧家人们。

好吧也许蚯蚓排队最垃圾,但是差不多嘛。枚举 x 之后每个位置只有两种选择…… 做做就好了。

蔬菜

极限得分:80+12(?)(部分分真的送的很多吗?纯纯的口胡),有点病,我突然觉得我能过。

显然倒推就可以直接硬贪心,这个是普及组问题。现在有多组,因为这是个普及组问题,你要从 day \(p\) 到 day \(p-1\),就是直接挑 \(m\) 个收益最小的菜不要就好了……

然后似乎有个不用倒推的并查集贪心做法,非常的 exciting 啊!

NOI2018

归程

极限得分:100。

先对水位线从大到小排序建一个 Kruskal 重构树,然后求出 \(1\) 到其他的点的最短路,然后树上倍增找到可以到达的所有点(容易发现构成一棵子树),找个最小值就好了。

冒泡排序

极限得分:72(数数水平低于人均 T^T)。

显然达到下界就是一个数不会往左移了往右移。如果出现一个长度为 \(3\) 的下降子序列就非常的不优秀(假设为 \(a,b,c\),在动 \(a\) 的时候 \(b\) 向左,动 \(b\) 的时候一定会和 \(c\) 交换)。

Dilworth 定理告诉我们最长链长度等于最大反链独立集,也就是有最多两个上升子序列。记 \(dp_{i,j}\) 表示选了 \(i\) 个数,最大值为 \(j\),那么向后要么加小于 \(j\) 的最小数,要么加大于 \(j\) 的数。

容易得到一个 \(O(n^3)\) 的 DP 转移,前缀和优化之后可以得到 \(dp_{i,j} = dp_{i-1,j}+dp_{i,j-1}\) 的转移。然后为了下面的分析,我们令 \(dp_{n, n}\)\(1\),转移改为 \(dp_{i,j}=dp_{i+1,j}+dp_{i,j+1}\)

然后注意到 \(i>j\) 的时候 \(dp_{i,j}\)\(0\)。这个形式类似于网格图,每次只能向下或者向左动,不能越过 \(y=x\) 这条线。

考虑计数。我们假设前 \(i-1\) 位和 \(q\) 一样,并且选出的最大值为 \(v\),没选到的最小值为 \(u\)

在选择第 \(i\) 位的时候,因为字典序要严格大于 \(q\),因此第 \(i\) 位的数 \(k > \max(q_i,v)\)。那么相当于有初始值 \(dp_{i,\max(q_i,v)+1 \cdots n} \gets 1\)。前缀和优化到 \(dp_{i-1,\max(q_i,v)+1} \gets 1\)。用网格图的角度就是 \((n,n) \to (i-1,\max(q_i,v)+1)\) 并且不越过 \(y=x\)。简单分析就好了,这个问题很经典。

然后如果 \(u<q_i<v\) 说明后面已经没有前途了!退出吧,再算就错了。否则显然后面还能继续填。

你的名字

极限得分:68(字符串一点也不会)。

试图建出 \(s[l\cdots r]\) 的后缀自动机。先建出 \(s\) 的后缀自动机,后缀树部分和自动机转移部分显然不变,要变的只有 endpos。

那就线段树合并表示出 endpos,然后就是近似 \(l=1,r=|s|\) 的情况,暴力匹配就好了。不难写。

另外算答案,注意到答案是 \(t\) 的本质不同子串个数,减去 \(s[l \cdots r]\)\(t\) 的本质不同公共子串个数。那么保证每次减去的都是本质不同的就好了…… 也就是在第 \(i\) 个位置的时候,匹配长度为 \(p\)\(t\) 的后缀自动机上新增的结点的父亲结点表示的最大长度为 \(q\),贡献就是 \(i-\max(p,q)\)

屠龙勇士

极限得分:100。

就是简单魔改 EXCRT,乱写就好了。

NOI2019

回家路线

极限得分:100(真的不是 O(mt) 吗)。

哦还真不是。虽然就也很,也许很简单吧,直接斜优!

好吧我过不了加强版。

序列

极限得分:64。

很久以前以为是个牛逼题,现在看比较傻…… 大概是说你先费用流建图建出来,可以直接反悔贪心做,分直接选一个 \(A_i,B_i\),退一个 \(A\) 部点往虚点 \(A\) 的流,退一个虚点 \(B\)\(B\) 部点的流。容易发现每次这种操作会让相同的下标多至少一个,先选出大的 \(K-L\) 个,再做 \(L\) 次上述过程就好了。遗憾的是口胡一张嘴啊,写不出来 100。

弹跳

极限得分:100。

k-d tree 优化建图,非常板子。

斗主地

极限得分:40。

前面的暴力和正解几乎没有关系。有个牛逼结论是一次函数洗牌之后还是一次函数,二次函数洗牌之后还是二次函数……如果有研究一下样例的意识就可以把结论猜出来了。

I 君的探险

极限得分:36(正在做)。

Subtask 1, 2:直接暴力。

Subtask 3:二进制分组。

NOI2020

美食家

\(w_i\) 很小,拆点。不要拆边,拆点就好。

图建出来,走到第一个点加一点愉悦值,写成一个类似于矩阵的形式,转化成 \((\max,+)\) 的广义矩阵乘法。美食节直接枚举就好了,中间可以预处理 \(2^k\) 的矩阵。

命运

先咕一下……之后重写一遍。

制作菜品

\(n=m\) 很萌萌。

\(m=n-1\) 也很萌萌阿。

然后 \(m=n-2\) 就不可爱了。我们想转化成两个 \(m_i = n_i - 1\) 的形式这样就有解了,那就是看有没有 \(S\) 使得 \(\displaystyle \sum_{i \in S} d_i = (|S|-1)k\)。左右两边同时减 \(|S|k\) 之后就是 \(\displaystyle \sum_{i \in S} d_i-k = -k\),这是 01 背包可以 bitset 优化。

老实说我觉得这个题挺简单阿,嗯哼?

超现实树

当初有点傻逼,写的题解也很蠢蛋阿。

先分析出如果每个点两个子树的大小 \(\min > 1\) 那么这个树无用的结论(我可以缩小,然后得到你没有的东西,是个天缺)。

然后有一个小 case 是单独一个根结点,和根结点左接一个点右边不接,根结点右接一个点左边不接,两边都接一个点等价。容易发现一个点是完备的,当且仅当:

  • 存在一棵有用的树其是光秃秃的一个结点挂在这里;
  • 下面四个情况同时满足:
    • 有一棵有用的树,其右儿子是完备的,左子树为空;
    • 有一棵有用的树,其左儿子是完备的,右子树为空;
    • 有一棵有用的树,其右儿子完备,左子树大小为一;
    • 有一棵有用的树,其左儿子完备,右子树大小为一。

暴力判断就好了。

NOI2021

应该是自 16 年以来最简单的一套。

轻重边

看起来可以用 LCT,实际确实可以,这是解法一。

解法二是像雨兔一样现场研究毛毛虫,但是这个代价可能比较高昂。

解法三就是普通树剖,但是 motivation 比较奇怪。

注意到路径上的所有点与其连接的所有边全变轻,考虑用颜色这样一个概念去描述。一个边是重的当且仅当两点颜色相同。这样就是链染色问题,整个问题变成了一个原题叫做 SDOI2011 染色,注意处理没染色的情况。这是个经典问题,重链线段树轻边暴力记下链头是什么就好了。

路径交点

Binet-Cauchy 定理简单运用。就是一堆矩阵乘起来求个行列式。

当然也可以 LGV。

关于动机的话,研究 \(k=2\)(就是直接求行列式的 case)之后这个题就是萌萌题了。(至于为什么乘起来,可以看这个)。

庆典

先缩点。现在是个外向树。

\(k=0\) 是萌萌。

\(k=1\) 的话,\(u \to v\)\(u \to u_1 \to v_1 \to v\) 两条路径。如果说 \(u\)\(v_1\) 子树内那还要多一个 \(v_1 \to u\)。可以用树剖打标记,下面都这么处理,每次清空就好。

\(k=2\) 按照上面的模式,如果存在一条 \(u\to v\) 的路径,接下来的工作就只有找环。首先如果每条路径可以通过怪七八糟的手法可以从 \(u/v\) 重复到达 \(u/v\) 那路径就是好的,然后好的路径之间还可以有环。

				st=r[0]=r[1]=false;
				if(In(u,v))	st=true,modifyChain(u,v);
				for(int i=0;i<=1;++i)
				{
					if(In(u,adu[i]) && In(adv[i],v))	modifyChain(u,adu[i]),modifyChain(adv[i],v),r[i]=st=true;
					if(In(u,adu[i]) && In(adv[i],adu[i^1]) && In(adv[i^1],v))	modifyChain(u,adu[i]),modifyChain(adv[i],adu[i^1]),modifyChain(adv[i^1],v),r[i]=r[i^1]=st=true;
				}
				if(st)
				{
					for(int i=0;i<=1;++i)
					{
						if(In(v,adu[i]) && In(adv[i],u))	modifyChain(v,adu[i]),modifyChain(adv[i],u),r[i]=true;
						if(In(u,adu[i]) && In(adv[i],u))	modifyChain(adu[i],adv[i]),r[i]=true;
						if(In(v,adu[i]) && In(adv[i],v))	modifyChain(adu[i],adv[i]),r[i]=true;
						if(In(v,adu[i]) && In(adv[i],adu[i^1]) && In(adv[i^1],v))	modifyChain(v,adu[i]),modifyChain(adv[i],adu[i^1]),modifyChain(adv[i^1],v),r[i]=r[i^1]=true;
						if(In(v,adu[i]) && In(adv[i],adu[i^1]) && In(adv[i^1],u))	modifyChain(v,adu[i]),modifyChain(adv[i],adu[i^1]),modifyChain(adv[i^1],u),r[i]=r[i^1]=true;
						if(In(u,adu[i]) && In(adv[i],adu[i^1]) && In(adv[i^1],u))	modifyChain(u,adu[i]),modifyChain(adv[i],adu[i^1]),modifyChain(adv[i^1],u),r[i]=r[i^1]=true;
					}
					for(int i=0;i<=1;++i)
					{
						if(r[i] && In(adv[i],adu[i^1]) && In(adv[i^1],adu[i]))	modifyChain(adv[i],adu[i^1]),modifyChain(adv[i^1],adu[i]);
						if(r[i] && In(adv[i],adu[i]))	modifyChain(adv[i],adu[i]);
					}
				}

量子通信

妙妙题,当初同步赛遗憾题。

\(k\) 很小,考虑将 \(256\) 位二进制数变成 \(16\)\(2^{16}\) 进制数。

因为 \(k \leq 15\),所以如果可能在字典出现至少有一位是一样的。

因为数据随机所以期望复杂度正确啊。虽然我觉得有点小小的搞笑。

密码箱

当初我是小丑吗??

首先根本不需要管最简这个条件,因为上下一定互质。

然后是,将分母上下看成矩阵,密码箱的操作就像是在做线性变换,每次右乘一个矩阵。

然后对每个操作构造一个矩阵然后插在后面,这个矩阵非常好构造我就稍微摆了,W\(\begin{bmatrix} 1 & 1 \\ 0 & 1\end{bmatrix}\)E 在两种情况下都是 \(\begin{bmatrix} 2 & -1 \\ 1 & 0\end{bmatrix}\),原来的线性变换是 \(\begin{bmatrix} a & 1 \\ 1 & 0\end{bmatrix}\)

想不到吧,这是个构造题

机器人游戏

首先 \(O(nm2^n)\) 的容斥写在样例里面了。

然后想平衡复杂度一脸折半。枚举钦定的最右边的指针 \(r\),不会爆炸的机器人位移只有 \(n-r\)。DP 定义为当前处理了前 \(i\) 个位置(每加一个位置容斥系数会多一个 \(-1\)),在包括当前点的长为 \(n-r\)范围外有没有钦定的位置(要看后面的是不是一定有一个不变在),然后最近的 \(n-r\) 个位置钦定的状态为 \(S\)。在 \(r\) 很小的时候也就 \(r2^r\),在 \(r\) 很大的时候是 \(r2^{n-r}\),这样复杂度是平衡的。

然后要考虑的是转移的系数以及 \(r\) 后面的元素。先考虑第一个问题,我们要求某个位置系数为 \(1,2,3\) 的机器人的个数(一个位置同时有状态 \(0,1\)\(2,3\);一个位置同时有 \(0,1\) 中的一个或 \(2,3\) 中的一个;系数为 \(3\) 的可以算有效机器人减去用掉的)。先用 bitset 存下以任意 \(s\) 为起点,使得第 \(s+i\) 个位置的状态是 \(0/1/2/3\)(不变/变成相反/变成 \(0\)/变成 \(1\))的机器人的位置状态。

那么首先如果当前在算的位置 \(p\) 不是 \(r\)(在这里等价于小于 \(r\),因为 \(r\) 显然使得 \(p\) 不变)或者是在 \(n-r\) 范围外有钦定的点,那么当前所有的机器人都会造成一个不变,比较显然;然后是可以算在这 \(n-r\) 个位置里面,所有被钦定的点的状态,使得当前这个位置变成状态 \(0/1/2/3\) 的机器人集合。这样分类讨论取个并就好了。

然后是处理 \(r\) 之后的元素,被限制的位置同样可以采用上面的方法解决,也就是枚举离 \(r\) 的距离 \(s\),然后枚举状态算出使 \(s\) 的状态是 \(0/1/2/3\) 的机器人集合,又是一顿算就好。

这个题代码太你妈抽象了,,,,所以我贴份代码。代码很清楚阿,要仔细看看。

/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
DONT NEVER AROUND . //
*/
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
typedef double DB;
typedef unsigned long long ULL;
const int MOD=1e9+7;
inline int Add(int u,int v){return u+v>=MOD?u+v-MOD:u+v;}
inline int Sub(int u,int v){return u-v>=0?u-v:u-v+MOD;}
inline int Mul(int u,int v){return LL(u)*LL(v)%MOD;}
inline int add(int &u,int v){return u=Add(u,v);}
inline int sub(int &u,int v){return u=Sub(u,v);}
inline int mul(int &u,int v){return u=Mul(u,v);}
int QuickPow(int x,int p=MOD-2)
{
	if(p<0)	p+=MOD-1;
	int ans=1,base=x;
	while(p)
	{
		if(p&1)	ans=Mul(ans,base);
		base=Mul(base,base);
		p>>=1;
	}
	return ans;
}
inline int lowbit(int x){return x&(-x);}
inline int popcount(int x){int ret=0;while(x)	++ret,x^=lowbit(x);return ret;}
typedef bitset<1005> BT;
BT F[(1<<17)][4];
BT Cv[35][4];
BT US;
int n,m;
char str[1005][105];
int v[1005][105],len[1005],mv[1005];
int dp[35][2][(1<<17)];
int pw2[1005],pw3[1005];
vector<int> uid[100];
int main(){
	scanf("%d %d",&n,&m);
	for(int i=1;i<=m;++i)	scanf("%s",str[i]+1),len[i]=strlen(str[i]+1);
	pw2[0]=pw3[0]=1;
	for(int i=1;i<=m;++i)	pw2[i]=Add(pw2[i-1],pw2[i-1]),pw3[i]=Add(Add(pw3[i-1],pw3[i-1]),pw3[i-1]);
	for(int i=1;i<=m;++i)
	{
		for(int j=1;j<=len[i];++j)
		{
			if(str[i][j]=='R')	++mv[i];
			else if(str[i][j]=='*')	v[i][mv[i]]^=1;
			else if(str[i][j]=='0')	v[i][mv[i]]=2;
			else	v[i][mv[i]]=3;
		}
		uid[mv[i]].push_back(i);
	}
	US.flip();
	int ans=0,siz=0;
	for(int r=n;r;--r)
	{
		siz+=int(uid[n-r].size());
		for(auto id:uid[n-r])	for(int i=0;i<=n+2;++i)	Cv[i][v[id][i]][id]=1;
		memset(dp,0,sizeof dp);
		dp[0][0][0]=1;
		for(int p=1;p<=r;++p)
		{
			int Tup=1<<min(p-1,n-r+1),Sup=1<<min(p,n-r+1),U=Sup-1;
			for(int c=0;c<=1;++c)
			{
				for(int S=0;S<Tup;++S)
				{
					int cn=(c || ((S<<1)>U));
					if(p^r)	add(dp[p][cn][(S<<1)&U],dp[p-1][c][S]); // r 被强制选。
					sub(dp[p][cn][((S<<1)|1)&U],dp[p-1][c][S]);
				}
			}
			for(int c=0;c<=1;++c)
			{
				int ct=c || (p^r);
				for(int j=0;j<=min(n-r+1,p)-1;++j)	for(int t=ct;t<=3;++t)	F[(1<<j)][t]=Cv[j][t];
				for(int S=0;S<Sup;++S)
				{
					if(S)	for(int t=ct;t<=3;++t)	F[S][t]=F[S^lowbit(S)][t]|F[lowbit(S)][t];
					if(ct)
					{
						BT v1=F[S][1]|(F[S][2]&F[S][3]),v2=(F[S][2]|F[S][3])&(US^v1); // 这个时候 F[S][0] = U.
						int p1=v1.count(),p2=v2.count();
						mul(dp[p][c][S],Mul(pw2[p2],pw3[siz-p1-p2]));
					}
					else
					{
						BT v1=(F[S][0]&F[S][1])|(F[S][2]&F[S][3]),v2=(F[S][0]|F[S][1])&(F[S][2]|F[S][3])&(US^v1);
						int p1=v1.count(),p2=v2.count();
						mul(dp[p][c][S],Mul(pw2[p2],pw3[siz-p1-p2]));
					}
				}
			}
		}
		int dis=min(n-r+1,r),Sup=1<<dis;
		for(int c=0;c<=1;++c)
		{
			for(int s=1;s<=n-r;++s)
			{
				for(int j=0;j<dis;++j)	for(int t=c;t<=3;++t)	F[(1<<j)][t]=Cv[j+s][t];
				for(int S=0;S<Sup;++S)
				{
					if(S)	for(int t=c;t<=3;++t)	F[S][t]=F[S^lowbit(S)][t]|F[lowbit(S)][t];
					if(c)
					{
						BT v1=F[S][1]|(F[S][2]&F[S][3]),v2=(F[S][2]|F[S][3])&(US^v1);
						/*
						 这个时候 F[S][0] = U.
						*/
						int p1=v1.count(),p2=v2.count();
						mul(dp[r][c][S],Mul(pw2[p2],pw3[siz-p1-p2]));
					}
					else
					{
						BT v1=(F[S][0]&F[S][1])|(F[S][2]&F[S][3]),v2=(F[S][0]|F[S][1])&(F[S][2]|F[S][3])&(US^v1);
						int p1=v1.count(),p2=v2.count();
						mul(dp[r][c][S],Mul(pw2[p2],pw3[siz-p1-p2]));
					}
				}
			}
		}
		for(int c=0;c<=1;++c)	for(int S=0;S<Sup;++S)	sub(ans,dp[r][c][S]);
	}
	printf("%d",ans);
	return 0;
}
posted @ 2022-05-25 23:01  SyadouHayami  阅读(93)  评论(0编辑  收藏  举报

My Castle Town.