NOIP模拟88(多校21)
前言
对于这套题的总体感觉就是难,然后就是自己很菜。。。
对于 T1 考试时只会一个最垃圾的背包,考完之后对于思路这一块也不是很顺利,大概这就是薄弱的地方吧。
然后 T2 是比较简单的一道题了,但是考试的时候只是拿了一点部分分,对于正解的思路也只有一点点。
大概和看错数据范围有一点关系吧,主要还是菜。
T3 T4 是那种我最害怕的题目了,T3 一看感觉根本不可做,然后一点思路没有。
T4 的博弈论更别提,推了个傻瓜特殊性质就跑路了。。。
T1 按位或
解题思路
容斥DP好题。
先来考虑复杂度为 \(3^{log_2t}logn\) 的做法。
那么由于每一次操作都是等价的因此我们考虑计算出每一次操作可能的贡献,然后做一个 \(n\) 次方进行求解。
对于 \(t\) 可以背包 DP 每一个二进制位,算出对于 \(\bmod\) 3 不同余数的方案,然后直接整一个 \(n\) 次方???
当然没有那么简单,发现有一些情况是不合法的,有一些二进制位的组合可能或在一起并不是我们要求的 \(t\) 但是他们每一个数字确实是 3 的倍数。
于是我们需要枚举 \(t\) 的所有子集,进行容斥,最后需要再容斥一下 0 的情况。code
但是显然这不是正解的复杂度。。。
于是我们可以考虑每一个二进制位的贡献,对于 \(2^i\) 如果 \(i\) 是奇数,那么 \(2^i\;\bmod\;3=2\) 否则就是 1 。
那么对于所有奇数或者说所有偶数而言他们对于答案的贡献都是相同的。
于是我们可以直接枚举奇数偶数位选择多少个,背包 DP 算出对应方案数,然后类似于上面的不合法情况直接容斥。
最后依旧做一个 \(n\) 次方进行求解,也需要考虑 0 的情况。
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
#define count __builtin_popcount
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=70,mod=998244353;
int n,m,ans,cnt,cnt1,cnt2,f[3],g[3],c[N][N];
int power(int x,int y,int p=mod)
{
int temp=1; n%=(p-1);
while(y)
{
if(y&1) temp=temp*x%p;
x=x*x%p; y>>=1;
}
return temp;
}
void solve(int x,int y)
{
f[1]=f[2]=0; f[0]=1;
for(int i=1;i<=x;i++)
{
for(int j=0;j<=2;j++) g[j]=f[j];
for(int j=0;j<=2;j++) f[(j+1)%3]=(f[(j+1)%3]+g[j])%mod;
}
for(int i=1;i<=y;i++)
{
for(int j=0;j<=2;j++) g[j]=f[j];
for(int j=0;j<=2;j++) f[(j+2)%3]=(f[(j+2)%3]+g[j])%mod;
}
}
#undef int
int main()
{
#define int long long
freopen("or.in","r",stdin); freopen("or.out","w",stdout);
n=read(); m=read();
int temp=m,pos=0;
while(temp)
{
if(temp&1) cnt++,cnt1+=pos&1;
pos++; temp>>=1;
}
c[0][0]=1;
for(int i=1;i<=cnt;i++)
{
c[i][0]=c[i][i]=1;
for(int j=1;j<i;j++)
c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
}
for(int i=1,bas=(cnt&1)?1:-1;i<=cnt;i++,bas=-bas)
for(int j=0;j<=min(i,cnt1);j++)
solve(i-j,j),ans=(ans+bas*power(f[0],n)*c[cnt1][j]%mod*c[cnt-cnt1][i-j]%mod+mod)%mod;
printf("%lld",(ans+((cnt&1)?-1:1)+mod)%mod);
return 0;
}
T2 最短路径
解题思路
并不是一道难题。
显然对于固定的 \(k\) 个点最短路径的起点终点一定是其中的两个。
假设最后必须回到起点,一条最短路径就可以视为遍历这 \(k\) 个点所需要经过的边的数量乘上 2 。
那么加上终点之后呢?我们一定是选择一对距离最长的点作为终点和起点。
考虑分别计算贡献,对于第一部分一条边有贡献当且仅当它两边的两个部分都有被选中的点,方案数直接组合数计算就好了, \(n\) 不是特别大 \(n\) 或者 \(n^2\) 均可。
对于第二部分,直接暴力枚举所选的点对并且计算可以选的其他点就好了,为了防止重复计算,对于两个点到某个点距离相同且都是最长路径的情况我们选择便后较小来计算贡献。
对于点对 \((x,y)\) 一个合法的 \(z\) 一定满足以下条件:
\(dis_{x,y}>dis_{x,z}\)
\(dis_{x,y}=dis_{x,z} 并且 y<z\)
\(dis_{x,y}>dis_{y,z}\)
\(dis_{x,y}=dis_{y,z} 并且 x<z\)
假设合法的点有 \(cnt\) 个,那么这条路径的贡献就是 \(\binom{k-2}{cnt}\times len\)
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=2e3+10,INF=1e18,mod=998244353;
int n,m,k,root,ans,top,sta[N],s[N],c[N][N],bas[N];
int tim,dfn[N],siz[N],son[N],dep[N],fa[N],topp[N];
int tot=1,head[N],nxt[N<<1],ver[N<<1];
void add_edge(int x,int y){ver[++tot]=y;nxt[tot]=head[x];head[x]=tot;}
void dfs1(int x)
{
siz[x]=1;
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i]; if(to==fa[x]) continue;
fa[to]=x; dep[to]=dep[x]+1;
dfs1(to); siz[x]+=siz[to]; bas[x]+=bas[to];
if(siz[to]>siz[son[x]]) son[x]=to;
if(!bas[to]||bas[to]==m) continue;
ans=(ans+(c[m][k]-c[bas[to]][k]-c[m-bas[to]][k]+2*mod)*2)%mod;
}
}
void dfs2(int x,int tp)
{
topp[x]=tp; dfn[x]=++tim;
if(son[x]) dfs2(son[x],tp);
for(int i=head[x];i;i=nxt[i])
if(!dfn[ver[i]]) dfs2(ver[i],ver[i]);
}
int LCA(int x,int y)
{
while(topp[x]^topp[y])
{
if(dep[topp[x]]<dep[topp[y]]) swap(x,y);
x=fa[topp[x]];
}
if(dep[x]>dep[y]) swap(x,y); return x;
}
inline int dist(int x,int y){return dep[x]+dep[y]-2*dep[LCA(x,y)];}
int power(int x,int y,int p=mod)
{
int temp=1;
while(y)
{
if(y&1) temp=temp*x%p;
x=x*x%p; y>>=1;
}
return temp;
}
void solve(int x,int y)
{
int cnt=0,dis=dist(x,y);
for(int i=1;i<=m;i++)
{
if(s[i]==x||s[i]==y) continue;
if(dist(x,s[i])>dis||dist(y,s[i])>dis) continue;
if(dist(x,s[i])==dis&&s[i]<y) continue;
if(dist(y,s[i])==dis&&s[i]<x) continue;
cnt++;
}
if(cnt>=k-2) ans=(ans-dis*c[cnt][k-2]%mod+mod)%mod;
}
#undef int
int main()
{
#define int long long
freopen("tree.in","r",stdin); freopen("tree.out","w",stdout);
n=read(); m=read(); k=read();
for(int i=1;i<=m;i++) s[i]=read(),bas[s[i]]=1;
for(int i=1,x,y;i<n;i++) x=read(),y=read(),add_edge(x,y),add_edge(y,x);
c[0][0]=1; for(int i=1;i<=n;i++) c[i][0]=c[i][i]=1;
for(int i=1;i<=n;i++) for(int j=1;j<i;j++) c[i][j]=(c[i-1][j]+c[i-1][j-1])%mod;
dfs1(1); dfs2(1,1); sort(s+1,s+m+1);
for(int i=1;i<=m;i++) for(int j=i+1;j<=m;j++) solve(s[i],s[j]);
printf("%lld",ans*power(c[m][k],mod-2)%mod);
return 0;
}
T3 仙人掌
神仙仙人掌+行列式,咕了
大坑未补
T4 对弈
解题思路
又是 老脸买原题 系列。
有一个性质: 在最优策略下,如果可以, Alice 只会向右移动, Bob 只会向左移动。
证明:了证明这个性质,记 \(a_i\) 表示第 \(i\) 个红棋的位置, \(b_i\) 表示第 \(i\) 个蓝棋的位置,我们将一个状态表示为一个 \(\frac{k}{2}\) 元组 \((b_1-a_1-1,b_2-a_2-1...b_{\frac{k}{2}}-a_{\frac{k}{2}}-1)\) 。记 \(d_i=b_i-a_i-1\) ,如果 Alice 将 \(a_i\) 向左移动,那么在它右边的那个蓝棋 \(b_i\) 一定可以移动相同距离,使得对应的 \(d_i\) 不变,但是显然 \(a_i\) 的移动范围变小了,不这么做显然不会更劣。
在这个性质的前提下 \(d_i\) 只可能会越来越小,就变成了一个取石子游戏,也就是(下文中的变量名可能与上文没有关系):
有 \(n\) 堆石子,每堆有 \(a_i\) 个,每次可以选 \(1\sim m\) 堆,从这几堆中各取走若干石子,每堆至少取一个。若轮到某人时,所有石子都已经被取完了,这个人就输了。
那么必败局面当且仅当将所有 \(a_i\) 转成二进制后,每一位 1 的个数 \(\bmod\;(m+1)=0\)
证明:
全为 0 的局面一定是必败态。
任何一个 \(N\) 状态(必败状态),经过一次操作以后必然会到达 \(P\) 状态(必胜状态)。在某一次移动中,至少有一堆被改变,也就是说至少有一个二进制位被改变。由于最多只能改变 \(m\) 堆石子,所以对于任何一个二进制位, 1 的个数至多改变 \(m\) 。而由于原先的总数为 \(m+1\) 的整数倍,所以改变之后必然不可能是 \(m+1\) 的整数倍。故在 \(N\) 状态下一次操作的结果必然是 \(P\) 状态。
任何 \(P\) 状态,总有一种操作使其变化成 \(N\) 状态。从高位到低位考虑所有的二进制位。假设用了某种方法,改变了 \(k\) 堆,使第 \(i\) 位之前的所有位的 1 的个数都变成 \(m+1\) 的整数倍。现在要证明总有一种方法让第 \(i\) 位也变成 \(m+1\) 的整数倍。显然,对于已经改变的那 \(k\) 堆,当前位可以自由选择 0 或 1 。设除去已经更改的 \(k\) 堆,剩下的堆第 \(i\) 位上 1 的总和 \(\bmod\;(m+1)=sum\) ,分类讨论:
- \(sum\le m-k\) 此时可以将这些堆上的 1 全部拿掉,然后让那 \(k\) 堆的第 \(i\) 位全部置成 0 。
- \(sum>m-k\) 此时我们在之前改变的 \(k\) 堆中选择 \(m+1-sum\) 堆,将他们的第 \(i\) 位置成 \(i\) ,剩下的置成 \(0\) 。由于 \(m+1-sum\le k\) ,故这是可以达到的。
那么就可以 DP 了 \(f(i,j)\) 表示考虑了前 \(i\) 位,当前总和为 \(j\) 的方案数。
于是从 \(\frac{k}{2}\) 堆里面选出 \(num\times (m+1)\) 个并且这些石子的第 \(i\) 位为 1 。
并且我们还要选出这几堆的位置,于是方程就是:
\]
\]
最后拿总方案数一减就好了。
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"RP++"<<endl
using namespace std;
inline int read()
{
int x=0,f=1;char ch=getchar();
while(ch>'9'||ch<'0'){if(ch=='-')f=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48);ch=getchar();}
return x*f;
}
const int N=3e4+10,Lg=70,mod=1e9+7;
int ans,n,m,k,lg,fac[N],ifac[N],f[Lg][N];
int power(int x,int y,int p=mod)
{
int temp=1;
while(y)
{
if(y&1) temp=temp*x%mod;
x=x*x%mod; y>>=1;
}
return temp;
}
void add(int &x,int y){x+=y;if(x>mod)x-=mod;}
int C(int x,int y){if(x<y)return 0;return fac[x]*ifac[y]%mod*ifac[x-y]%mod;}
#undef int
int main()
{
#define int long long
freopen("chess.in","r",stdin); freopen("chess.out","w",stdout);
n=read(); k=read(); m=read(); lg=log2(n)+1; f[0][0]=1;
fac[0]=ifac[0]=1; for(int i=1;i<=n;i++) fac[i]=fac[i-1]*i%mod;
ifac[n]=power(fac[n],mod-2); for(int i=n-1;i>=1;i--) ifac[i]=ifac[i+1]*(i+1)%mod;
for(int i=0;i<=lg;i++)
for(int j=0;j<=n-k;j++)
for(int p=0;;p++)
if((p<<i)*(m+1)>n-k||p*(m+1)>k/2) break;
else add(f[i+1][j+(p<<i)*(m+1)],f[i][j]*C(k/2,p*(m+1))%mod);
for(int i=0;i<=n-k;i++) add(ans,f[lg][i]*C(n-i-k/2,k/2)%mod);
printf("%lld",(C(n,k)-ans+mod)%mod);
return 0;
}
NOIP模拟88(多校21)的更多相关文章
- NOIP模拟83(多校16)
前言 CSP之后第一次模拟赛,感觉考的一般. 不得不吐槽多校联测 OJ 上的评测机是真的慢... T1 树上的数 解题思路 感觉自己思维有些固化了,一看题目就感觉是线段树. 考完之后才想起来这玩意直接 ...
- Noip模拟58 2021.9.21(中秋祭&&换机房祭)
第一次在学校过中秋节,给家里人视频电话,感觉快回家了很开心, 然后还吃了汉堡喝饮料非常爽,颓废了一会儿还换了新机房,$Linux2.0$非常dei,少爷机也非常快, 发现好像测评机又成了老爷机,这就是 ...
- Noip模拟22 2021.7.21
T1 d 简化题意就是找到相对平均长宽的偏移量较大的矩形给他删掉 可以说是个贪心,按照a,b分别为第一关键字排序 然后假装删去要求的那么多个按a排序的较小的,然后再去b中, 找到 删去的a中的那几个矩 ...
- Noip模拟45 2021.8.21
一定别删大括号,检查是;还是, ceil函数里面要写double,否则根本没用!!!!!!! T1 打表 正解:打表 考场上很难真正把柿子理解着推出来 况且想要理解题意就很难,比如我就理解错了 半猜着 ...
- NOIP模拟92(多校25)
前言 所以说这次是 HZOI 多校联测巅峰????(题目,数据过水??) T1 石子合并 解题思路 签到题. 发现我们可以给每个数字附一个正负号,每个数字的贡献就是它本身乘上这个符号. 发现至少应该有 ...
- NOIP模拟84(多校17)
T1 宝藏 解题思路 考场上一眼出 \(nlog^2\) 做法,然后没看见是 1s 3e5 的数据,我竟然以为自己切了?? 考完之后尝试着把二分改为指针的移动,然后就过了??或许是数据水吧,感觉自己的 ...
- NOIP模拟85(多校18)
前言 好像每个题目背景所描述的人都是某部番里的角色,热切好像都挺惨的(情感上的惨). 然后我只知道 T1 的莓,确实挺惨... T1 莓良心 解题思路 首先答案只与 \(w\) 的和有关系,于是问题就 ...
- NOIP模拟86(多校19)
T1 特殊字符串 解题思路 \(f_{i,j}\) 表示前 \(i\) 个字符中结尾为 \(j\) 的最大贡献. 转移枚举当前位置于之前位置结尾的组合加上贡献即可. 对于边界问题,容易发现选择 1 一 ...
- NOIP模拟96(多校29)
T1 子集和 解题思路 大概是一个退背包的大白板,然而我考场上想复杂了,竟然还用到了组合数. 但是大概意思是一样的,有数的最小值一定是一个在 \(a\) 数组中存在的数字. 那么我们想办法除去它对应的 ...
随机推荐
- 迷你商城后端管理系统 ———— stage2 项目的核心代码实现
应用程序主函数接口 @SpringBootApplication(scanBasePackages = {"org.linlinjava.litemall.db", "o ...
- Shell系列(22)- 字符截取命令awk
简介 awk是一个数据处理工具,相比于sed常常作用于一整行的处理,awk则比较倾向于将一行分成数个"字段"来处理 awk的流程是依次读取每一行数据,读取完一行数据后,进行条件判断 ...
- jmeter 元件详细介绍
一.基本介绍 可以进行功能测试.性能测试.自动化测试. 二.配置元件介绍 线程组:threads 虚拟用户数,设置Jmeter按照什么场景运行,是一系列线程的集合,每一个线程都代表一个正在使用应用程序 ...
- JMeter多个线程组的使用说明
Run Thread Groups consecutively (i.e one at a time),即独立运行每个线程组(例如在一个组运行结束后启动下一个) https://help.aliyun ...
- php无限分类 构建树形结构
<?php class Classification { const PARENT_ID = 'parentid'; const ID = 'id'; const CHILDREN = 'chi ...
- 鸿蒙内核源码分析(静态链接篇) | 完整小项目看透静态链接过程 | 百篇博客分析OpenHarmony源码 | v54.01
百篇博客系列篇.本篇为: v54.xx 鸿蒙内核源码分析(静态链接篇) | 完整小项目看透静态链接过程 | 51.c.h.o 下图是一个可执行文件编译,链接的过程. 本篇将通过一个完整的小工程来阐述E ...
- 鸿蒙内核源码分析(CPU篇) | 整个内核就是一个死循环 | 祝新的一年牛气冲天 ! | v32.02
百篇博客系列篇.本篇为: v32.xx 鸿蒙内核源码分析(CPU篇) | 整个内核就是一个死循环 | 51.c.h .o 任务管理相关篇为: v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度 ...
- Sentry 监控 - Alerts 告警
系列 1 分钟快速使用 Docker 上手最新版 Sentry-CLI - 创建版本 快速使用 Docker 上手 Sentry-CLI - 30 秒上手 Source Maps Sentry For ...
- 深入浅出WPF-04.x名称空间详解
x名称空间详解 几个需要特别说明的名称空间: x:Class 用来标记XAML和后台代码之间的合并关系.x:Class根节点的类型必须和x:Class值指向的类型保持一致.x:Class的值指向的类型 ...
- Visual Studio 6.0 在 Windows 10 下崩溃的一种解决方法
Visual Studio 6.0 下载地址: https://winworldpc.com/product/microsoft-visual-stu/60 安装步骤: https://www.cod ...