[62] (NOIP 集训) NOIP2024加赛 2
lhx: 为啥你不会线性筛,这么重要的东西
lhx: 你不筛积性函数吗
我: 我一般做数学题推不到需要上线性筛的地方
lhx: 哦那你确实用不到
A.新的阶乘
找质数
发现
因此尝试枚举所有
发现这玩意和埃氏筛天作之合,你枚举倍数的时候顺便就把质数筛了,考虑直接上埃氏筛
因为不会怎么求因子出现个数于是用了复杂度很假的暴力做法
理论
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
bool notprime[10000001];
vector<int>ans;
inline int powof(int i,int j){
int res=0;
while(j){
if(j%i==0) j/=i,res++;
else break;
}
return res;
}
signed main(){
cin>>n;
notprime[0]=true;
notprime[1]=true;
for(int i=2;i<=n;++i){
if(notprime[i]==false){
ans.push_back(i);
int cnt=n-i+1;
for(int j=2;j*i<=n;++j){
notprime[i*j]=true;
cnt+=(powof(i,j)+1)*(n-j*i+1);
}
ans.push_back(cnt);
}
}
cout<<"f("<<n<<")=";
if(n<=1) cout<<n;
for(int i=0;i<=(int)ans.size()-1;i+=2){
if(i!=0) cout<<'*';
if(ans[i+1]==1){
cout<<ans[i];
}
else{
cout<<ans[i]<<'^'<<ans[i+1];
}
}
}
B.博弈树
写爆搜打表找规律发现:所有节点中,Bob 至多只有一个
爆搜
#include<bits/stdc++.h>
using namespace std;
int n,q;
vector<int>e[1001];
int deep[1001],fa[10][1001],w[10][1001];
void dfs(int now,int last){
deep[now]=deep[last]+1;
fa[0][now]=last;
for(int i:e[now]){
if(i!=last){
w[0][i]=1;
dfs(i,now);
}
}
}
inline void prework(){
for(int i=1;i<=9;++i){
for(int j=1;j<=n;++j){
fa[i][j]=fa[i-1][fa[i-1][j]];
w[i][j]=w[i-1][j]+w[i-1][fa[i-1][j]];
}
}
}
inline int dis(int x,int y){
if(deep[x]<deep[y]) swap(x,y);
int res=0;
for(int i=9;i>=0;--i){
if(deep[fa[i][x]]>=deep[y]){
res+=w[i][x];
x=fa[i][x];
}
}
if(x==y) return res;
for(int i=9;i>=0;--i){
if(fa[i][x]!=fa[i][y]){
res+=w[i][x]+w[i][y];
x=fa[i][x];
y=fa[i][y];
}
}
return res+w[0][x]+w[0][y];
}
//true = win
bool bydfs(int now,int lastdis){
for(int i=1;i<=n;++i){
if(i!=now){
int tmp=dis(i,now);
if(tmp>lastdis){
if(bydfs(i,tmp)==false) return true;
}
}
}
return false;
}
int main(){
cin>>n>>q;
for(int i=1;i<=n-1;++i){
int x,y;cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1,0);
prework();
while(q--){
int t;cin>>t;
cout<<(bydfs(t,0)?"Alice":"Bob")<<'\n';
}
}
而且这个点离树的中心极其近
额外发现当树的重心有两个的时候,没有 Bob
尝试直接特判树的重心假完了
手摸了出错的数据发现,应该是自己树的重心这个思路不对,考虑到 Bob 和 Alice 的博弈过程,这里应该是某种和树上距离有关的 “重心”
于是想出了正确的结论
- 定义重心为使得到所有点的距离的最大值最小的节点
- 如果重心只有一个,Bob 会在此处获胜,否则均为 Alice 获胜
证明:
考虑如果先手在直径的一个端点上,可以通过移动到另一个端点来直接胜出,否则不能移动到直径的端点上,否则后手会移动到另一个端点而直接胜出
考虑删除了原树所有直径的端点的树,如果初始点在这棵树上为直径的某一个端点,那么也一定是先手必胜的,因为先手可以将点移动到直径另一个端点,这样后手就一定会将点移动到原树的直径端点上,并且移动的长度一定小于原树直径,这样先手就可以将点移动到原树的另一个直径端点取得胜利
因此后手胜利,当且仅当先手无论如何都会移动到某层的直径端点上,这样的情况只会在只存在一个点不在某层的直径端点上的图上,并且该点恰好为重心
但是这个东西却是
但是你没必要去枚举所有的点,因为重心一定在树的直径上,而且一定是直径上靠近中点的位置,你直接去判断靠近直径中点那几个点就行了
被
#include<bits/stdc++.h>
using namespace std;
int n,q;
vector<int>e[100001];
vector<int>zx;
int size[100001];
int maxsonsize[100001];
int deep[100001],fa[20][100001],w[20][100001];
void dfs(int now,int last){
deep[now]=deep[last]+1;
fa[0][now]=last;
for(int i:e[now]){
if(i!=last){
w[0][i]=1;
dfs(i,now);
}
}
}
inline void prework(){
for(int i=1;i<=19;++i){
for(int j=1;j<=n;++j){
fa[i][j]=fa[i-1][fa[i-1][j]];
w[i][j]=w[i-1][j]+w[i-1][fa[i-1][j]];
}
}
}
inline int dis(int x,int y){
if(deep[x]<deep[y]) swap(x,y);
int res=0;
for(int i=19;i>=0;--i){
if(deep[fa[i][x]]>=deep[y]){
res+=w[i][x];
x=fa[i][x];
}
}
if(x==y) return res;
for(int i=19;i>=0;--i){
if(fa[i][x]!=fa[i][y]){
res+=w[i][x]+w[i][y];
x=fa[i][x];
y=fa[i][y];
}
}
return res+w[0][x]+w[0][y];
}
int minsize=0x7fffffff;
void dfs2(int i){
int maxdis=0;
for(int j=1;j<=n;++j){
maxdis=max(maxdis,dis(i,j));
}
if(maxdis<minsize){
minsize=maxdis;
zx={i};
}
else if(maxdis==minsize){
zx.push_back(i);
}
}
int dis5[100001];
int dis6[100001];
int maxdis5,maxdis5id;
int maxdis6,maxdis6id;
void dfs5(int now,int last){
for(int i:e[now]){
if(i!=last){
dis5[i]=dis5[now]+1;
if(dis5[i]>maxdis5){
maxdis5=dis5[i];
maxdis5id=i;
}
dfs5(i,now);
}
}
}
void dfs6(int now,int last){
for(int i:e[now]){
if(i!=last){
dis6[i]=dis6[now]+1;
if(dis6[i]>maxdis6){
maxdis6=dis6[i];
maxdis6id=i;
}
dfs6(i,now);
}
}
}
vector<int>zj;
bool dfs7(int now,int last,int tar){
if(now==tar){
zj.push_back(now);
return true;
}
for(int i:e[now]){
if(i!=last){
if(dfs7(i,now,tar)){
zj.push_back(now);
return true;
}
}
}
return false;
}
void dfs8(){
int tmp=(int)zj.size()/2;
int tmp2=tmp-1;
if(tmp>=0) for(int i:e[zj[tmp]]) dfs2(i);
if(tmp2>=0) for(int i:e[zj[tmp2]]) dfs2(i);
}
int main(){
freopen("tree.in","r",stdin);
freopen("tree.out","w",stdout);
cin>>n>>q;
if(n==1){
while(q--){
cout<<"Bob\n";
}
return 0;
}
for(int i=1;i<=n-1;++i){
int x,y;
cin>>x>>y;
e[x].push_back(y);
e[y].push_back(x);
}
dfs(1,0);
prework();
dfs5(1,0);
dfs6(maxdis5id,0);
dfs7(maxdis5id,0,maxdis6id);
dfs8();
while(q--){
int t;cin>>t;
if(zx.size()!=1ull) cout<<"Alice\n";
else if(zx[0]!=t) cout<<"Alice\n";
else cout<<"Bob\n";
}
}
C.划分
先说 corner case
显然了,只有唯一的划分方式
- 字符串最前面有超过
位全是
这个时候直接在前面划就行了,无论你怎么拆后面带
设前面有
注意这个 case 有一个全
- 通解
上面两个 corner case 都要判掉再做通解
最优解一定形如选出
这样剩余的情况就只有
因为
发现有人不理解这句话:实际上,因为你
因此只比较那个长区间,直接暴力比较是
这里注意最长公共前缀等于原串的情况,有可能会越界
还是那句话,谁的高位
注意卡模数的问题
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=998244353;
const int P1=1e9+3,P2=1e7+0721;
int n,k;
string x;
int power(int a,int t){
int base=a,ans=1;
while(t){
if(t&1){
ans=ans*base%p;
}
base=base*base%p;
t>>=1;
}
return ans;
}
int fact[2000001];
int C(int n,int m){
return fact[n]*power(fact[m]*fact[n-m]%p,p-2)%p;
}
int cal(int l,int r){
int res=0;
for(int i=l;i<=r;++i){
res=(res*2+x[i]-'0')%p;
}
return res;
}
const unsigned long long num=233,num2=2333;
unsigned long long h[2000001],basenum[2000001];
unsigned long long geth(int l,int r){
if(l>r) return 0;
return (h[r]-h[l-1]*basenum[r-l+1]%P1+P1)%P1;
}
unsigned long long h2[2000001],basenum2[2000001];
unsigned long long geth2(int l,int r){
if(l>r) return 0;
return (h2[r]-h2[l-1]*basenum2[r-l+1]%P2+P2)%P2;
}
bool great(int now,int ori){
int l=0,r=n-k+1,ans=0;
while(l<=r){
int mid=(l+r)/2;
if(geth(now,now+mid-1)==geth(ori,ori+mid-1) and geth2(now,now+mid-1)==geth2(ori,ori+mid-1)){
l=mid+1;
ans=mid;
}
else r=mid-1;
}
if(ans==n-k+1) return false;
return x[now+ans]>x[ori+ans];
}
inline int cal2(int l,int r){
int res=0;
for(int i=1;i<l;++i){
res=(res+x[i]-'0')%p;
}
int res2=0;
for(int i=l;i<=r;++i){
res2=(res2*2+x[i]-'0')%p;
}
res=(res+res2)%p;
for(int i=r+1;i<=n;++i){
res=(res+x[i]-'0')%p;
}
return res;
}
signed main(){
cin>>n>>k>>x;x=" "+x;
if(n==k){
int res=0;
for(int i=1;i<=n;++i){
res=(res+x[i]-'0')%p;
}
cout<<res<<" "<<1;
return 0;
}
fact[0]=1;
basenum[0]=1;
basenum2[0]=1;
for(int i=1;i<=n;++i){
fact[i]=fact[i-1]*i%p;
h[i]=(h[i-1]*num+x[i])%P1;
basenum[i]=basenum[i-1]*num%P1;
h2[i]=(h2[i-1]*num2+x[i])%P2;
basenum2[i]=basenum2[i-1]*num2%P2;
}
int first1=n;
for(int i=1;i<=n;++i){
if(x[i]=='1'){
first1=i;
break;
}
}
if(first1>=k){
cout<<cal(1,n)<<' ';
int res=0;
for(int i=k-1;i<=first1-1;++i){
res=(res+C(first1-1,i))%p;
}
cout<<res;
return 0;
}
int maxid=0;
for(int i=1;i<=k;++i){
if(maxid==0) maxid=i;
else{
if(great(i,maxid)){
maxid=i;
}
}
}
cout<<cal2(maxid,maxid+n-k)<<" ";
int res=0;
for(int i=1;i<=k;++i){
if(geth(i,i+n-k-1)==geth(maxid,maxid+n-k-1) and geth2(i,i+n-k-1)==geth2(maxid,maxid+n-k-1)) res++;
}
cout<<res;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!