NOI2016~2021 Solution Set
口胡一张嘴。
28/36。
NOI2016
优秀的拆分
极限得分:95(100?)。
在 的 间计数,然后相当于求 分别表示以第 个字符结尾有多少个 , 类似。
枚举 ,然后在 放置分割点,容易发现 过两个分割点。然后查 LCP 或 LCS 后缀数组解决。
网格
极限得分:92(写挂了)。
答案显然小于等于 。
判断 比较显然,然后现在是找整个图里面有没有割点。我们选取一些怪七八糟的关键点然后 Tarjan 就好了。主要是没有证明很麻烦,具体是如果该行有蛐蛐,那就正上下左右以及与之八联通的格子看成关键点,然后在选取所有的四个角的八联通格子当成关键点。
建图就 相同的建一块儿就好了。证明我不会,反正过掉了 =.=
循环之美
极限得分:100。
简单莫比乌斯反演。规律显然。主要是不想写式子,后面补吧。这个题在莫比乌斯反演一套里面算很简单的了。
算了写一发,理性猜测选出来的分数 要满足 且 ,证明显然(除了相当于小数点移位了)。那就化一波式子,中间那些杂七杂八的不要了,就是:
把里面的 拆成 和 ,并把只跟 有关的东西提出来。
那里面那坨东西非常好算只需要预处理 项。已经是一个类似于整除分块的形式。把不能整除分块的东西提出来做这个。总之可以拆成一个可以递归的形式,然后上杜教筛求 的前缀和。
吐槽,这种题出出来不是给差点金哥哥送分的?
区间
极限得分:100。
大概是说因为只跟最大的和最小的有关,我们将区间长度从大到小排序,选出来的区间定然是一段。离散化后线段树维护区间的覆盖,双指针移动就好了。
国王饮水记
极限得分:85。
每次操作显然必带结点 ,不然没有意义。
然后显然我们每次只能操作比当前更大的,可以得到一个 的 DP。当然你可以暂时先不管精度,然后倒推,可以做到 ,其中 的级别可以到 (不妙啊!这样就可以 85 了)。
这个 DP 不好优化,注意到每次选两个比较优,猜测选的大段个数和长度不会很大。那么先用上面不管精度最后倒推的方法,然后设个一个好的转移阈值(转移区间最大长度,之类的)就好了,总之能过。
旷野大计算
极限得分:40(吧?)。
我再也不抄题解了,对不起。这个题太牛逼了,找个时间好好做……
NOI2017
真是奇怪啊,为什么这场我是真的一个题也不会啊(划掉)
整数
极限得分:44(你妈啊,我是真不会啊)。
正负拆开,只需要比较前 位大小。暴力加均摊复杂度正确,压位计算就好了。
话说得好听啊,但是我怎么觉得这个狗题这么难写??
蚯蚓排队
极限得分:100(?有点大并。)
直接大暴力就好了…… 时间复杂度是 的,哈希就好了。
读题是难点。
泳池
极限得分:25(15?)。
不会,不会就是不会,怎么学也不会。
先容斥掉,小于等于 的减去小于 的。不用大于是因为上限有点太高了。
【等待施工中……】
游戏
极限得分:100。
到达 NOI2017 最垃圾题,游戏!太垃圾啦游戏。哎呀这不 2-SAT 吗,还是枚举一下 x
是什么吧家人们。
好吧也许蚯蚓排队最垃圾,但是差不多嘛。枚举 x
之后每个位置只有两种选择…… 做做就好了。
蔬菜
极限得分:80+12(?)(部分分真的送的很多吗?纯纯的口胡),有点病,我突然觉得我能过。
显然倒推就可以直接硬贪心,这个是普及组问题。现在有多组,因为这是个普及组问题,你要从 day 到 day ,就是直接挑 个收益最小的菜不要就好了……
然后似乎有个不用倒推的并查集贪心做法,非常的 exciting 啊!
NOI2018
归程
极限得分:100。
先对水位线从大到小排序建一个 Kruskal 重构树,然后求出 到其他的点的最短路,然后树上倍增找到可以到达的所有点(容易发现构成一棵子树),找个最小值就好了。
冒泡排序
极限得分:72(数数水平低于人均 T^T)。
显然达到下界就是一个数不会往左移了往右移。如果出现一个长度为 的下降子序列就非常的不优秀(假设为 ,在动 的时候 向左,动 的时候一定会和 交换)。
Dilworth 定理告诉我们最长链长度等于最大反链独立集,也就是有最多两个上升子序列。记 表示选了 个数,最大值为 ,那么向后要么加小于 的最小数,要么加大于 的数。
容易得到一个 的 DP 转移,前缀和优化之后可以得到 的转移。然后为了下面的分析,我们令 为 ,转移改为 。
然后注意到 的时候 为 。这个形式类似于网格图,每次只能向下或者向左动,不能越过 这条线。
考虑计数。我们假设前 位和 一样,并且选出的最大值为 ,没选到的最小值为 。
在选择第 位的时候,因为字典序要严格大于 ,因此第 位的数 。那么相当于有初始值 。前缀和优化到 。用网格图的角度就是 并且不越过 。简单分析就好了,这个问题很经典。
然后如果 说明后面已经没有前途了!退出吧,再算就错了。否则显然后面还能继续填。
你的名字
极限得分:68(字符串一点也不会)。
试图建出 的后缀自动机。先建出 的后缀自动机,后缀树部分和自动机转移部分显然不变,要变的只有 endpos。
那就线段树合并表示出 endpos,然后就是近似 的情况,暴力匹配就好了。不难写。
另外算答案,注意到答案是 的本质不同子串个数,减去 与 的本质不同公共子串个数。那么保证每次减去的都是本质不同的就好了…… 也就是在第 个位置的时候,匹配长度为 , 的后缀自动机上新增的结点的父亲结点表示的最大长度为 ,贡献就是 。
屠龙勇士
极限得分:100。
就是简单魔改 EXCRT,乱写就好了。
NOI2019
回家路线
极限得分:100(真的不是 O(mt) 吗)。
哦还真不是。虽然就也很,也许很简单吧,直接斜优!
好吧我过不了加强版。
序列
极限得分:64。
很久以前以为是个牛逼题,现在看比较傻…… 大概是说你先费用流建图建出来,可以直接反悔贪心做,分直接选一个 ,退一个 部点往虚点 的流,退一个虚点 往 部点的流。容易发现每次这种操作会让相同的下标多至少一个,先选出大的 个,再做 次上述过程就好了。遗憾的是口胡一张嘴啊,写不出来 100。
弹跳
极限得分:100。
k-d tree 优化建图,非常板子。
斗主地
极限得分:40。
前面的暴力和正解几乎没有关系。有个牛逼结论是一次函数洗牌之后还是一次函数,二次函数洗牌之后还是二次函数……如果有研究一下样例的意识就可以把结论猜出来了。
I 君的探险
极限得分:36(正在做)。
Subtask 1, 2:直接暴力。
Subtask 3:二进制分组。
NOI2020
美食家
很小,拆点。不要拆边,拆点就好。
图建出来,走到第一个点加一点愉悦值,写成一个类似于矩阵的形式,转化成 的广义矩阵乘法。美食节直接枚举就好了,中间可以预处理 的矩阵。
命运
先咕一下……之后重写一遍。
制作菜品
很萌萌。
也很萌萌阿。
然后 就不可爱了。我们想转化成两个 的形式这样就有解了,那就是看有没有 使得 。左右两边同时减 之后就是 ,这是 01 背包可以 bitset
优化。
老实说我觉得这个题挺简单阿,嗯哼?
超现实树
当初有点傻逼,写的题解也很蠢蛋阿。
先分析出如果每个点两个子树的大小 那么这个树无用的结论(我可以缩小,然后得到你没有的东西,是个天缺)。
然后有一个小 case 是单独一个根结点,和根结点左接一个点右边不接,根结点右接一个点左边不接,两边都接一个点等价。容易发现一个点是完备的,当且仅当:
- 存在一棵有用的树其是光秃秃的一个结点挂在这里;
- 下面四个情况同时满足:
- 有一棵有用的树,其右儿子是完备的,左子树为空;
- 有一棵有用的树,其左儿子是完备的,右子树为空;
- 有一棵有用的树,其右儿子完备,左子树大小为一;
- 有一棵有用的树,其左儿子完备,右子树大小为一。
暴力判断就好了。
NOI2021
应该是自 16 年以来最简单的一套。
轻重边
看起来可以用 LCT,实际确实可以,这是解法一。
解法二是像雨兔一样现场研究毛毛虫,但是这个代价可能比较高昂。
解法三就是普通树剖,但是 motivation 比较奇怪。
注意到路径上的所有点与其连接的所有边全变轻,考虑用颜色这样一个概念去描述。一个边是重的当且仅当两点颜色相同。这样就是链染色问题,整个问题变成了一个原题叫做 SDOI2011 染色,注意处理没染色的情况。这是个经典问题,重链线段树轻边暴力记下链头是什么就好了。
路径交点
Binet-Cauchy 定理简单运用。就是一堆矩阵乘起来求个行列式。
当然也可以 LGV。
关于动机的话,研究 (就是直接求行列式的 case)之后这个题就是萌萌题了。(至于为什么乘起来,可以看这个)。
庆典
先缩点。现在是个外向树。
是萌萌。
的话,, 两条路径。如果说 在 子树内那还要多一个 。可以用树剖打标记,下面都这么处理,每次清空就好。
按照上面的模式,如果存在一条 的路径,接下来的工作就只有找环。首先如果每条路径可以通过怪七八糟的手法可以从 重复到达 那路径就是好的,然后好的路径之间还可以有环。
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]);
}
}
量子通信
妙妙题,当初同步赛遗憾题。
很小,考虑将 位二进制数变成 位 进制数。
因为 ,所以如果可能在字典出现至少有一位是一样的。
因为数据随机所以期望复杂度正确啊。虽然我觉得有点小小的搞笑。
密码箱
当初我是小丑吗??
首先根本不需要管最简这个条件,因为上下一定互质。
然后是,将分母上下看成矩阵,密码箱的操作就像是在做线性变换,每次右乘一个矩阵。
然后对每个操作构造一个矩阵然后插在后面,这个矩阵非常好构造我就稍微摆了,W
是 ,E
在两种情况下都是 ,原来的线性变换是 。
想不到吧,这是个构造题。
机器人游戏
首先 的容斥写在样例里面了。
然后想平衡复杂度一脸折半。枚举钦定的最右边的指针 ,不会爆炸的机器人位移只有 。DP 定义为当前处理了前 个位置(每加一个位置容斥系数会多一个 ),在包括当前点的长为 的范围外有没有钦定的位置(要看后面的是不是一定有一个不变在),然后最近的 个位置钦定的状态为 。在 很小的时候也就 ,在 很大的时候是 ,这样复杂度是平衡的。
然后要考虑的是转移的系数以及 后面的元素。先考虑第一个问题,我们要求某个位置系数为 的机器人的个数(一个位置同时有状态 或 ;一个位置同时有 中的一个或 中的一个;系数为 的可以算有效机器人减去用掉的)。先用 bitset
存下以任意 为起点,使得第 个位置的状态是 (不变/变成相反/变成 /变成 )的机器人的位置状态。
那么首先如果当前在算的位置 不是 (在这里等价于小于 ,因为 显然使得 不变)或者是在 范围外有钦定的点,那么当前所有的机器人都会造成一个不变,比较显然;然后是可以算在这 个位置里面,所有被钦定的点的状态,使得当前这个位置变成状态 的机器人集合。这样分类讨论取个并就好了。
然后是处理 之后的元素,被限制的位置同样可以采用上面的方法解决,也就是枚举离 的距离 ,然后枚举状态算出使 的状态是 的机器人集合,又是一顿算就好。
这个题代码太你妈抽象了,,,,所以我贴份代码。代码很清楚阿,要仔细看看。
/*
他决定要“格”院子里的竹子。于是他搬了一条凳子坐在院子里,面对着竹子硬想了七天,结果因为头痛而宣告失败。
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构