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\) 数组中存在的数字. 那么我们想办法除去它对应的 ...
随机推荐
- php curl发送数据和文件
function mycurl($file, $url, $aid) { // 如果文件名是中文名,将中文字符编码转换一下 $file=iconv("UTF-8","gb ...
- js 获取转换网址中文参数
var search = decodeURI(location.search).substr(1); console.log(search); decodeURI 方法返回一个已编码的统一资源标识符 ...
- jquery获取一个元素符合条件的第一个父元素
closest jQuery 1.3新增.从元素本身开始,逐级向上级元素匹配,并返回最先匹配的元素.. closest会首先检查当前元素是否匹配,如果匹配则直接返回元素本身.如果不匹配则向上查找父元素 ...
- Linux系列(40) - 自动同步时间chrony
前言 Centos8开始取消了ntp同步时间,改为chrony同步 chrony工具安装 yum -y install chrony 修改配置文件 将配置文件中的同步服务器修改为国内的时间服务器(推荐 ...
- Dubbo 学习(二)服务注册与发现
在上一篇中我们提到,dubbo官网上推荐使用ZooKeeper作为注册中心.那么今天我们就通过代码来实践一番,看看一个dubbo的服务消费者如果找到通过ZooKeeper暴露自己的dubbo服务提供者 ...
- jmeter之命令行执行jmx脚本
使用界面执行不稳定,且保存报告非常麻烦 https://www.jb51.net/article/191367.htm 作者:Anthony_tester 来源:CSDN 原文:https://blo ...
- 制作python程序windows安装包(飞机大战源码)
本文以飞机大战源码为例: 1.首先使用pyinstaller -w xxx.py打包 -w的意思是不显示命令行:飞机大战源码由多个.py文件以及一些图片,音乐文件组成,我们将main.py打包, ...
- YbtOJ#573-后缀表达【二分图匹配】
正题 题目链接:https://www.ybtoj.com.cn/contest/115/problem/2 题目大意 给出一个包含字母变量和若干种同级操作符的后缀表达式.求一个等价的表达式满足该表达 ...
- 解除你学习Python自动化测试框架的所有疑惑,开启学习直通车
学习框架第一步 前言 很多同学学完Python基础后出现迷茫......有同感的小伙伴,点赞关注........ 学习完Python还要学习什么? 什么是自动化测试框架? 如何搭建自动化测试框架? 甚 ...
- 关于java实体类时间类型的格式化调整问题
关于java bean在后台\转化为json交给前台时间类型格式调整的方法: 首先要引入fastjson依赖. 在实体类上使用注解: @JsonFormat(pattern = "yyyy- ...