NOIP模拟52
「异或·赌神·路径·树」 on 9.13
梦是现实的延续,现实是梦的终结。
前言
T1 就是个找规律,T2 这一类型的题我好像都不是特别会做是个短版。。
T3 看出来是 点分治+FFT 的板子题了,但是转念一想,点分治板子忘了,FFT板子,压根就没记住,直接暴力走人。。
T4 做梦都没有想到数据这么水,暴力碾标算(尽管我考场上只打了一个最暴力的暴力。。)
T1 异或
解题思路
我也不知道该怎么说,就是打表找规律。。。看一下官方题解吧:
考虑每一位的贡献,从低到高的第 \(i\) 位会每隔 \(2^i\) 个数变化一次,于是第 \(i\) 位对答案的贡献就是 \(\lfloor\dfrac{n}{2^i}\rfloor\) ,把每一位的贡献加起来即可
结合赛时的想法感觉这里的暴力可以莽到 \(10^{16}\)(如果时间够用的话),在规定时间内显然 \(10^8\) 是可以直接算出来的,我们预处理一下 \([0,10^8]\times 10^8\) 的最开始的那个值存起来,然后每次向后计算剩余的部分。
还有一个比较好用的函数 __builtin_popcount()
可以用来计算一个数字二进制表示的 1 的个数。
code
#include<bits/stdc++.h>
#define int long long
#define ull unsigned long long
#define f() cout<<"Failed"<<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=1e6+10,Lg=70;
int n,ans,p[Lg];
signed main()
{
n=read()-1; p[0]=1;
for(int i=1;i<=60;i++) p[i]=p[i-1]<<1;
for(int i=1;i<=60;i++)
{
if(p[i-1]-1>n) break;
ans+=((n-p[i-1]+1)/p[i]+1)*i;
}
printf("%lld",ans);
return 0;
}
T2 赌神
解题思路
先想一下 \(n=2\) 的情况,假设 \(dp_{i,j}\) 表示你当前有 1 个筹码,箱子里两种颜色的球分别有 \(i,j\) 个,最终能获得多少筹码 。
因为两个人都足够聪明,因此只有我们让幕后黑手选择调出的颜色的值都是相同的时候才会达到我们的目的。
假设在第一种颜色上押 \(x\) 个筹码,在第二种颜色上押 \(y\) 个筹码,就一定有 \(dp_{i-1,j}\times x=dp_{i,j-1}\times y\) ,再联立 \(x+y=1\),每次选择的贡献是 \(2\),就可以得到如下柿子:
\]
考虑优化,假设 \(f_{i,j}=\dfrac{dp_{i,j}}{2^{i+j}}\),那么 f 就可以用如下方式转移:
\]
两边都取倒数就可以得到:
\]
\(\dfrac{1}{f_{i,j}}\) 其实就是在二维平面内,一次只能向上或向右走,从 \((0,0)\) 走到 \((i,j)\) 的方案数,也就是 \(C_{i,j}^i\) 所以 \(Ans=\dfrac{2^{i+j}}{C_{i,j}^i}\)
扩展到 n 维就是从 \((0,0…0)\) 到 \((x_1,x_2…x_n)\) 就是各个方向合起来的总的排列除去每个等效的点的内部排列。
假设 \(Sum=\sum\limits_{i=1}^nx_i\),答案就是 \(n^{Sum}\times\dfrac{A_{Sum}^{Sum}}{\prod\limits^{n}_{i=1}A_{x_i}^{x_i}}\)
也就是 \(n^{Sum}\times\dfrac{Sum!}{\prod\limits^{n}_{i=1}x_i!}\)
code
#include<bits/stdc++.h>
#define int long long
#define f() cout<<"Failed"<<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=1e6+10,mod=998244353;
int n,sum,ans=1,fac[N],ifac[N],s[N];
int power(int x,int y){int temp=1;while(y){if(y&1) temp=temp*x%mod;x=x*x%mod; y>>=1;}return temp;}
signed main(){
n=read(); for(int i=1;i<=n;i++) s[i]=read(),sum+=s[i];
fac[0]=ifac[0]=1; for(int i=1;i<=sum;i++) fac[i]=fac[i-1]*i%mod;
ifac[sum]=power(fac[sum],mod-2); for(int i=sum-1;i>=1;i--) ifac[i]=ifac[i+1]*(i+1)%mod;
for(int i=1;i<=n;i++) ans=ans*ifac[s[i]]%mod;
printf("%lld",power(ans*fac[sum]%mod,mod-2)*power(n,sum)%mod); return 0;
}
T3 路径
解题思路
显然是点分治。
把每一棵子树内的点到分治中心的距离当作多项式的次幂,每种距离的路径的条数就是对应的系数,这样问题就可以通过多项式乘法解决了。
直接暴力负责度是 \(\mathcal{O}(n^2logn)\) 但是严格小于。
通过 FFT 或者 NTT 可以优化到 \(\mathcal{O}(nlog^2n)\) 卡一卡常足以通过此题。
但是正解是 斯特林反演,粘一下官方题解吧。。
code
#include<bits/stdc++.h>
#define int long long
#define f() cout<<"Failed"<<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 double pi=acos(-1.0);
const int N=1e6+10,M=1e7+10,mod=998244353;
int n,S,m,root,ans,top,p[N],dep[N],d[M<<1],sta[N],siz[N],mxsiz[N];
int tot=1,head[N],nxt[N<<1],ver[N<<1];
bool vis[N];
struct Plural
{
double a,b;
Plural friend operator + (Plural x,Plural y){return (Plural){x.a+y.a,x.b+y.b};}
Plural friend operator - (Plural x,Plural y){return (Plural){x.a-y.a,x.b-y.b};}
Plural friend operator * (Plural x,Plural y){return (Plural){x.a*y.a-x.b*y.b,x.a*y.b+y.a*x.b};}
}a[M<<1];
void add_edge(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
int power(int x,int y)
{
int temp=1;
while(y)
{
if(y&1) temp=temp*x%mod;
x=x*x%mod; y>>=1;
}
return temp;
}
void get_root(int x,int fat)
{
siz[x]=1; mxsiz[x]=0;
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i];
if(to==fat||vis[to]) continue;
get_root(to,x);
siz[x]+=siz[to];
mxsiz[x]=max(mxsiz[x],siz[to]);
}
mxsiz[x]=max(mxsiz[x],S-siz[x]);
if(mxsiz[x]<mxsiz[root]) root=x;
}
void get_deep(int x,int fa)
{
sta[++top]=dep[x];
for(int i=head[x];i;i=nxt[i])
{
int to=ver[i];
if(to==fa||vis[to]) continue;
dep[to]=dep[x]+1;
get_deep(to,x);
}
}
void FFT(Plural s[],int lim,int typ)
{
for(int i=0;i<lim;i++)
if(i<d[i])
swap(s[i],s[d[i]]);
for(int mid=1,len=2;mid<lim;mid<<=1,len<<=1)
{
Plural wn=(Plural){cos(pi/mid),typ*sin(pi/mid)};
for(int j=0;j<lim;j+=len)
{
Plural w=(Plural){1,0};
for(int k=0;k<mid;k++,w=w*wn)
{
Plural tmp1=s[j+k],tmp2=w*s[j+mid+k];
s[j+k]=tmp1+tmp2;
s[j+mid+k]=tmp1-tmp2;
}
}
}
}
void solve(int typ)
{
if(!top) return ;
int mx=0,lim=1,l=0;
for(int i=1;i<=top;i++) mx=max(mx,sta[i]);
while(lim<=(mx<<1)) lim<<=1,l++;
for(int i=0;i<=lim;i++) a[i].a=a[i].b=0;
for(int i=1;i<=top;i++) a[sta[i]].a++;
for(int i=0;i<lim;i++) d[i]=(d[i>>1]>>1|((i&1)<<l-1));
FFT(a,lim,1);
for(int i=0;i<=lim;i++) a[i]=a[i]*a[i];
FFT(a,lim,-1);
for(int i=1;i<=lim;i++) ans+=((int)(a[i].a/lim+0.5))%mod*p[i]%mod*typ+mod;
}
void dfs(int x)
{
vis[x]=true; dep[x]=top=0; get_deep(x,0); solve(1);
for(int i=head[x];i;i=nxt[i])
if(!vis[ver[i]])
{
int to=ver[i];
dep[to]=1; top=0; get_deep(to,0); solve(-1);
S=siz[to]; root=0; get_root(to,0); dfs(root);
}
}
signed main()
{
n=read(); m=read();
for(int i=1;i<=n;i++) p[i]=power(i,m);
for(int i=1,x,y;i<n;i++)
x=read(),y=read(),
add_edge(x,y),add_edge(y,x);
S=mxsiz[0]=n; get_root(1,0); dfs(root);
printf("%lld",ans%mod*power(2,mod-2)%mod);
return 0;
}
T4 树
解题思路
数据实在是太水。
对于每一个深度开一个 vector 记录出现的点,然后每一次更改直接暴力循环查找符合条件的深度里 dfs 序在 \([dfn_v,dfn_v+siz_v-1]\) 的点进行更改。
然后就可以干掉。。当然也可以对于每一个深度开一棵线段树进行区间修改,但是数据太水我又懒得打。。
正解就暂时咕了。
code
#include<bits/stdc++.h>
#define int long long
#define f() cout<<"Failed"<<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=3e5+10;
int n,q,tim,s[N],dfn[N],siz[N],dep[N],mxdep[N];
int tot=1,head[N],ver[N<<1],nxt[N<<1];
vector<int> v[N];
void add_edge(int x,int y)
{
ver[++tot]=y;
nxt[tot]=head[x];
head[x]=tot;
}
void dfs(int x)
{
siz[x]=1; dfn[x]=++tim; mxdep[x]=dep[x];
v[dep[x]].push_back(x);
for(int i=head[x];i;i=nxt[i])
if(!siz[ver[i]])
dep[ver[i]]=dep[x]+1,dfs(ver[i]),siz[x]+=siz[ver[i]],mxdep[x]=max(mxdep[x],mxdep[ver[i]]);
}
signed main()
{
n=read(); q=read();
for(int i=1,x,y;i<n;i++)
x=read(),y=read(),
add_edge(x,y),add_edge(y,x);
dep[1]=1;dfs(1);
while(q--)
{
int opt,pos,x,y,z; opt=read(); pos=read();
if(opt==2){printf("%lld\n",s[pos]);continue;}
x=read(); y=read(); z=read();
for(int i=dep[pos]+y;i<=mxdep[pos];i+=x)
for(int j=0;j<v[i].size();j++)
if(dfn[v[i][j]]>=dfn[pos]&&dfn[v[i][j]]<=dfn[pos]+siz[pos]-1)
s[v[i][j]]+=z;
}
return 0;
}